akm-cli 0.9.31 → 0.10.261001-beta

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 (270) hide show
  1. package/CHANGELOG.md +409 -0
  2. package/STABILITY.md +99 -57
  3. package/dist/api-node.mjs +20 -0
  4. package/dist/api.d.ts +23 -0
  5. package/dist/api.js +45 -0
  6. package/dist/assets/hints/cli-hints-full.md +15 -29
  7. package/dist/assets/hints/cli-hints-short.md +2 -2
  8. package/dist/assets/improve-strategies/default.json +3 -3
  9. package/dist/assets/prompts/consolidate-pair.md +9 -7
  10. package/dist/assets/stash-skeleton/README.md +1 -1
  11. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
  12. package/dist/cli/shared.js +31 -48
  13. package/dist/cli/unknown-flags.js +6 -43
  14. package/dist/cli.js +8 -34
  15. package/dist/commands/agent/contribute-cli.js +1 -1
  16. package/dist/commands/completions.js +1 -2
  17. package/dist/commands/config-cli.js +5 -28
  18. package/dist/commands/env/env-cli.js +5 -5
  19. package/dist/commands/env/secret-cli.js +2 -2
  20. package/dist/commands/feedback-cli.js +75 -49
  21. package/dist/commands/health/checks.js +41 -16
  22. package/dist/commands/health/plugin-staleness.js +52 -22
  23. package/dist/commands/improve/autonomy-gate.js +5 -3
  24. package/dist/commands/improve/consolidate/chunking.js +3 -10
  25. package/dist/commands/improve/consolidate/continuity-check.js +4 -4
  26. package/dist/commands/improve/consolidate/pair-pass.js +29 -10
  27. package/dist/commands/improve/consolidate.js +10 -65
  28. package/dist/commands/improve/distill/lesson-update.js +91 -0
  29. package/dist/commands/improve/distill-guards.js +1 -43
  30. package/dist/commands/improve/distill.js +148 -21
  31. package/dist/commands/improve/eligibility.js +14 -1
  32. package/dist/commands/improve/extract-cli.js +1 -1
  33. package/dist/commands/improve/extract-prompt.js +17 -7
  34. package/dist/commands/improve/extract.js +5 -7
  35. package/dist/commands/improve/improve-cli.js +92 -106
  36. package/dist/commands/improve/improve-strategies.js +8 -12
  37. package/dist/commands/improve/improve.js +134 -52
  38. package/dist/commands/improve/loop-stages.js +18 -5
  39. package/dist/commands/improve/planner.js +0 -1
  40. package/dist/commands/improve/preparation.js +40 -99
  41. package/dist/commands/improve/proactive-maintenance.js +2 -2
  42. package/dist/commands/improve/reflect-noise.js +3 -55
  43. package/dist/commands/improve/reflect.js +29 -34
  44. package/dist/commands/improve/retrieval-queries.js +37 -0
  45. package/dist/commands/improve/retrieval-scope.js +2 -2
  46. package/dist/commands/improve/salience.js +2 -9
  47. package/dist/commands/improve/session-asset.js +4 -1
  48. package/dist/commands/improve/stage.js +23 -12
  49. package/dist/commands/lint/index.js +5 -20
  50. package/dist/commands/observability-cli.js +3 -9
  51. package/dist/commands/proposal/drain.js +43 -6
  52. package/dist/commands/proposal/proposal-cli.js +11 -31
  53. package/dist/commands/proposal/repository.js +20 -14
  54. package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -3
  55. package/dist/commands/read/knowledge.js +3 -3
  56. package/dist/commands/read/remember-cli.js +0 -13
  57. package/dist/commands/read/search-cli.js +29 -97
  58. package/dist/commands/read/search.js +0 -11
  59. package/dist/commands/read/show.js +0 -19
  60. package/dist/commands/sources/plugin-upgrade.js +164 -101
  61. package/dist/commands/sources/sources-cli.js +1 -15
  62. package/dist/commands/sources/stash-cli.js +6 -14
  63. package/dist/commands/tasks/tasks-cli.js +4 -115
  64. package/dist/commands/workflow/plan.js +11 -33
  65. package/dist/commands/workflow-cli.js +2 -7
  66. package/dist/core/adapter/adapter-ids.js +1 -2
  67. package/dist/core/adapter/adapters/agent-skills-adapter.js +0 -4
  68. package/dist/core/adapter/adapters/akm-adapter.js +9 -48
  69. package/dist/core/adapter/adapters/akm-lint.js +10 -26
  70. package/dist/core/adapter/adapters/akm-metadata.js +2 -72
  71. package/dist/core/adapter/adapters/akm-task-adapter.js +0 -5
  72. package/dist/core/adapter/adapters/akm-workflow-adapter.js +9 -28
  73. package/dist/core/adapter/adapters/dotenv-adapter.js +1 -20
  74. package/dist/core/adapter/adapters/generic-files-adapter.js +0 -10
  75. package/dist/core/adapter/adapters/llm-wiki-adapter.js +0 -4
  76. package/dist/core/adapter/adapters/tool-dir-shared.js +0 -24
  77. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -4
  78. package/dist/core/adapter/recognize-match.js +3 -4
  79. package/dist/core/common.js +18 -2
  80. package/dist/core/config/config-schema.js +12 -15
  81. package/dist/core/config/config-types.js +6 -15
  82. package/dist/core/config/config-walker.js +6 -23
  83. package/dist/core/config/config.js +17 -80
  84. package/dist/core/config/engine-semantics.js +7 -3
  85. package/dist/core/config/schema/engines.js +7 -7
  86. package/dist/core/config/schema/improve-processes.js +10 -63
  87. package/dist/core/config/schema/improve.js +2 -21
  88. package/dist/core/config/schema/index-config.js +6 -39
  89. package/dist/core/env-secret-ref.js +2 -45
  90. package/dist/core/errors.js +1 -2
  91. package/dist/core/extra-params.js +2 -86
  92. package/dist/core/file-lock.js +2 -6
  93. package/dist/core/improve-result.js +13 -1
  94. package/dist/core/mutation-target.js +2 -2
  95. package/dist/core/paths.js +4 -0
  96. package/dist/core/recognition-util.js +4 -5
  97. package/dist/core/run-lock.js +1 -1
  98. package/dist/core/subprocess.js +49 -0
  99. package/dist/core/warn.js +21 -4
  100. package/dist/core/write-source.js +3 -3
  101. package/dist/execution/target-ref.js +1 -2
  102. package/dist/indexer/indexer.js +15 -6
  103. package/dist/indexer/passes/memory-inference.js +15 -6
  104. package/dist/indexer/search/db-search.js +49 -36
  105. package/dist/indexer/search/fts-query.js +0 -19
  106. package/dist/indexer/search/ranking.js +29 -1
  107. package/dist/indexer/usage/usage-events.js +21 -16
  108. package/dist/integrations/agent/engine-resolution.js +5 -2
  109. package/dist/integrations/agent/index.js +1 -1
  110. package/dist/integrations/agent/profiles.js +41 -98
  111. package/dist/integrations/agent/request-lowering.js +3 -3
  112. package/dist/integrations/harnesses/aider/index.js +5 -0
  113. package/dist/integrations/harnesses/amazonq/index.js +1 -0
  114. package/dist/integrations/harnesses/claude/config-import.js +2 -3
  115. package/dist/integrations/harnesses/claude/index.js +5 -0
  116. package/dist/integrations/harnesses/codex/index.js +1 -0
  117. package/dist/integrations/harnesses/copilot/index.js +1 -0
  118. package/dist/integrations/harnesses/gemini/index.js +1 -0
  119. package/dist/integrations/harnesses/index.js +18 -39
  120. package/dist/integrations/harnesses/opencode/agent-builder.js +60 -35
  121. package/dist/integrations/harnesses/opencode/config-import.js +121 -52
  122. package/dist/integrations/harnesses/opencode/index.js +22 -0
  123. package/dist/integrations/harnesses/opencode/plugin-layout.js +68 -0
  124. package/dist/integrations/harnesses/opencode/session-log-types.js +12 -0
  125. package/dist/integrations/harnesses/opencode/session-log-v1.js +72 -0
  126. package/dist/integrations/harnesses/opencode/session-log-v2.js +140 -0
  127. package/dist/integrations/harnesses/opencode/session-log.js +104 -115
  128. package/dist/integrations/harnesses/opencode/version.js +98 -0
  129. package/dist/integrations/harnesses/opencode-sdk/{harness.js → index.js} +5 -17
  130. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +105 -206
  131. package/dist/integrations/harnesses/opencode-sdk/v1-adapter.js +199 -0
  132. package/dist/integrations/harnesses/opencode-sdk/v2-adapter.js +254 -0
  133. package/dist/integrations/harnesses/opencode-sdk/wire.js +66 -0
  134. package/dist/integrations/harnesses/openhands/index.js +1 -0
  135. package/dist/integrations/harnesses/pi/index.js +1 -0
  136. package/dist/integrations/harnesses/shared.js +2 -0
  137. package/dist/integrations/harnesses/types.js +2 -0
  138. package/dist/llm/client.js +41 -11
  139. package/dist/output/cli-hints.js +1 -1
  140. package/dist/output/context.js +10 -19
  141. package/dist/output/format-exempt.js +5 -8
  142. package/dist/output/shapes/helpers.js +1 -22
  143. package/dist/output/shapes/passthrough.js +0 -1
  144. package/dist/output/shapes.js +3 -31
  145. package/dist/output/stdout.js +5 -1
  146. package/dist/output/text/command-format.js +6 -1
  147. package/dist/output/text/workflow-format.js +4 -22
  148. package/dist/scripts/akm-migrate-node.js +15745 -16711
  149. package/dist/scripts/akm-migrate.js +16318 -17280
  150. package/dist/setup/detect.js +10 -4
  151. package/dist/setup/harness-config-import.js +5 -14
  152. package/dist/setup/setup.js +1 -1
  153. package/dist/setup/steps/tasks.js +12 -13
  154. package/dist/sources/providers/git-stash.js +94 -13
  155. package/dist/storage/repositories/index-utility-repository.js +2 -32
  156. package/dist/storage/repositories/index-vec-repository.js +4 -0
  157. package/dist/storage/repositories/task-history-repository.js +0 -5
  158. package/dist/storage/repositories/workflow-runs-repository.js +5 -15
  159. package/dist/tasks/embedded.js +2 -1
  160. package/dist/tasks/run/run-command-task.js +9 -1
  161. package/dist/tasks/run/run-workflow-task.js +4 -3
  162. package/dist/tasks/run/task-history.js +0 -2
  163. package/dist/tasks/run/task-log.js +38 -1
  164. package/dist/tasks/schedule-gates.js +39 -0
  165. package/dist/tasks/scheduler-binding.js +0 -16
  166. package/dist/tasks/scheduler-sync.js +3 -97
  167. package/dist/tasks/source/bounded-document.js +1 -1
  168. package/dist/tasks/source/task-source-v4.js +2 -12
  169. package/dist/tasks/task-run-reserved-flags.js +2 -31
  170. package/dist/workflows/authoring/authoring.js +6 -6
  171. package/dist/workflows/compile.js +8 -65
  172. package/dist/workflows/exec/child-workflow.js +22 -3
  173. package/dist/workflows/exec/native-executor.js +12 -147
  174. package/dist/workflows/exec/run-workflow.js +31 -103
  175. package/dist/workflows/exec/step-work.js +9 -178
  176. package/dist/workflows/exec/unit-dispatch.js +0 -10
  177. package/dist/workflows/freeze/environment.js +3 -44
  178. package/dist/workflows/freeze/freeze.js +8 -41
  179. package/dist/workflows/freeze/targets/child-workflow.js +27 -63
  180. package/dist/workflows/freeze/targets/command.js +2 -16
  181. package/dist/workflows/freeze/targets/shell.js +0 -2
  182. package/dist/workflows/freeze/task-bindings.js +2 -28
  183. package/dist/workflows/ir/params.js +12 -2
  184. package/dist/workflows/parser.js +82 -232
  185. package/dist/workflows/plan.js +1 -1
  186. package/dist/workflows/renderer.js +10 -19
  187. package/dist/workflows/runtime/run-plan.js +11 -53
  188. package/dist/workflows/runtime/runs.js +4 -27
  189. package/dist/workflows/source-files.js +12 -16
  190. package/dist/workflows/source-semantics.js +1 -93
  191. package/docs/README.md +1 -1
  192. package/docs/agents/README.md +3 -3
  193. package/docs/agents/agent-install.md +1 -1
  194. package/docs/architecture/0.10-process-rules.md +827 -0
  195. package/docs/architecture/adapters.md +17 -31
  196. package/docs/architecture/architecture.md +4 -5
  197. package/docs/architecture/improvement.md +115 -76
  198. package/docs/architecture/internals/improve-workflow.md +22 -16
  199. package/docs/architecture/internals/indexing.md +1 -1
  200. package/docs/architecture/internals/search.md +1 -2
  201. package/docs/architecture/internals/storage-locations.md +6 -6
  202. package/docs/architecture/persisted-data-compat.md +13 -4
  203. package/docs/architecture/specs/0.9.0-decisions.md +6 -2
  204. package/docs/architecture/specs/improve-collapse-churn-detector-design.md +2 -2
  205. package/docs/architecture/specs/workflow-format-unification.md +1 -6
  206. package/docs/architecture/testing/manual-testing-checklist.md +21 -30
  207. package/docs/architecture/testing/task-workflow-manual-tests.md +106 -203
  208. package/docs/architecture/testing/testing-workflow.md +1 -1
  209. package/docs/architecture/workflow-engine.md +5 -56
  210. package/docs/guides/author-workflows.md +39 -69
  211. package/docs/guides/bundles.md +3 -1
  212. package/docs/guides/capture-knowledge.md +1 -1
  213. package/docs/guides/claude-code-vs-akm-workflows.md +1 -2
  214. package/docs/guides/discover-and-load.md +4 -4
  215. package/docs/guides/getting-started.md +1 -1
  216. package/docs/guides/improve-the-library.md +10 -9
  217. package/docs/guides/recipes/headless-install.md +2 -2
  218. package/docs/guides/run-workflows.md +1 -1
  219. package/docs/guides/scheduling.md +9 -1
  220. package/docs/guides/use-with-any-agent.md +49 -4
  221. package/docs/integration/bundling-akm.md +7 -6
  222. package/docs/maintainers/curate-workmap.md +1 -2
  223. package/docs/maintainers/release-checklist.md +33 -1
  224. package/docs/migration/README.md +2 -0
  225. package/docs/migration/release-notes/0.10.0.md +201 -0
  226. package/docs/migration/release-notes/README.md +1 -0
  227. package/docs/migration/v0.9-to-v0.10.md +507 -0
  228. package/docs/posts/akm-command-tour.md +3 -3
  229. package/docs/product-surface.md +1 -1
  230. package/docs/reference/README.md +1 -0
  231. package/docs/reference/api.md +65 -0
  232. package/docs/reference/asset-types.md +3 -5
  233. package/docs/reference/bundle-types.md +2 -4
  234. package/docs/reference/cli.md +160 -188
  235. package/docs/reference/configuration.md +101 -20
  236. package/docs/reference/data-and-telemetry.md +2 -2
  237. package/docs/reference/env-and-secrets.md +2 -2
  238. package/docs/reference/supported-formats.md +3 -4
  239. package/docs/reference/tasks.md +12 -19
  240. package/docs/reference/website-sources.md +1 -1
  241. package/docs/reference/workflow-schema.md +89 -317
  242. package/docs/reference/workflows.md +20 -48
  243. package/package.json +12 -2
  244. package/schemas/akm-config.json +2 -243
  245. package/schemas/akm-task.json +2 -3
  246. package/schemas/akm-workflow.json +47 -84
  247. package/dist/assets/improve-strategies/catchup.json +0 -41
  248. package/dist/assets/improve-strategies/proactive-maintenance.json +0 -24
  249. package/dist/assets/improve-strategies/quick.json +0 -17
  250. package/dist/assets/improve-strategies/reflect-distill.json +0 -29
  251. package/dist/assets/improve-strategies/thorough.json +0 -49
  252. package/dist/assets/prompts/retrieval-relevance-judge.md +0 -6
  253. package/dist/assets/tasks/improve/akm-improve-catchup.yml +0 -6
  254. package/dist/assets/tasks/improve/akm-improve-frequent.yml +0 -4
  255. package/dist/assets/tasks/improve/akm-improve-nightly.yml +0 -4
  256. package/dist/cli/retired-commands.js +0 -118
  257. package/dist/commands/improve/retrieval-gate.js +0 -132
  258. package/dist/commands/tasks/validate.js +0 -176
  259. package/dist/core/config/legacy-source-shape-shim.js +0 -117
  260. package/dist/indexer/feedback/utility-policy.js +0 -75
  261. package/dist/integrations/harnesses/ids.js +0 -28
  262. package/dist/tasks/source/task-source-v3-frozen.js +0 -424
  263. package/dist/tasks/source/task-to-v4.js +0 -863
  264. package/dist/workflows/exec/worktree.js +0 -372
  265. package/dist/workflows/freeze/child-output-references.js +0 -83
  266. package/dist/workflows/freeze/identity.js +0 -22
  267. package/dist/workflows/freeze/targets/script.js +0 -50
  268. package/dist/workflows/freeze/targets/task.js +0 -124
  269. package/dist/workflows/github-yaml.js +0 -506
  270. package/dist/workflows/runtime/run-outputs.js +0 -80
@@ -116,6 +116,51 @@ export function scheduleKillLadder(proc, opts) {
116
116
  if (typeof sigkillTimer !== "number")
117
117
  sigkillTimer.unref?.();
118
118
  }
119
+ const liveGroupChildren = new Set();
120
+ let signalGuardHandlers;
121
+ function removeSignalGuard() {
122
+ for (const [signal, handler] of signalGuardHandlers ?? [])
123
+ process.off(signal, handler);
124
+ signalGuardHandlers = undefined;
125
+ }
126
+ /**
127
+ * A captured child runs in its own process group, so the terminal's Ctrl-C and
128
+ * a `kill <akm-pid>` no longer reach it: without this, SIGTERM/SIGINT to akm
129
+ * ended akm and left the harness CLI (and everything it started) running. Each
130
+ * live child is registered here and, on the first such signal, takes the same
131
+ * ladder as a timeout; once every child is gone the signal is re-raised so akm
132
+ * still ends by the signal itself.
133
+ *
134
+ * The listeners are installed only while a child is live and only when nothing
135
+ * else handles the signal. A command that handles it itself (`workflow run`,
136
+ * `improve`, task runs) unwinds through its own cleanup, which already aborts
137
+ * its children, and must not have its handling pre-empted by a re-raise.
138
+ */
139
+ function registerLiveGroupChild(child) {
140
+ liveGroupChildren.add(child);
141
+ if (!signalGuardHandlers && process.listenerCount("SIGINT") === 0 && process.listenerCount("SIGTERM") === 0) {
142
+ const handlers = [];
143
+ for (const signal of ["SIGINT", "SIGTERM"]) {
144
+ const handler = () => {
145
+ const children = [...liveGroupChildren];
146
+ for (const live of children)
147
+ live.terminate();
148
+ removeSignalGuard();
149
+ void Promise.allSettled(children.map((live) => live.proc.exited)).then(() => {
150
+ process.kill(process.pid, signal);
151
+ });
152
+ };
153
+ process.once(signal, handler);
154
+ handlers.push([signal, handler]);
155
+ }
156
+ signalGuardHandlers = handlers;
157
+ }
158
+ return () => {
159
+ liveGroupChildren.delete(child);
160
+ if (liveGroupChildren.size === 0)
161
+ removeSignalGuard();
162
+ };
163
+ }
119
164
  /**
120
165
  * The joined, human-readable reason ONE managed run's capture is incomplete, or
121
166
  * `undefined` when both pipes drained cleanly.
@@ -401,6 +446,8 @@ export async function runManagedSubprocess(cmd, opts) {
401
446
  else
402
447
  abortSignal.addEventListener("abort", onAbort, { once: true });
403
448
  }
449
+ // A termination signal to this process takes the own-group child down too.
450
+ const unregisterLiveChild = capture && spawnsOwnProcessGroup() ? registerLiveGroupChild({ proc, terminate: onAbort }) : () => { };
404
451
  // Stream-drain timeout: a short grace once nothing living owns the pipe any
405
452
  // more, or a one-hour safety bound when execution itself is unbounded.
406
453
  //
@@ -454,6 +501,7 @@ export async function runManagedSubprocess(cmd, opts) {
454
501
  if (timer !== undefined)
455
502
  clearTimeoutImpl(timer);
456
503
  abortSignal?.removeEventListener("abort", onAbort);
504
+ unregisterLiveChild();
457
505
  // Drain readers so they don't surface as unhandled rejections. The streams
458
506
  // carry their own drain timeout so this cannot block indefinitely.
459
507
  await Promise.allSettled([stdoutPromise, stderrPromise]);
@@ -470,6 +518,7 @@ export async function runManagedSubprocess(cmd, opts) {
470
518
  }
471
519
  clearTimeoutImpl(timer);
472
520
  abortSignal?.removeEventListener("abort", onAbort);
521
+ unregisterLiveChild();
473
522
  const [stdoutRead, stderrRead] = await Promise.all([stdoutPromise, stderrPromise]);
474
523
  return {
475
524
  exitCode,
package/dist/core/warn.js CHANGED
@@ -15,6 +15,8 @@
15
15
  import fs from "node:fs";
16
16
  import path from "node:path";
17
17
  let quiet = false;
18
+ /** Calls inside {@link withQuiet}; counted so overlapping calls cannot leave the flag flipped. */
19
+ let quietScopes = 0;
18
20
  let verbose = false;
19
21
  let logFilePath;
20
22
  let sinkOverride;
@@ -33,7 +35,22 @@ export function resetQuiet() {
33
35
  quiet = false;
34
36
  }
35
37
  export function isQuiet() {
36
- return quiet;
38
+ return quiet || quietScopes > 0;
39
+ }
40
+ /**
41
+ * Run `fn` with stderr diagnostics suppressed (the log file sink still
42
+ * receives them), then restore. For in-process callers (`src/api.ts`) that
43
+ * must stay silent without touching the host's `--quiet` state: a depth count
44
+ * rather than save/restore of the flag keeps overlapping async calls correct.
45
+ */
46
+ export async function withQuiet(fn) {
47
+ quietScopes++;
48
+ try {
49
+ return await fn();
50
+ }
51
+ finally {
52
+ quietScopes--;
53
+ }
37
54
  }
38
55
  /**
39
56
  * Set the verbose flag from a CLI flag. The `AKM_VERBOSE` env var, when set,
@@ -103,7 +120,7 @@ export function info(...args) {
103
120
  return;
104
121
  }
105
122
  appendToLogFile("INFO", args);
106
- if (!quiet) {
123
+ if (!isQuiet()) {
107
124
  console.warn(...args);
108
125
  }
109
126
  }
@@ -118,7 +135,7 @@ export function warn(...args) {
118
135
  return;
119
136
  }
120
137
  appendToLogFile("WARN", args);
121
- if (!quiet) {
138
+ if (!isQuiet()) {
122
139
  console.warn(...args);
123
140
  }
124
141
  }
@@ -148,7 +165,7 @@ export function error(...args) {
148
165
  return;
149
166
  }
150
167
  appendToLogFile("ERROR", args);
151
- if (!quiet) {
168
+ if (!isQuiet()) {
152
169
  console.error(...args);
153
170
  }
154
171
  }
@@ -7,7 +7,7 @@
7
7
  * The only module that branches on `source.kind` for writes. It does two
8
8
  * things:
9
9
  *
10
- * 1. Resolve the destination: `--target` → `defaultWriteTarget` → working
10
+ * 1. Resolve the destination: `--bundle` → `defaultWriteTarget` → working
11
11
  * stash (`defaultBundle`). `writable` defaults to true on `filesystem` and
12
12
  * false on `git`; `website` / `npm` are never writable (the config loader
13
13
  * rejects `writable: true` on them).
@@ -53,7 +53,7 @@ export function isWriteCapableSourceKind(kind) {
53
53
  return kind === "filesystem" || kind === "git";
54
54
  }
55
55
  /**
56
- * Resolve the destination for a write: explicit `--target`, then
56
+ * Resolve the destination for a write: explicit `--bundle`, then
57
57
  * `defaultWriteTarget`, then the working stash (`defaultBundle`). There is no
58
58
  * fallback to the first writable source.
59
59
  */
@@ -61,7 +61,7 @@ export function resolveWriteTarget(akmConfig, explicitTarget, options = {}) {
61
61
  const allConfiguredSources = resolveConfiguredSources(akmConfig);
62
62
  const configuredSources = resolveActiveConfiguredSources(akmConfig);
63
63
  const requireWritable = options.requireWritable !== false;
64
- const flag = options.flag ?? "--target";
64
+ const flag = options.flag ?? "--bundle";
65
65
  if (explicitTarget) {
66
66
  const match = configuredSources.find((s) => s.name === explicitTarget);
67
67
  if (!match) {
@@ -22,8 +22,7 @@
22
22
  *
23
23
  * Explicit non-goals (binding, spec §4.1): no GitHub locator grammar, no
24
24
  * `akm/command` builtin special case, no resolution, no filesystem access, no
25
- * guessing. Callers layer builtin detection on top (see
26
- * `classifyWorkflowStepUses` in src/workflows/source-semantics.ts).
25
+ * guessing.
27
26
  */
28
27
  import { bundleRefToString, parseBundleRef } from "../core/asset/asset-ref.js";
29
28
  import { UsageError } from "../core/errors.js";
@@ -1291,7 +1291,10 @@ export async function lookup(ref) {
1291
1291
  }
1292
1292
  // ── Utility score recomputation ──────────────────────────────────────────────
1293
1293
  /**
1294
- * Recompute utility scores for all entries based on usage_events data.
1294
+ * Recompute utility scores based on usage_events data: every entry by default,
1295
+ * or just `onlyEntryId` (what `akm feedback` passes so the value it stores is
1296
+ * the one `akm index` would compute, not a second formula). A one-entry call
1297
+ * leaves the retention purge and the `last_utility_computed_at` watermark alone.
1295
1298
  *
1296
1299
  * For each entry:
1297
1300
  * - Count search appearances (event_type = 'search')
@@ -1306,11 +1309,13 @@ export async function lookup(ref) {
1306
1309
  *
1307
1310
  * Called during `akm index` after FTS rebuild.
1308
1311
  */
1309
- export function recomputeUtilityScores(db, stateDb) {
1312
+ export function recomputeUtilityScores(db, stateDb, onlyEntryId) {
1310
1313
  const EMA_DECAY = 0.7;
1314
+ const scoped = onlyEntryId !== undefined;
1311
1315
  // Purge stale usage events (90-day retention). usage_events lives in state.db
1312
1316
  // (Chunk-8 WI-8.3); its table is created by state migration 020.
1313
- purgeOldUsageEvents(stateDb, USAGE_EVENT_RETENTION_DAYS);
1317
+ if (!scoped)
1318
+ purgeOldUsageEvents(stateDb, USAGE_EVENT_RETENTION_DAYS);
1314
1319
  // Time-proportional decay: apply one round of EMA per elapsed day so
1315
1320
  // indexing frequency doesn't affect how fast scores decay.
1316
1321
  const lastComputedAt = getMeta(db, "last_utility_computed_at");
@@ -1344,14 +1349,17 @@ export function recomputeUtilityScores(db, stateDb) {
1344
1349
  FROM usage_events u
1345
1350
  WHERE u.entry_id IS NOT NULL
1346
1351
  AND u.source = 'user'
1352
+ ${scoped ? "AND u.entry_id = ?" : ""}
1347
1353
  GROUP BY u.entry_id
1348
1354
  `)
1349
- .all();
1355
+ .all(...(scoped ? [onlyEntryId] : []));
1350
1356
  const entryExists = db.prepare("SELECT 1 FROM entries WHERE id = ?");
1351
1357
  const usageByEntry = new Map(aggregatedRows.filter((row) => entryExists.get(row.entry_id) != null).map((row) => [row.entry_id, row]));
1352
1358
  // Batch-load existing utility scores
1353
1359
  const existingScores = new Map();
1354
- const scoreRows = db.prepare("SELECT entry_id, utility, last_used_at FROM utility_scores").all();
1360
+ const scoreRows = db
1361
+ .prepare(`SELECT entry_id, utility, last_used_at FROM utility_scores${scoped ? " WHERE entry_id = ?" : ""}`)
1362
+ .all(...(scoped ? [onlyEntryId] : []));
1355
1363
  for (const row of scoreRows) {
1356
1364
  existingScores.set(row.entry_id, { utility: row.utility, lastUsedAt: row.last_used_at ?? undefined });
1357
1365
  }
@@ -1391,5 +1399,6 @@ export function recomputeUtilityScores(db, stateDb) {
1391
1399
  lastUsedAt,
1392
1400
  });
1393
1401
  }
1394
- setMeta(db, "last_utility_computed_at", new Date().toISOString());
1402
+ if (!scoped)
1403
+ setMeta(db, "last_utility_computed_at", new Date().toISOString());
1395
1404
  }
@@ -247,12 +247,19 @@ async function runMemoryInferencePassBody(ctx, provenance) {
247
247
  const featureConfig = invocationOwnsRunner
248
248
  ? { ...config, index: { ...config.index, memory: { ...config.index?.memory, enabled: true } } }
249
249
  : config;
250
- // The pass only writes to the primary (working) stash. Read-only caches
251
- // (git, npm, website) are deliberately untouched — writing inferred
252
- // children there would be clobbered by the next sync().
253
- const primary = sources[0];
254
- if (!primary || (primary.adapterId ?? detectAdapterId(primary.path)) !== "akm")
250
+ // The pass only writes to the default (working) stash, found by its
251
+ // `isDefault` flag and never by array position. Read-only caches (git, npm,
252
+ // website) are deliberately untouched — writing inferred children there
253
+ // would be clobbered by the next sync().
254
+ const primary = sources.find((source) => source.isDefault === true);
255
+ if (!primary) {
256
+ warn("memory inference: skipped — no default bundle among the sources, so there is no write target");
255
257
  return completeResult();
258
+ }
259
+ if ((primary.adapterId ?? detectAdapterId(primary.path)) !== "akm") {
260
+ warn(`memory inference: skipped — the default bundle at ${primary.path} is not an akm bundle`);
261
+ return completeResult();
262
+ }
256
263
  const pending = collectPendingMemories(primary.path).filter((record) => !options.candidateRefs || options.candidateRefs.has(record.ref));
257
264
  result.considered = pending.length;
258
265
  if (pending.length === 0)
@@ -511,7 +518,9 @@ function markParentProcessed(parent) {
511
518
  warn(`memory inference: skipping markParentProcessed for ${parent.filePath} — could not parse frontmatter block`);
512
519
  return;
513
520
  }
514
- const updatedFm = { ...parent.data, [FM_INFERENCE_PROCESSED]: true };
521
+ // Stamp the frontmatter as it is now: `parent.data` was parsed before the model
522
+ // call, and writing it back would revert any edit made to the file since.
523
+ const updatedFm = { ...parseFrontmatter(raw).data, [FM_INFERENCE_PROCESSED]: true };
515
524
  const next = assembleAsset(updatedFm, block.content);
516
525
  try {
517
526
  fs.writeFileSync(parent.filePath, next, "utf8");
@@ -6,15 +6,16 @@
6
6
  *
7
7
  * Ranking is two candidate channels fused by reciprocal rank (`ranking.ts`):
8
8
  * BM25 over whole documents matching any query word, and the nearest document
9
- * vectors to the query embedding. Filters narrow the fused list; nothing else
9
+ * vectors to the query embedding. The fused score is then scaled by the
10
+ * asset's recorded user feedback. Filters narrow the list; nothing else
10
11
  * reorders it.
11
12
  */
12
13
  import path from "node:path";
13
- import { stashDirFor } from "../../core/asset/asset-placement.js";
14
14
  import { displayRef } from "../../core/asset/resolve-ref.js";
15
15
  import { compareCodePoints } from "../../core/common.js";
16
16
  import { classifyPathAccess } from "../../core/path-access.js";
17
17
  import { getDbPath } from "../../core/paths.js";
18
+ import { withStateDbTelemetry } from "../../core/state-db.js";
18
19
  import { systemErrorCode } from "../../core/system-error.js";
19
20
  import { presentationFor } from "../../core/type-presentation.js";
20
21
  import { embed } from "../../llm/embedder.js";
@@ -27,8 +28,9 @@ import { getMeta } from "../../storage/repositories/index-meta-repository.js";
27
28
  import { getEmbeddingCount, searchVec } from "../../storage/repositories/index-vec-repository.js";
28
29
  import { ensureIndex } from "../ensure-index.js";
29
30
  import { isProposedQuality } from "../passes/metadata.js";
30
- import { ftsQueryTokens, parseRefPrefixQuery, parseRetiredTypePrefixQuery } from "./fts-query.js";
31
- import { reciprocalRankFusion } from "./ranking.js";
31
+ import { countFeedbackSignalsByRef } from "../usage/usage-events.js";
32
+ import { ftsQueryTokens, parseRefPrefixQuery } from "./fts-query.js";
33
+ import { applyFeedback, reciprocalRankFusion, } from "./ranking.js";
32
34
  import { attachSearchHitAttribution } from "./search-attribution.js";
33
35
  import { enrichSearchHit } from "./search-hit-enrichers.js";
34
36
  import { buildEditHint, findSourceForPath, isEditable } from "./search-source.js";
@@ -81,30 +83,31 @@ function resolveSearchHitRef(entry, provenance, defaultBundleId) {
81
83
  }
82
84
  // ── Main search entrypoint ───────────────────────────────────────────────────
83
85
  /**
84
- * Whether an embedding provider is actually configured.
86
+ * Whether `auto` has a usable embedding provider.
85
87
  *
86
- * A remote provider needs BOTH endpoint and model. A LOCAL provider needs
87
- * neither — `embedding.localModel` selects a transformers model that runs in
88
- * process. Checking only the remote pair told every local-provider user that
89
- * "no embedding provider is configured" and pointed them at
90
- * `akm config set embedding '{"endpoint":...}'`, which is the wrong remedy and
91
- * hid the real diagnostic recorded in the semantic status.
88
+ * `auto` with no (or no remote) embedding settings falls back to the built-in
89
+ * local model (`DEFAULT_LOCAL_MODEL`, or `embedding.localModel`), so semantic
90
+ * search works without any configuration. The only config that can never
91
+ * succeed is a remote `endpoint` without a `model` — `RemoteEmbedder` throws on
92
+ * construction. Treating "no endpoint+model pair" as unconfigured warned every
93
+ * default install that semantic search had no provider while it worked.
92
94
  */
93
95
  function hasConfiguredEmbeddingProvider(config) {
94
- if (config.embedding?.localModel)
96
+ const embedding = config.embedding;
97
+ if (!embedding?.endpoint)
95
98
  return true;
96
- return Boolean(config.embedding?.endpoint && config.embedding?.model);
99
+ return Boolean(embedding.model);
97
100
  }
98
101
  export async function searchLocal(input) {
99
- const { query, stashDir, config } = input;
102
+ const { stashDir, config } = input;
100
103
  const warnings = [];
101
104
  // Semantic search is attempted fresh on every query (see `startVectorChannel`);
102
105
  // there is no cached readiness verdict to consult here. The only thing
103
106
  // worth flagging ahead of the attempt is a config that can never succeed.
104
107
  if (config.semanticSearchMode === "auto" && !hasConfiguredEmbeddingProvider(config)) {
105
- warnings.push("Semantic search is enabled (semanticSearchMode='auto') but no embedding provider is configured. " +
106
- 'Either: (a) `akm config set embedding \'{"endpoint":"...","model":"..."}\'`, or ' +
107
- "(b) `akm config set semanticSearchMode off` to use keyword-only search.");
108
+ warnings.push("Semantic search is enabled (semanticSearchMode='auto') but embedding.endpoint is set without embedding.model. " +
109
+ "Either: (a) set embedding.model, or (b) remove embedding.endpoint to use the built-in local model, or " +
110
+ "(c) `akm config set semanticSearchMode off` to use keyword-only search.");
108
111
  }
109
112
  // Bootstrap-only: builds the index inline when it cannot serve this stash.
110
113
  // Content freshness is the writers' job (indexWrittenAssets + full runs);
@@ -138,12 +141,14 @@ export async function searchLocal(input) {
138
141
  const staleHint = buildStaleIndexHint(db);
139
142
  if (staleHint)
140
143
  warnings.push(staleHint);
141
- const { hits, embedMs, rankMs, mode, semanticWarning } = await searchDatabase(db, input);
144
+ const { hits, embedMs, rankMs, mode, semanticWarning, feedbackWarning } = await searchDatabase(db, input);
142
145
  if (semanticWarning)
143
146
  warnings.push(semanticWarning);
147
+ if (feedbackWarning)
148
+ warnings.push(feedbackWarning);
144
149
  return {
145
150
  hits,
146
- tip: hits.length === 0 ? emptyResultTip(query) : undefined,
151
+ tip: hits.length === 0 ? EMPTY_RESULT_TIP : undefined,
147
152
  warnings: warnings.length > 0 ? warnings : undefined,
148
153
  embedMs,
149
154
  rankMs,
@@ -218,7 +223,9 @@ async function searchDatabase(db, input) {
218
223
  const embedMs = Date.now() - startedAt;
219
224
  const tRank0 = Date.now();
220
225
  const vectorCandidates = vector.neighbors ? keepAllowedTypes(db, vector.neighbors, typeFilter, defaultExcludes) : [];
221
- const fused = reciprocalRankFusion([lexical, vectorCandidates]);
226
+ const rrf = reciprocalRankFusion([lexical, vectorCandidates]);
227
+ const feedback = readFeedback(rrf);
228
+ const fused = applyFeedback(rrf, feedback.counts);
222
229
  const selected = selectFusedEntries(db, fused, limit, filterOptions);
223
230
  const rankMs = Date.now() - tRank0;
224
231
  const hits = await Promise.all(selected.map(({ candidate, row }) => buildDbHit({
@@ -235,7 +242,27 @@ async function searchDatabase(db, input) {
235
242
  db,
236
243
  })));
237
244
  const mode = vector.warning ? "fts-fallback" : vector.neighbors ? "semantic" : "keyword";
238
- return { embedMs, rankMs, hits, mode, semanticWarning: vector.warning };
245
+ return { embedMs, rankMs, hits, mode, semanticWarning: vector.warning, feedbackWarning: feedback.warning };
246
+ }
247
+ /**
248
+ * User feedback counts of the fused candidates, from state.db in one query.
249
+ * A search never creates state.db, and one that cannot read it ranks without
250
+ * feedback and says so.
251
+ */
252
+ function readFeedback(fused) {
253
+ let counts = new Map();
254
+ try {
255
+ withStateDbTelemetry((stateDb) => {
256
+ counts = countFeedbackSignalsByRef(stateDb, fused.map((candidate) => candidate.itemRef));
257
+ });
258
+ }
259
+ catch (error) {
260
+ return {
261
+ counts: new Map(),
262
+ warning: `Feedback was not applied to the ranking: ${error instanceof Error ? error.message : String(error)}`,
263
+ };
264
+ }
265
+ return { counts };
239
266
  }
240
267
  /**
241
268
  * Walk the fused list in order, loading entries a batch at a time, and keep
@@ -294,21 +321,7 @@ function describeRanks(candidate) {
294
321
  ...(vectorRank !== undefined ? [`vector rank ${vectorRank}`] : []),
295
322
  ];
296
323
  }
297
- /**
298
- * The no-hits tip. A query in the retired `<type>:` / `<type>:<prefix>/` browse
299
- * grammar gets the conceptId spelling that replaces it: without this it comes
300
- * back empty and silent, which is the failure D4 removed the grammar to avoid.
301
- */
302
- function emptyResultTip(query) {
303
- const generic = "No matching stash assets were found. Try a different query or run 'akm index' to rebuild.";
304
- const retired = parseRetiredTypePrefixQuery(query);
305
- if (!retired)
306
- return generic;
307
- const root = stashDirFor(retired.type);
308
- if (!root)
309
- return generic;
310
- return `No matching stash assets were found. The '<type>:' browse grammar was removed in 0.9.0 — use the conceptId spelling: 'akm search "${root}/${retired.rest}"'.`;
311
- }
324
+ const EMPTY_RESULT_TIP = "No matching stash assets were found. Try a different query or run 'akm index' to rebuild.";
312
325
  // ── Enumeration (browse) path ────────────────────────────────────────────────
313
326
  /**
314
327
  * Enumerate index entries without FTS scoring — the browse path shared by
@@ -78,22 +78,3 @@ export function parseRefPrefixQuery(query) {
78
78
  return { bundle, conceptIdPrefix: rest };
79
79
  return null;
80
80
  }
81
- /**
82
- * Recognize the retired `<type>:` / `<type>:<prefix>/` browse grammar so the
83
- * caller can name the replacement spelling rather than letting the query
84
- * degrade silently into a keyword search — the exact silent failure D4 removes.
85
- * Shape recognition only; mapping the type to its conceptId root belongs to the
86
- * caller, which keeps this module dependency-free.
87
- */
88
- export function parseRetiredTypePrefixQuery(query) {
89
- const trimmed = query.trim();
90
- if (trimmed.length === 0 || /\s/.test(trimmed) || trimmed.includes("//"))
91
- return null;
92
- const colon = trimmed.indexOf(":");
93
- if (colon <= 0)
94
- return null;
95
- const rest = trimmed.slice(colon + 1);
96
- if (rest !== "" && !rest.endsWith("/"))
97
- return null;
98
- return { type: trimmed.slice(0, colon), rest };
99
- }
@@ -3,7 +3,8 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  /**
5
5
  * Reciprocal rank fusion (Cormack, Clarke and Büttcher, SIGIR 2009) of the
6
- * lexical and vector candidate lists — the whole of search ranking.
6
+ * lexical and vector candidate lists, then one feedback factor on the fused
7
+ * score — the whole of search ranking.
7
8
  *
8
9
  * Fusing ranks rather than scores needs no calibration between BM25 and cosine
9
10
  * similarity, so it holds for every embedding model. On the retrieval suite,
@@ -35,3 +36,30 @@ export function reciprocalRankFusion(channels) {
35
36
  });
36
37
  return [...fused.values()].sort((a, b) => b.score - a.score || compareCodePoints(a.itemRef, b.itemRef));
37
38
  }
39
+ /**
40
+ * Weight of one log-count of feedback on the fused score. A factor of
41
+ * 1 + w·(ln(1+pos) − ln(1+neg)) moves a score by about 2% / 3% / 5% / 9% for
42
+ * 1 / 2 / 5 / 20 positives at 0.03. Measured on the retrieval suite's fused
43
+ * lists, 5 positives lift a hit from rank 10 by a median of 3 places (from rank 5,
44
+ * by 2) and 20 by 4 to 5, so a voted hit passes its near neighbours and a clearly
45
+ * better match stays ahead. At 0.05 the same votes lift it 4 to 5 and 6 to 7
46
+ * places, and two wrong votes cost about 1.6 times the nDCG@10 they cost at 0.03.
47
+ */
48
+ export const FEEDBACK_WEIGHT = 0.03;
49
+ /**
50
+ * The multiplier on a hit's fused score for its feedback: the same for every
51
+ * asset, rising with the number of positives and falling with the number of
52
+ * negatives, each by a logarithm so a heavily voted asset gains less per vote.
53
+ */
54
+ export function feedbackFactor({ pos, neg }) {
55
+ return 1 + FEEDBACK_WEIGHT * (Math.log1p(pos) - Math.log1p(neg));
56
+ }
57
+ /** Scale fused scores by the feedback of each asset (keyed by `itemRef`) and reorder, with the same tie-break as fusion. */
58
+ export function applyFeedback(fused, feedback) {
59
+ return fused
60
+ .map((candidate) => {
61
+ const counts = feedback.get(candidate.itemRef);
62
+ return counts ? { ...candidate, score: candidate.score * feedbackFactor(counts) } : candidate;
63
+ })
64
+ .sort((a, b) => b.score - a.score || compareCodePoints(a.itemRef, b.itemRef));
65
+ }
@@ -8,6 +8,7 @@
8
8
  * id, event_type, query, entry_id (nullable), entry_ref, signal, metadata, source, created_at
9
9
  */
10
10
  import { rethrowIfTestIsolationError } from "../../core/errors.js";
11
+ import { SQLITE_CHUNK_SIZE } from "../../storage/repositories/index-sql.js";
11
12
  const USAGE_EVENT_SOURCES = new Set(["user", "improve", "task", "audit", "unknown"]);
12
13
  /**
13
14
  * Resolve subprocess provenance without treating an invalid value as user
@@ -64,22 +65,26 @@ export function insertUsageEvent(db, event) {
64
65
  }
65
66
  // ── Query ────────────────────────────────────────────────────────────────────
66
67
  /**
67
- * Aggregate positive/negative feedback counts for a single entry.
68
- *
69
- * Lifted verbatim from `akm feedback` (feedback-cli.ts) where the same
70
- * SUM(CASE …) query was hand-rolled inline. Returns plain numbers (NULL SUMs
71
- * over an empty set are coalesced to 0) so the result fully materialises before
72
- * any owning connection closes.
68
+ * User feedback counts for many assets at once, keyed by durable `entry_ref`.
69
+ * Refs with no user feedback are absent from the map. Read in one query per
70
+ * chunk of refs.
73
71
  */
74
- export function countFeedbackSignals(db, entryId) {
75
- const counts = db
76
- .prepare(`SELECT
77
- SUM(CASE WHEN signal = 'positive' THEN 1 ELSE 0 END) AS pos,
78
- SUM(CASE WHEN signal = 'negative' THEN 1 ELSE 0 END) AS neg
79
- FROM usage_events
80
- WHERE event_type = 'feedback' AND entry_id = ? AND source = 'user'`)
81
- .get(entryId);
82
- return { pos: counts?.pos ?? 0, neg: counts?.neg ?? 0 };
72
+ export function countFeedbackSignalsByRef(db, refs) {
73
+ const counts = new Map();
74
+ for (let i = 0; i < refs.length; i += SQLITE_CHUNK_SIZE) {
75
+ const chunk = refs.slice(i, i + SQLITE_CHUNK_SIZE);
76
+ const rows = db
77
+ .prepare(`SELECT entry_ref,
78
+ SUM(CASE WHEN signal = 'positive' THEN 1 ELSE 0 END) AS pos,
79
+ SUM(CASE WHEN signal = 'negative' THEN 1 ELSE 0 END) AS neg
80
+ FROM usage_events
81
+ WHERE event_type = 'feedback' AND source = 'user' AND entry_ref IN (${chunk.map(() => "?").join(",")})
82
+ GROUP BY entry_ref`)
83
+ .all(...chunk);
84
+ for (const row of rows)
85
+ counts.set(row.entry_ref, { pos: row.pos, neg: row.neg });
86
+ }
87
+ return counts;
83
88
  }
84
89
  /**
85
90
  * Count usage events of a given `event_type`.
@@ -95,7 +100,7 @@ export function countUsageEventsByType(db, eventType) {
95
100
  /**
96
101
  * Durable refs a user-attributed `search`, `curate` or `show` returned, or a
97
102
  * user `feedback` named, at or after `sinceIso`. Machine traffic (`improve`,
98
- * `task`, `audit`, `unknown`) is not demand, as in {@link countFeedbackSignals}.
103
+ * `task`, `audit`, `unknown`) is not demand, as in {@link countFeedbackSignalsByRef}.
99
104
  */
100
105
  export function listUsedEntryRefs(db, sinceIso) {
101
106
  const rows = db
@@ -12,8 +12,9 @@ import { formatExtraParamsIssue, validateExtraParams } from "../../core/extra-pa
12
12
  import { collectSensitiveValues } from "../../core/redaction.js";
13
13
  import { resolveSecretFromStore } from "../../sources/snapshot-fetchers/secret-seam.js";
14
14
  import { getHarness } from "../harnesses/index.js";
15
+ import { resolveOpencodeBin } from "../harnesses/opencode/version.js";
15
16
  import { DEFAULT_LLM_TIMEOUT_MS } from "./config.js";
16
- import { getBuiltinAgentProfile, OPENCODE_SDK_SERVER_BIN } from "./profiles.js";
17
+ import { getBuiltinAgentProfile } from "./profiles.js";
17
18
  const LLM_CONNECTION_FIELDS = [
18
19
  "provider",
19
20
  "endpoint",
@@ -273,11 +274,13 @@ function lowerAgentEngine(name, engine, config) {
273
274
  const platform = harness.id;
274
275
  const sdk = platform === "opencode-sdk";
275
276
  const builtin = getBuiltinAgentProfile(platform);
277
+ // An OpenCode engine without its own `bin` runs the newest OpenCode on PATH.
278
+ const opencodeFamily = platform === "opencode" || sdk;
276
279
  const profile = {
277
280
  name,
278
281
  platform,
279
282
  personaChannel: sdk ? "native" : (harness.agentBuilder?.personaChannel ?? "prompt"),
280
- bin: engine.bin ?? builtin?.bin ?? (sdk ? OPENCODE_SDK_SERVER_BIN : platform),
283
+ bin: opencodeFamily ? resolveOpencodeBin(engine.bin) : (engine.bin ?? builtin?.bin ?? platform),
281
284
  args: engine.args ?? builtin?.args ?? [],
282
285
  stdio: "captured",
283
286
  ...(builtin?.env ? { env: builtin.env } : {}),
@@ -3,5 +3,5 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  export { DEFAULT_AGENT_TIMEOUT_MS } from "./config.js";
5
5
  export { _setAgentDetectForTests, defaultWhich, detectAgentCliProfiles, pickDefaultAgentProfile } from "./detect.js";
6
- export { BUILTIN_AGENT_PROFILE_NAMES, getBuiltinAgentProfile, listBuiltinAgentProfiles, } from "./profiles.js";
6
+ export { getBuiltinAgentProfile, listBuiltinAgentProfiles, } from "./profiles.js";
7
7
  export { buildProposePrompt, buildReflectPrompt, parseAgentProposalPayload } from "./prompts.js";