akm-cli 0.7.5 → 0.8.0-rc.11

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 (300) hide show
  1. package/{.github/CHANGELOG.md → CHANGELOG.md} +192 -2
  2. package/README.md +22 -6
  3. package/SECURITY.md +93 -0
  4. package/dist/cli/config-migrate.js +144 -0
  5. package/dist/cli/config-validate.js +39 -0
  6. package/dist/cli/confirm.js +73 -0
  7. package/dist/cli/parse-args.js +133 -0
  8. package/dist/cli/shared.js +129 -0
  9. package/dist/cli.js +2569 -1449
  10. package/dist/commands/add-cli.js +279 -0
  11. package/dist/commands/agent-dispatch.js +110 -0
  12. package/dist/commands/agent-support.js +68 -0
  13. package/dist/commands/completions.js +3 -0
  14. package/dist/commands/config-cli.js +130 -534
  15. package/dist/commands/consolidate.js +2122 -0
  16. package/dist/commands/curate.js +44 -3
  17. package/dist/commands/db-cli.js +23 -0
  18. package/dist/commands/distill-promotion-policy.js +660 -0
  19. package/dist/commands/distill.js +1075 -77
  20. package/dist/commands/env.js +213 -0
  21. package/dist/commands/eval-cases.js +43 -0
  22. package/dist/commands/events.js +5 -23
  23. package/dist/commands/extract-cli.js +127 -0
  24. package/dist/commands/extract-prompt.js +204 -0
  25. package/dist/commands/extract.js +477 -0
  26. package/dist/commands/feedback-cli.js +331 -0
  27. package/dist/commands/graph.js +477 -0
  28. package/dist/commands/health.js +1302 -0
  29. package/dist/commands/help/help-accept.md +12 -0
  30. package/dist/commands/help/help-improve.md +69 -0
  31. package/dist/commands/help/help-proposals.md +18 -0
  32. package/dist/commands/help/help-propose.md +17 -0
  33. package/dist/commands/help/help-reject.md +11 -0
  34. package/dist/commands/history.js +54 -46
  35. package/dist/commands/improve-auto-accept.js +97 -0
  36. package/dist/commands/improve-cli.js +217 -0
  37. package/dist/commands/improve-profiles.js +166 -0
  38. package/dist/commands/improve-result-file.js +167 -0
  39. package/dist/commands/improve.js +2373 -0
  40. package/dist/commands/info.js +5 -2
  41. package/dist/commands/init.js +50 -2
  42. package/dist/commands/installed-stashes.js +102 -139
  43. package/dist/commands/knowledge.js +136 -0
  44. package/dist/commands/lint/agent-linter.js +49 -0
  45. package/dist/commands/lint/base-linter.js +479 -0
  46. package/dist/commands/lint/command-linter.js +49 -0
  47. package/dist/commands/lint/default-linter.js +16 -0
  48. package/dist/commands/lint/env-key-rules.js +154 -0
  49. package/dist/commands/lint/index.js +196 -0
  50. package/dist/commands/lint/knowledge-linter.js +16 -0
  51. package/dist/commands/lint/markdown-insertion.js +343 -0
  52. package/dist/commands/lint/memory-linter.js +61 -0
  53. package/dist/commands/lint/registry.js +36 -0
  54. package/dist/commands/lint/skill-linter.js +45 -0
  55. package/dist/commands/lint/task-linter.js +50 -0
  56. package/dist/commands/lint/types.js +4 -0
  57. package/dist/commands/lint/workflow-linter.js +56 -0
  58. package/dist/commands/lint.js +4 -0
  59. package/dist/commands/migration-help.js +5 -2
  60. package/dist/commands/proposal.js +67 -12
  61. package/dist/commands/propose.js +86 -31
  62. package/dist/commands/reflect.js +1091 -73
  63. package/dist/commands/registry-cli.js +150 -0
  64. package/dist/commands/registry-search.js +5 -2
  65. package/dist/commands/remember-cli.js +257 -0
  66. package/dist/commands/remember.js +69 -6
  67. package/dist/commands/schema-repair.js +203 -0
  68. package/dist/commands/search.js +115 -14
  69. package/dist/commands/secret.js +173 -0
  70. package/dist/commands/self-update.js +3 -0
  71. package/dist/commands/show.js +148 -25
  72. package/dist/commands/source-add.js +17 -45
  73. package/dist/commands/source-clone.js +3 -0
  74. package/dist/commands/source-manage.js +14 -19
  75. package/dist/commands/tasks.js +437 -0
  76. package/dist/commands/url-checker.js +42 -0
  77. package/dist/core/action-contributors.js +28 -0
  78. package/dist/core/asset-ref.js +17 -2
  79. package/dist/core/asset-registry.js +12 -17
  80. package/dist/core/asset-serialize.js +88 -0
  81. package/dist/core/asset-spec.js +67 -1
  82. package/dist/core/common.js +182 -0
  83. package/dist/core/concurrent.js +25 -0
  84. package/dist/core/config-io.js +347 -0
  85. package/dist/core/config-migration.js +622 -0
  86. package/dist/core/config-schema.js +534 -0
  87. package/dist/core/config-sources.js +108 -0
  88. package/dist/core/config-types.js +4 -0
  89. package/dist/core/config-walker.js +337 -0
  90. package/dist/core/config.js +364 -981
  91. package/dist/core/errors.js +42 -20
  92. package/dist/core/events.js +91 -138
  93. package/dist/core/file-lock.js +104 -0
  94. package/dist/core/frontmatter.js +75 -8
  95. package/dist/core/lesson-lint.js +3 -0
  96. package/dist/core/markdown.js +20 -0
  97. package/dist/core/memory-belief.js +62 -0
  98. package/dist/core/memory-contradiction-detect.js +274 -0
  99. package/dist/core/memory-improve.js +806 -0
  100. package/dist/core/parse.js +158 -0
  101. package/dist/core/paths.js +280 -14
  102. package/dist/core/proposal-quality-validators.js +380 -0
  103. package/dist/core/proposal-validators.js +69 -0
  104. package/dist/core/proposals.js +512 -42
  105. package/dist/core/state-db.js +1068 -0
  106. package/dist/core/text-truncation.js +107 -0
  107. package/dist/core/time.js +54 -0
  108. package/dist/core/tty.js +59 -0
  109. package/dist/core/warn.js +64 -1
  110. package/dist/core/write-source.js +3 -0
  111. package/dist/indexer/db-backup.js +391 -0
  112. package/dist/indexer/db-search.js +178 -256
  113. package/dist/indexer/db.js +975 -103
  114. package/dist/indexer/ensure-index.js +64 -0
  115. package/dist/indexer/file-context.js +3 -0
  116. package/dist/indexer/graph-boost.js +376 -101
  117. package/dist/indexer/graph-db.js +391 -0
  118. package/dist/indexer/graph-dedup.js +95 -0
  119. package/dist/indexer/graph-extraction.js +550 -124
  120. package/dist/indexer/index-context.js +4 -0
  121. package/dist/indexer/indexer.js +523 -301
  122. package/dist/indexer/llm-cache.js +52 -0
  123. package/dist/indexer/manifest.js +3 -0
  124. package/dist/indexer/matchers.js +167 -160
  125. package/dist/indexer/memory-inference.js +152 -74
  126. package/dist/indexer/metadata-contributors.js +29 -0
  127. package/dist/indexer/metadata.js +275 -196
  128. package/dist/indexer/path-resolver.js +92 -0
  129. package/dist/indexer/project-context.js +192 -0
  130. package/dist/indexer/ranking-contributors.js +331 -0
  131. package/dist/indexer/ranking.js +81 -0
  132. package/dist/indexer/search-fields.js +5 -9
  133. package/dist/indexer/search-hit-enrichers.js +111 -0
  134. package/dist/indexer/search-source.js +44 -10
  135. package/dist/indexer/semantic-status.js +6 -17
  136. package/dist/indexer/staleness-detect.js +447 -0
  137. package/dist/indexer/usage-events.js +12 -9
  138. package/dist/indexer/walker.js +28 -0
  139. package/dist/integrations/agent/builders.js +135 -0
  140. package/dist/integrations/agent/config.js +122 -230
  141. package/dist/integrations/agent/detect.js +3 -0
  142. package/dist/integrations/agent/index.js +7 -13
  143. package/dist/integrations/agent/model-aliases.js +55 -0
  144. package/dist/integrations/agent/profiles.js +70 -5
  145. package/dist/integrations/agent/prompts.js +214 -80
  146. package/dist/integrations/agent/runner.js +151 -0
  147. package/dist/integrations/agent/sdk-runner.js +126 -0
  148. package/dist/integrations/agent/spawn.js +118 -23
  149. package/dist/integrations/github.js +3 -0
  150. package/dist/integrations/lockfile.js +32 -69
  151. package/dist/integrations/session-logs/index.js +69 -0
  152. package/dist/integrations/session-logs/inline-refs.js +35 -0
  153. package/dist/integrations/session-logs/pre-filter.js +152 -0
  154. package/dist/integrations/session-logs/providers/claude-code.js +282 -0
  155. package/dist/integrations/session-logs/providers/opencode.js +258 -0
  156. package/dist/integrations/session-logs/types.js +4 -0
  157. package/dist/llm/call-ai.js +62 -0
  158. package/dist/llm/client.js +77 -124
  159. package/dist/llm/embedder.js +20 -29
  160. package/dist/llm/embedders/cache.js +3 -7
  161. package/dist/llm/embedders/local.js +42 -1
  162. package/dist/llm/embedders/remote.js +20 -8
  163. package/dist/llm/embedders/types.js +3 -7
  164. package/dist/llm/feature-gate.js +95 -48
  165. package/dist/llm/graph-extract.js +676 -70
  166. package/dist/llm/index-passes.js +44 -29
  167. package/dist/llm/memory-infer.js +77 -71
  168. package/dist/llm/metadata-enhance.js +42 -29
  169. package/dist/llm/prompts/extract-session.md +80 -0
  170. package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
  171. package/dist/output/cli-hints-full.md +292 -0
  172. package/dist/output/cli-hints-short.md +66 -0
  173. package/dist/output/cli-hints.js +7 -320
  174. package/dist/output/context.js +60 -8
  175. package/dist/output/renderers.js +300 -257
  176. package/dist/output/shapes/curate.js +56 -0
  177. package/dist/output/shapes/distill.js +10 -0
  178. package/dist/output/shapes/env-list.js +19 -0
  179. package/dist/output/shapes/events.js +11 -0
  180. package/dist/output/shapes/helpers.js +424 -0
  181. package/dist/output/shapes/history.js +7 -0
  182. package/dist/output/shapes/passthrough.js +102 -0
  183. package/dist/output/shapes/proposal-accept.js +7 -0
  184. package/dist/output/shapes/proposal-diff.js +7 -0
  185. package/dist/output/shapes/proposal-list.js +7 -0
  186. package/dist/output/shapes/proposal-producer.js +11 -0
  187. package/dist/output/shapes/proposal-reject.js +7 -0
  188. package/dist/output/shapes/proposal-show.js +7 -0
  189. package/dist/output/shapes/registry-search.js +6 -0
  190. package/dist/output/shapes/registry.js +30 -0
  191. package/dist/output/shapes/search.js +6 -0
  192. package/dist/output/shapes/secret-list.js +19 -0
  193. package/dist/output/shapes/show.js +6 -0
  194. package/dist/output/shapes/vault-list.js +19 -0
  195. package/dist/output/shapes.js +51 -516
  196. package/dist/output/text/add.js +6 -0
  197. package/dist/output/text/clone.js +6 -0
  198. package/dist/output/text/config.js +6 -0
  199. package/dist/output/text/curate.js +6 -0
  200. package/dist/output/text/distill.js +7 -0
  201. package/dist/output/text/enable-disable.js +7 -0
  202. package/dist/output/text/events.js +10 -0
  203. package/dist/output/text/feedback.js +6 -0
  204. package/dist/output/text/helpers.js +1039 -0
  205. package/dist/output/text/history.js +7 -0
  206. package/dist/output/text/import.js +6 -0
  207. package/dist/output/text/index.js +6 -0
  208. package/dist/output/text/info.js +6 -0
  209. package/dist/output/text/init.js +6 -0
  210. package/dist/output/text/list.js +6 -0
  211. package/dist/output/text/proposal-producer.js +8 -0
  212. package/dist/output/text/proposal.js +11 -0
  213. package/dist/output/text/registry-commands.js +11 -0
  214. package/dist/output/text/registry.js +30 -0
  215. package/dist/output/text/remember.js +6 -0
  216. package/dist/output/text/remove.js +6 -0
  217. package/dist/output/text/save.js +6 -0
  218. package/dist/output/text/search.js +6 -0
  219. package/dist/output/text/show.js +6 -0
  220. package/dist/output/text/update.js +6 -0
  221. package/dist/output/text/upgrade.js +6 -0
  222. package/dist/output/text/vault.js +16 -0
  223. package/dist/output/text/wiki.js +15 -0
  224. package/dist/output/text/workflow.js +14 -0
  225. package/dist/output/text.js +44 -1092
  226. package/dist/registry/build-index.js +3 -0
  227. package/dist/registry/create-provider-registry.js +3 -0
  228. package/dist/registry/factory.js +4 -1
  229. package/dist/registry/origin-resolve.js +3 -0
  230. package/dist/registry/providers/index.js +3 -0
  231. package/dist/registry/providers/skills-sh.js +71 -50
  232. package/dist/registry/providers/static-index.js +53 -48
  233. package/dist/registry/providers/types.js +3 -24
  234. package/dist/registry/resolve.js +11 -16
  235. package/dist/registry/types.js +3 -0
  236. package/dist/scripts/migrate-storage.js +17750 -0
  237. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
  238. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  239. package/dist/setup/detect.js +3 -0
  240. package/dist/setup/ripgrep-install.js +3 -0
  241. package/dist/setup/ripgrep-resolve.js +3 -0
  242. package/dist/setup/setup.js +775 -37
  243. package/dist/setup/steps.js +3 -15
  244. package/dist/sources/include.js +3 -0
  245. package/dist/sources/provider-factory.js +5 -12
  246. package/dist/sources/provider.js +3 -20
  247. package/dist/sources/providers/filesystem.js +19 -23
  248. package/dist/sources/providers/git.js +138 -21
  249. package/dist/sources/providers/index.js +3 -0
  250. package/dist/sources/providers/install-types.js +3 -13
  251. package/dist/sources/providers/npm.js +3 -4
  252. package/dist/sources/providers/provider-utils.js +3 -0
  253. package/dist/sources/providers/sync-from-ref.js +3 -11
  254. package/dist/sources/providers/tar-utils.js +3 -0
  255. package/dist/sources/providers/website.js +18 -22
  256. package/dist/sources/resolve.js +3 -0
  257. package/dist/sources/types.js +3 -0
  258. package/dist/sources/website-ingest.js +7 -0
  259. package/dist/tasks/backends/cron.js +203 -0
  260. package/dist/tasks/backends/exec-utils.js +28 -0
  261. package/dist/tasks/backends/index.js +24 -0
  262. package/dist/tasks/backends/launchd-template.xml +19 -0
  263. package/dist/tasks/backends/launchd.js +187 -0
  264. package/dist/tasks/backends/schtasks-template.xml +29 -0
  265. package/dist/tasks/backends/schtasks.js +215 -0
  266. package/dist/tasks/parser.js +211 -0
  267. package/dist/tasks/resolveAkmBin.js +87 -0
  268. package/dist/tasks/runner.js +458 -0
  269. package/dist/tasks/schedule.js +227 -0
  270. package/dist/tasks/schema.js +15 -0
  271. package/dist/tasks/validator.js +62 -0
  272. package/dist/version.js +3 -0
  273. package/dist/wiki/index-template.md +12 -0
  274. package/dist/wiki/ingest-workflow-template.md +54 -0
  275. package/dist/wiki/log-template.md +8 -0
  276. package/dist/wiki/schema-template.md +61 -0
  277. package/dist/wiki/wiki-templates.js +15 -0
  278. package/dist/wiki/wiki.js +13 -61
  279. package/dist/workflows/authoring.js +8 -25
  280. package/dist/workflows/cli.js +3 -0
  281. package/dist/workflows/db.js +140 -10
  282. package/dist/workflows/document-cache.js +3 -10
  283. package/dist/workflows/parser.js +3 -0
  284. package/dist/workflows/renderer.js +11 -3
  285. package/dist/workflows/runs.js +77 -92
  286. package/dist/workflows/schema.js +3 -0
  287. package/dist/workflows/scope-key.js +3 -0
  288. package/dist/workflows/validator.js +4 -8
  289. package/dist/workflows/workflow-template.md +24 -0
  290. package/docs/README.md +10 -2
  291. package/docs/data-and-telemetry.md +225 -0
  292. package/docs/migration/release-notes/0.7.0.md +1 -1
  293. package/docs/migration/release-notes/0.7.5.md +2 -2
  294. package/docs/migration/release-notes/0.8.0.md +48 -0
  295. package/docs/migration/v0.7-to-v0.8.md +1307 -0
  296. package/package.json +30 -12
  297. package/.github/LICENSE +0 -374
  298. package/dist/commands/install-audit.js +0 -381
  299. package/dist/commands/vault.js +0 -328
  300. package/dist/templates/wiki-templates.js +0 -100
@@ -1,3 +1,6 @@
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/.
1
4
  /**
2
5
  * Shared prompt builders for proposal-producing agent commands (#226).
3
6
  *
@@ -23,13 +26,14 @@
23
26
  * during validation. We carry it through if the agent supplies it.
24
27
  */
25
28
  import { TYPE_DIRS } from "../../core/asset-spec";
29
+ import { parseEmbeddedJsonResponse, stripCodeFences, stripThinkBlocks } from "../../core/parse";
26
30
  /**
27
31
  * Per-asset-type frontmatter / authoring hints surfaced in the prompt so
28
32
  * the agent can produce content that passes proposal validation. Kept tiny:
29
33
  * full schema docs live in `docs/` — these are nudges, not contracts.
30
34
  */
31
35
  const TYPE_HINTS = {
32
- lesson: "lesson assets MUST start with frontmatter containing `description` and `when_to_use` keys (both non-empty). Body should be 1–3 short paragraphs of practical guidance.",
36
+ lesson: "lesson assets MUST start with frontmatter containing `description` and `when_to_use` keys (both non-empty). Body: 1–3 short paragraphs of practical guidance. A lesson is NOT a restatement of the source asset — it answers: When should I reach for this? What goes wrong without it? What did real use reveal that the asset itself doesn't say?",
33
37
  skill: "skill assets are stored as `skills/<name>/SKILL.md`. Frontmatter typically includes `name`, `description`, and `when_to_use`.",
34
38
  command: "command assets are markdown with optional frontmatter (`name`, `description`). The body is the prompt template the user invokes.",
35
39
  agent: "agent assets are markdown with frontmatter describing the agent role (`name`, `description`, optional `tools`, `model`).",
@@ -37,7 +41,8 @@ const TYPE_HINTS = {
37
41
  memory: "memory assets are short factual notes the user wants persisted across sessions. Frontmatter usually includes `description`.",
38
42
  workflow: "workflow assets are markdown describing a multi-step process. Include `# <Title>` and ordered `## Step N` sections.",
39
43
  script: "script assets are executable text files. Include a shebang and minimal usage comment.",
40
- vault: "vault assets store environment variables (KEY=VALUE pairs). Comments use `#`. Never echo secret values back to the user.",
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
+ vault: "vault assets are DEPRECATED (use env). They store environment variables (KEY=VALUE pairs); comments use `#`. Never echo secret values back to the user.",
41
46
  wiki: "wiki assets are markdown reference pages with `# Title` and structured headings.",
42
47
  };
43
48
  function hintForType(type) {
@@ -54,9 +59,15 @@ function knownTypeList() {
54
59
  */
55
60
  const RESPONSE_CONTRACT_JSON = [
56
61
  "Respond ONLY with a single JSON object. No prose before or after.",
57
- 'Shape: {"ref": "<type>:<name>", "content": "<full file contents>", "frontmatter": {...}}',
62
+ 'Shape: {"ref": "<type>:<name>", "content": "<full file contents>", "frontmatter": {...}, "confidence": <number 0..1>}',
58
63
  "`content` is the full file body that will be written if accepted.",
59
64
  "`frontmatter` is optional — include it if `content` starts with `---` so reviewers can sanity-check the keys.",
65
+ "`confidence` is REQUIRED. Self-rate this proposal on [0, 1] by how certain you are it materially improves the source asset. Calibrate honestly:",
66
+ " • 0.90+ — high certainty: fixes a real defect or adds load-bearing missing content; a reviewer would clearly accept.",
67
+ " • 0.70–0.89 — clear improvement, but a reviewer might reasonably prefer different framing or scope.",
68
+ " • 0.50–0.69 — marginal / judgment call; might help, might not be worth the churn.",
69
+ " • Below 0.50 — you are not confident this improves on the source. Prefer returning the source body roughly unchanged with a low score over inventing changes.",
70
+ "Auto-accept gates on confidence ≥ 0.80 by default. Overclaiming ships low-quality changes; underclaiming leaves good ones stuck in queue. Be honest.",
60
71
  ].join("\n");
61
72
  /**
62
73
  * Response contract used when a draft file path is available. Instructs the
@@ -68,19 +79,55 @@ function fileWriteContract(draftFilePath) {
68
79
  `Write the complete improved asset content to: ${draftFilePath}`,
69
80
  "Use your file-editing tools to create or overwrite that file.",
70
81
  "Do NOT output JSON to stdout. Do NOT print the file contents. Just write the file.",
71
- "When you are done writing the file, output a single line: DRAFT_WRITTEN",
82
+ "When done, output a single line on stdout: DRAFT_WRITTEN confidence=<0.0-1.0>",
83
+ "`confidence` is REQUIRED and must be your honest self-rated [0, 1] score for this proposal:",
84
+ " • 0.90+ — fixes a real defect or adds load-bearing missing content; reviewer would clearly accept.",
85
+ " • 0.70–0.89 — clear improvement, but a reviewer might prefer different framing.",
86
+ " • 0.50–0.69 — marginal / judgment call.",
87
+ " • Below 0.50 — not confident; prefer not writing changes at all.",
88
+ "Auto-accept gates on confidence ≥ 0.80. Overclaim → low-quality changes land; underclaim → good changes stuck in queue.",
72
89
  ].join("\n");
73
90
  }
91
+ /**
92
+ * Extract a confidence score from a `DRAFT_WRITTEN confidence=<n>` line emitted
93
+ * by an agent following {@link fileWriteContract}. Tolerates trailing prose,
94
+ * surrounding log lines, and missing/invalid confidence (returns `undefined`
95
+ * so callers can keep the proposal but skip auto-accept).
96
+ *
97
+ * Matched forms (case-insensitive, anywhere in stdout):
98
+ * - `DRAFT_WRITTEN confidence=0.85`
99
+ * - `DRAFT_WRITTEN confidence=0.85 ...trailing`
100
+ * - `DRAFT_WRITTEN` (no confidence — returns `undefined`)
101
+ */
102
+ export function extractDraftConfidence(stdout) {
103
+ if (!stdout)
104
+ return undefined;
105
+ const match = stdout.match(/\bDRAFT_WRITTEN\b[^\S\r\n]+confidence=([0-9]*\.?[0-9]+)/i);
106
+ if (!match)
107
+ return undefined;
108
+ const value = Number.parseFloat(match[1] ?? "");
109
+ if (!Number.isFinite(value) || value < 0 || value > 1)
110
+ return undefined;
111
+ return value;
112
+ }
74
113
  /**
75
114
  * Build the prompt for `akm reflect [ref]`. Asks the agent to review an
76
115
  * existing asset (plus any negative feedback / lint findings) and propose
77
- * an improved version. Returns a single string the agent runtime will
78
- * forward it as the trailing positional arg.
116
+ * an improved version. Returns a {@link ReflectPromptResult} containing the
117
+ * prompt string and an optional character ceiling for max-tokens enforcement.
79
118
  */
80
119
  export function buildReflectPrompt(input) {
81
120
  const sections = [];
82
121
  if (input.ref && input.type && input.name) {
83
- sections.push(`You are reviewing an akm stash asset (${input.type}) called "${input.name}" and proposing an improved version.`);
122
+ // Change 2 type-conditioned goal framing
123
+ const isLesson = input.type === "lesson";
124
+ const isSkill = input.type === "skill";
125
+ const goalSentence = isLesson
126
+ ? `Your task is to distill what usage signals reveal about this ${input.type} asset — when to reach for it, what goes wrong without it, and what real use has revealed that the asset itself does not say. Do not reproduce the source content; your proposal must add information the source does not contain.`
127
+ : isSkill
128
+ ? "Your task is to review this skill asset, identify what the feedback and related distilled lessons show is broken, missing, unclear, or durable enough to promote into long-term documentation, and produce a single improved proposal. If the strongest evidence points to companion reference material rather than the main SKILL.md, you may instead propose a skill-adjacent knowledge doc such as `knowledge:skills/<skill>/references/<topic>`."
129
+ : `Your task is to review this ${input.type} asset, identify what the feedback signals as broken, missing, or unclear, and produce an improved version. Do not reproduce the source content unchanged; your proposal must correct or add something the source lacks.`;
130
+ sections.push(goalSentence);
84
131
  sections.push(`Target ref: ${input.ref}`);
85
132
  sections.push(`Asset-type guidance: ${hintForType(input.type)}`);
86
133
  }
@@ -92,10 +139,35 @@ export function buildReflectPrompt(input) {
92
139
  if (input.task?.trim()) {
93
140
  sections.push(`Task / focus: ${input.task.trim()}`);
94
141
  }
142
+ // Change 3 & 4 — feedback moved before asset content; missing else branch added
143
+ if (input.feedback && input.feedback.length > 0) {
144
+ sections.push("Recent feedback / signals:");
145
+ for (const line of input.feedback)
146
+ sections.push(`- ${line}`);
147
+ }
148
+ else if (!input.ref) {
149
+ sections.push("Recent feedback / signals:");
150
+ sections.push("- (no feedback events recorded)");
151
+ }
152
+ else if (input.type === "skill" && input.relatedLessons && input.relatedLessons.length > 0) {
153
+ sections.push("No direct feedback events were recorded. Limit substantive changes to what is justified by the related distilled lessons below; do not speculate beyond that evidence.");
154
+ }
155
+ else {
156
+ // ref is set but no feedback — explicitly constrain scope to schema compliance
157
+ 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.");
158
+ }
95
159
  if (input.assetContent?.trim()) {
96
- sections.push("Current asset content (verbatim):");
160
+ // Cap at 12 000 chars to stay well under OS ARG_MAX when the prompt is
161
+ // passed as a CLI argument to opencode/claude. Large assets (wiki snapshots,
162
+ // long runbooks) would otherwise trigger E2BIG on posix_spawn.
163
+ const REFLECT_CONTENT_CAP = 12_000;
164
+ const body = input.assetContent.trimEnd();
165
+ const truncated = body.length > REFLECT_CONTENT_CAP;
166
+ sections.push(truncated
167
+ ? `Current asset content (first ${REFLECT_CONTENT_CAP} chars — full asset is ${body.length} chars):`
168
+ : "Current asset content (verbatim):");
97
169
  sections.push("```");
98
- sections.push(input.assetContent.trimEnd());
170
+ sections.push(truncated ? `${body.slice(0, REFLECT_CONTENT_CAP)}\n... [truncated — focus on the visible portion]` : body);
99
171
  sections.push("```");
100
172
  }
101
173
  else if (input.ref) {
@@ -104,23 +176,103 @@ export function buildReflectPrompt(input) {
104
176
  else {
105
177
  sections.push("(No existing asset content was supplied.)");
106
178
  }
107
- if (input.feedback && input.feedback.length > 0) {
108
- sections.push("Recent feedback / signals:");
109
- for (const line of input.feedback)
110
- sections.push(`- ${line}`);
111
- }
112
- else if (!input.ref) {
113
- sections.push("Recent feedback / signals:");
114
- sections.push("- (no feedback events recorded)");
115
- }
116
179
  if (input.schemaHints && input.schemaHints.length > 0) {
117
180
  sections.push("Schema / lint hints to address:");
118
181
  for (const line of input.schemaHints)
119
182
  sections.push(`- ${line}`);
120
183
  }
121
- sections.push("Produce a single proposal that addresses the feedback and respects the asset-type contract.");
184
+ if (input.relatedLessons && input.relatedLessons.length > 0) {
185
+ sections.push("Related distilled lessons to evaluate for consolidation:");
186
+ for (const lesson of input.relatedLessons) {
187
+ sections.push(`Lesson ref: ${lesson.ref}`);
188
+ sections.push("```");
189
+ sections.push(lesson.content.trimEnd());
190
+ sections.push("```");
191
+ }
192
+ sections.push("Evaluate whether these lessons contain strong evidence of factual, repeatable guidance that should be promoted into long-term skill documentation.");
193
+ sections.push("Promote only guidance that is durable, generally applicable, and supported by repeated evidence. Do not copy anecdotal details, one-off incidents, or duplicate wording verbatim.");
194
+ sections.push("If the guidance belongs in the main skill instructions, update the skill proposal. If it belongs in a companion reference document, return a `knowledge:skills/<skill>/references/<topic>` proposal instead.");
195
+ }
196
+ if (input.rejectedProposals && input.rejectedProposals.length > 0) {
197
+ const lines = ["## Previously Rejected Proposals"];
198
+ lines.push("The following proposals for this ref were already reviewed and rejected. " +
199
+ "Do NOT reproduce the same content or the same structural shape. " +
200
+ "Your new proposal must meaningfully differ from each of these in its approach, framing, or evidence used.");
201
+ for (const rp of input.rejectedProposals) {
202
+ lines.push(`\nRef: ${rp.ref}`);
203
+ lines.push(`Rejection reason: ${rp.reason}`);
204
+ if (rp.contentPreview) {
205
+ lines.push("Rejected content preview:");
206
+ lines.push("```");
207
+ lines.push(rp.contentPreview);
208
+ lines.push("```");
209
+ }
210
+ }
211
+ sections.push(lines.join("\n"));
212
+ }
213
+ if (input.avoidPatterns && input.avoidPatterns.length > 0) {
214
+ sections.push(`## Avoid These Patterns\nPrevious assets in this run produced these errors — do not repeat them:\n${input.avoidPatterns.map((e) => `- ${e}`).join("\n")}`);
215
+ }
216
+ // R-1 / #372: Self-Refine (arXiv:2303.17651) — inject prior draft as critique target.
217
+ // On refinement iterations (iter > 0), the agent is shown its previous proposal
218
+ // and asked to self-critique and improve it rather than starting from scratch.
219
+ if (input.priorDraft?.trim()) {
220
+ sections.push("## Self-Refine: Critique and Improve\n" +
221
+ "The following is your previous draft proposal. " +
222
+ "Identify specific weaknesses: missing evidence, vague wording, incomplete frontmatter, " +
223
+ "or claims that duplicate existing content without adding new signal. " +
224
+ "Then produce an improved version that addresses those weaknesses. " +
225
+ "The revised proposal must be meaningfully better than the draft below — " +
226
+ "do not return the same content unchanged.\n\n" +
227
+ "Previous draft:\n```\n" +
228
+ input.priorDraft.trimEnd() +
229
+ "\n```");
230
+ }
231
+ sections.push("Produce a single proposal that addresses the feedback and respects the asset-type contract. If the proposal's frontmatter is missing `when_to_use`, you MUST generate one — a one-line trigger sentence describing exactly when a user should reach for this asset.");
232
+ // Content-preservation safety rails (#reflect-pipeline-fixes).
233
+ // These rules counter the observed failure modes where reflect rewrites
234
+ // asset content into shorter prose, drops concrete structure, or strips
235
+ // load-bearing frontmatter. Loud and explicit so small models follow.
236
+ //
237
+ // maxOutputChars is hoisted so the return value can include it for callers
238
+ // on the LLM path that want to set a hard max_tokens cap on the request.
239
+ let maxOutputChars;
240
+ if (input.ref && input.assetContent?.trim()) {
241
+ // Strip frontmatter to get source body length — mirrors checkReflectSize which
242
+ // compares body-only lengths. Inline regex avoids importing parseFrontmatter.
243
+ const rawContent = input.assetContent.trimEnd();
244
+ const fmBodyMatch = rawContent.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
245
+ const sourceBodyLen = (fmBodyMatch ? fmBodyMatch[1] : rawContent).trim().length;
246
+ // Compute concrete char bounds matching checkReflectSize constants:
247
+ // REFLECT_SIZE_GUARD_MIN_BYTES=200, REFLECT_SHRINK_RATIO_MIN=0.5,
248
+ // REFLECT_ABSOLUTE_FLOOR_BYTES=150, REFLECT_EXPAND_RATIO_MAX=2.5,
249
+ // REFLECT_ABSOLUTE_CEILING_BYTES=2500, REFLECT_ABSOLUTE_MAX_BYTES=25000.
250
+ // Embed concrete counts only when the gate will actually fire (source >= 200 chars).
251
+ const showCharBounds = sourceBodyLen >= 200;
252
+ const minChars = Math.max(Math.round(0.5 * sourceBodyLen), 150);
253
+ const maxChars = Math.min(Math.max(Math.round(2.5 * sourceBodyLen), 2500), 25000);
254
+ if (showCharBounds)
255
+ maxOutputChars = maxChars;
256
+ sections.push([
257
+ "## Content preservation rules (MUST follow)",
258
+ "1. PRESERVE ALL concrete content: code blocks, fenced snippets, CLI commands, numbered/bulleted checklists, tables, YAML/JSON examples, file paths, configuration keys, environment variable names, and CSS/HTML selectors. These are load-bearing — do NOT replace them with prose summaries.",
259
+ "2. PRESERVE the source asset's frontmatter. The post-processor reassembles the final asset from the original frontmatter plus your body. Do NOT emit `---` frontmatter delimiters at the top of `content` — start `content` with the markdown body (e.g. `# Heading` or the first paragraph). If you include frontmatter anyway, identity fields (`name`, `ref`, `id`, `slug`, `type`) will be reset to the original values.",
260
+ showCharBounds
261
+ ? `3. DO NOT shrink the asset. Your body must be at least ${minChars} characters (source body is ${sourceBodyLen} chars; floor is 50%). If you genuinely need to remove a major section, explain why in a comment line at the top of the body (e.g. \`<!-- removed obsolete section X because ... -->\`).`
262
+ : "3. DO NOT shrink the asset dramatically. The improved body must be at least 50% of the source body length. If you genuinely need to remove a major section, explain why in a comment line at the top of the body (e.g. `<!-- removed obsolete section X because ... -->`).",
263
+ showCharBounds
264
+ ? `4. DO NOT pad the asset with speculative material. Your body must be at most ${maxChars} characters (source body is ${sourceBodyLen} chars; ceiling is 250%). Do not add invented sections, hypothetical examples, or padding prose.`
265
+ : "4. DO NOT pad the asset with speculative material. The improved body must be at most 250% of the source body length unless the feedback explicitly requests added sections.",
266
+ "5. Improve clarity of surrounding prose, fix structural issues, add missing required frontmatter fields. Do NOT rewrite a runbook into an essay.",
267
+ ].join("\n"));
268
+ }
269
+ if (!input.draftFilePath && input.ref) {
270
+ // Reinforce that the `ref` field is mandatory and must exactly match the target.
271
+ // Small models frequently omit `ref` from the response JSON, causing parse errors.
272
+ sections.push(`IMPORTANT: The JSON "ref" field is REQUIRED. It MUST be exactly: "${input.ref}"`);
273
+ }
122
274
  sections.push(input.draftFilePath ? fileWriteContract(input.draftFilePath) : RESPONSE_CONTRACT_JSON);
123
- return sections.join("\n\n");
275
+ return { prompt: sections.join("\n\n"), ...(maxOutputChars !== undefined ? { maxOutputChars } : {}) };
124
276
  }
125
277
  /**
126
278
  * Build the prompt for `akm propose <type> <name> --task ...`. Asks the
@@ -141,6 +293,33 @@ export function buildProposePrompt(input) {
141
293
  sections.push(input.draftFilePath ? fileWriteContract(input.draftFilePath) : RESPONSE_CONTRACT_JSON);
142
294
  return sections.join("\n\n");
143
295
  }
296
+ /**
297
+ * Build the prompt for the schema repair pass in `akm improve`. Asks the
298
+ * agent to add the minimal required frontmatter to an asset that failed
299
+ * validation — without rewriting the body.
300
+ */
301
+ export function buildSchemaRepairPrompt(input) {
302
+ const sections = [];
303
+ sections.push(`This ${input.type} asset failed schema validation with the error: "${input.reason}". ` +
304
+ `Your task is to fix the schema issue by adding or correcting the missing/invalid field(s) ` +
305
+ `while preserving all existing content.`);
306
+ sections.push(`Target ref: ${input.ref}`);
307
+ sections.push(`Schema requirements for ${input.type} assets: ${hintForType(input.type)}`);
308
+ const CONTENT_CAP = 3000;
309
+ const body = input.assetContent.trimEnd();
310
+ const truncated = body.length > CONTENT_CAP;
311
+ sections.push("Current asset content (first 3000 chars — sufficient to generate missing frontmatter):");
312
+ sections.push("```");
313
+ sections.push(truncated ? `${body.slice(0, CONTENT_CAP)}\n... [truncated]` : body);
314
+ sections.push("```");
315
+ sections.push("Produce the minimal fix: add ONLY the missing required frontmatter field(s). " +
316
+ "Do not rewrite the body unless it is empty. " +
317
+ "If `description` is missing, generate a concise one-sentence description from the content. " +
318
+ "If `when_to_use` is missing, generate a one-line trigger sentence. " +
319
+ "Preserve all existing frontmatter keys and the full body verbatim.");
320
+ sections.push(input.draftFilePath ? fileWriteContract(input.draftFilePath) : RESPONSE_CONTRACT_JSON);
321
+ return sections.join("\n\n");
322
+ }
144
323
  /**
145
324
  * Parse agent stdout into a proposal payload. The agent contract requires a
146
325
  * single JSON object; anything else is reported as a parse error so callers
@@ -151,7 +330,8 @@ export function buildProposePrompt(input) {
151
330
  * 2. Prose preamble / postamble around the JSON object (handled by `extractEmbeddedJson`).
152
331
  */
153
332
  export function parseAgentProposalPayload(stdout) {
154
- const trimmed = stripJsonFences(stdout).trim();
333
+ // Strip <think> blocks and fences, then attempt full parse with embedded fallback.
334
+ const trimmed = stripCodeFences(stripThinkBlocks(stdout)).trim();
155
335
  if (!trimmed)
156
336
  throw new Error("agent produced empty output");
157
337
  let parsed;
@@ -162,7 +342,7 @@ export function parseAgentProposalPayload(stdout) {
162
342
  // Agent output contains prose before/after the JSON object (e.g. a local
163
343
  // LLM that narrates before responding). Try extracting the first balanced
164
344
  // top-level `{…}` from the text rather than failing immediately.
165
- const embedded = extractEmbeddedJson(trimmed);
345
+ const embedded = parseEmbeddedJsonResponse(trimmed);
166
346
  if (!embedded)
167
347
  throw directErr;
168
348
  parsed = embedded;
@@ -180,68 +360,22 @@ export function parseAgentProposalPayload(stdout) {
180
360
  if (parsed.frontmatter && typeof parsed.frontmatter === "object" && !Array.isArray(parsed.frontmatter)) {
181
361
  out.frontmatter = parsed.frontmatter;
182
362
  }
183
- return out;
184
- }
185
- /**
186
- * Extract the first balanced top-level `{…}` object from `text`. Used as a
187
- * fallback when direct `JSON.parse` fails due to surrounding prose. Kept
188
- * local to `agent/` (mirrors `parseEmbeddedJsonResponse` in `src/llm/client.ts`
189
- * without importing across the one-way boundary — v1 spec §9.7).
190
- */
191
- function extractEmbeddedJson(text) {
192
- for (let start = 0; start < text.length; start++) {
193
- if (text[start] !== "{")
194
- continue;
195
- let depth = 0;
196
- let inString = false;
197
- let escaped = false;
198
- for (let i = start; i < text.length; i++) {
199
- const ch = text[i];
200
- if (inString) {
201
- if (escaped) {
202
- escaped = false;
203
- }
204
- else if (ch === "\\") {
205
- escaped = true;
206
- }
207
- else if (ch === '"') {
208
- inString = false;
209
- }
210
- continue;
211
- }
212
- if (ch === '"') {
213
- inString = true;
214
- continue;
215
- }
216
- if (ch === "{")
217
- depth++;
218
- if (ch === "}") {
219
- depth--;
220
- if (depth === 0) {
221
- try {
222
- return JSON.parse(text.slice(start, i + 1));
223
- }
224
- catch {
225
- break;
226
- }
227
- }
228
- }
229
- }
363
+ // Phase 6A: extract optional `confidence` (number in [0, 1]). Clamp gently
364
+ // rather than reject — a model that returns 1.0 or 0 with extra precision
365
+ // (e.g. 1.0000001) should still surface a usable score. Anything that isn't
366
+ // a finite number is dropped so downstream `createProposal` can rely on the
367
+ // shape invariant.
368
+ if (typeof parsed.confidence === "number" && Number.isFinite(parsed.confidence)) {
369
+ const clamped = Math.max(0, Math.min(1, parsed.confidence));
370
+ out.confidence = clamped;
230
371
  }
231
- return undefined;
372
+ return out;
232
373
  }
233
374
  /**
234
375
  * Strip `\`\`\`json … \`\`\`` fences and `<think>…</think>` reasoning blocks
235
- * from agent output. Mirrors `client.ts` but kept local to `agent/` per v1
236
- * spec §9.7 (one-way boundary `agent/` does not import from `llm/`).
376
+ * from agent output. Thin wrapper around `core/parse` helpers, kept exported
377
+ * for backward compatibility (re-exported from `integrations/agent/index.ts`).
237
378
  */
238
379
  export function stripJsonFences(text) {
239
- const stripped = text
240
- .trim()
241
- .replace(/<think>[\s\S]*?<\/think>/gi, "")
242
- .trim();
243
- const fenced = stripped.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/);
244
- if (fenced)
245
- return fenced[1] ?? stripped;
246
- return stripped;
380
+ return stripCodeFences(stripThinkBlocks(text));
247
381
  }
@@ -0,0 +1,151 @@
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
+ import { ConfigError } from "../../core/errors";
5
+ function resolveEffectiveMode(entry, profileName, config) {
6
+ if (entry.mode)
7
+ return entry.mode;
8
+ // Infer from profile pool when profile is specified
9
+ if (profileName) {
10
+ if (config.profiles?.llm?.[profileName])
11
+ return "llm";
12
+ const agentProfile = config.profiles?.agent?.[profileName];
13
+ if (agentProfile) {
14
+ return agentProfile.platform === "opencode-sdk" ? "sdk" : "agent";
15
+ }
16
+ }
17
+ // Fall back to defaults
18
+ if (config.defaults?.llm)
19
+ return "llm";
20
+ if (config.defaults?.agent)
21
+ return "agent";
22
+ return "llm";
23
+ }
24
+ function resolveProfileName(entry, mode, config) {
25
+ if (entry.profile)
26
+ return entry.profile;
27
+ if (mode === "llm") {
28
+ const defaultName = config.defaults?.llm;
29
+ if (defaultName)
30
+ return defaultName;
31
+ throw new ConfigError(`No LLM profile configured. Set defaults.llm in config or specify profile in the process entry.`, "LLM_NOT_CONFIGURED", "Run `akm setup` or define a profile under `profiles.llm` and set `defaults.llm`.");
32
+ }
33
+ const defaultName = config.defaults?.agent;
34
+ if (defaultName)
35
+ return defaultName;
36
+ throw new ConfigError(`No agent profile configured. Set defaults.agent in config or specify profile in the process entry.`, "INVALID_CONFIG_FILE", "Run `akm setup` to configure an agent profile, or add one under `profiles.agent`.");
37
+ }
38
+ function buildLlmRunnerSpec(profileName, timeoutMs, config) {
39
+ const profile = config.profiles?.llm?.[profileName];
40
+ if (!profile) {
41
+ throw new ConfigError(`LLM profile "${profileName}" not found in profiles.llm.`, "LLM_NOT_CONFIGURED", `Available profiles: ${Object.keys(config.profiles?.llm ?? {}).join(", ") || "none"}. Run \`akm setup\` to configure.`);
42
+ }
43
+ return {
44
+ kind: "llm",
45
+ connection: profile,
46
+ ...(typeof timeoutMs === "number" ? { timeoutMs } : {}),
47
+ };
48
+ }
49
+ function buildAgentRunnerSpec(kind, profileName, timeoutMs, config) {
50
+ const profileConfig = config.profiles?.agent?.[profileName];
51
+ if (!profileConfig) {
52
+ throw new ConfigError(`Agent profile "${profileName}" not found in profiles.agent.`, "INVALID_CONFIG_FILE", `Available profiles: ${Object.keys(config.profiles?.agent ?? {}).join(", ") || "none"}. Run \`akm setup\` to configure.`);
53
+ }
54
+ // Validate mode/platform consistency
55
+ if (kind === "sdk" && profileConfig.platform !== "opencode-sdk") {
56
+ throw new ConfigError(`Mode "sdk" requires platform "opencode-sdk", but profiles.agent["${profileName}"].platform is "${profileConfig.platform}".`, "INVALID_CONFIG_FILE");
57
+ }
58
+ if (kind === "agent" && profileConfig.platform === "opencode-sdk") {
59
+ throw new ConfigError(`Mode "agent" requires platform "opencode" or "claude", but profiles.agent["${profileName}"].platform is "opencode-sdk".`, "INVALID_CONFIG_FILE");
60
+ }
61
+ const agentProfile = {
62
+ name: profileName,
63
+ bin: profileConfig.bin ?? profileName,
64
+ args: profileConfig.args ?? [],
65
+ stdio: "captured",
66
+ envPassthrough: [],
67
+ parseOutput: "text",
68
+ ...(profileConfig.model ? { model: profileConfig.model } : {}),
69
+ ...(profileConfig.workspace ? { workspace: profileConfig.workspace } : {}),
70
+ };
71
+ return {
72
+ kind,
73
+ profile: agentProfile,
74
+ ...(typeof timeoutMs === "number" ? { timeoutMs } : {}),
75
+ };
76
+ }
77
+ /**
78
+ * Resolve the runner used for "validation" passes on the `improve` section
79
+ * (Advantage D3 / Phase 4B — third model tier).
80
+ *
81
+ * Look-up order:
82
+ * 1. `profiles.improve.default.processes.validation` (preferred — lets users
83
+ * wire a lower-cost classifier model for staleness detection, confidence
84
+ * scoring, and lesson classification).
85
+ * 2. `defaults.llm` as a final fallback so callers always get a usable
86
+ * runner when any LLM is configured.
87
+ *
88
+ * Returns `null` when neither is configured (callers may then skip the
89
+ * validation pass rather than throwing).
90
+ */
91
+ export function resolveValidationRunner(config) {
92
+ const validation = config.profiles?.improve?.default?.processes?.validation;
93
+ if (validation && validation.enabled !== false && (validation.profile || validation.mode)) {
94
+ try {
95
+ const spec = resolveImproveProcessRunnerFromProfile(validation, config);
96
+ if (spec)
97
+ return spec;
98
+ }
99
+ catch {
100
+ // Fall through to defaults.llm below.
101
+ }
102
+ }
103
+ const defaultLlm = config.defaults?.llm;
104
+ if (defaultLlm) {
105
+ try {
106
+ return buildLlmRunnerSpec(defaultLlm, undefined, config);
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ }
112
+ return null;
113
+ }
114
+ export function resolveRunner(mode, profileName, config) {
115
+ if (mode === "llm")
116
+ return buildLlmRunnerSpec(profileName, undefined, config);
117
+ return buildAgentRunnerSpec(mode, profileName, undefined, config);
118
+ }
119
+ /**
120
+ * Resolve a RunnerSpec from an improve-profile process entry. Returns `null`
121
+ * when the entry is absent or provides no overrides — callers should fall
122
+ * back to the default per-process runner resolution path.
123
+ */
124
+ export function resolveImproveProcessRunnerFromProfile(processConfig, config) {
125
+ if (!processConfig)
126
+ return null;
127
+ const { mode: explicitMode, profile, timeoutMs } = processConfig;
128
+ if (!explicitMode && !profile)
129
+ return null;
130
+ const mode = explicitMode ?? resolveEffectiveMode(processConfig, profile, config);
131
+ const profileName = profile ?? resolveProfileName(processConfig, mode, config);
132
+ if (mode === "llm") {
133
+ return buildLlmRunnerSpec(profileName, timeoutMs, config);
134
+ }
135
+ if (mode === "agent" || mode === "sdk") {
136
+ return buildAgentRunnerSpec(mode, profileName, timeoutMs, config);
137
+ }
138
+ return null;
139
+ }
140
+ /**
141
+ * Convenience accessor for callers that previously read
142
+ * `getProcessOptions("index", "staleness_detection", config).thresholdDays`.
143
+ * After the 0.8.0 migration, those values live on first-class config keys —
144
+ * see `config.index?.stalenessDetection?.thresholdDays` etc.
145
+ */
146
+ export function getStalenessDetectionThresholdDays(config) {
147
+ return config.index?.stalenessDetection?.thresholdDays;
148
+ }
149
+ // Re-export `isProcessEnabled` from feature-gate.ts so callers that previously
150
+ // imported it from runner.ts continue to work.
151
+ export { isProcessEnabled } from "../../llm/feature-gate";