akm-cli 0.8.6 → 0.8.14

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 (324) hide show
  1. package/CHANGELOG.md +442 -0
  2. package/dist/assets/help/help-proposals.md +1 -2
  3. package/dist/assets/hints/cli-hints-full.md +34 -19
  4. package/dist/assets/hints/cli-hints-short.md +1 -1
  5. package/dist/assets/profiles/catchup.json +13 -0
  6. package/dist/assets/profiles/consolidate.json +13 -0
  7. package/dist/assets/profiles/frequent.json +13 -0
  8. package/dist/assets/tasks/core/backup.yml +4 -0
  9. package/dist/assets/tasks/core/extract.yml +4 -0
  10. package/dist/assets/tasks/core/improve.yml +4 -0
  11. package/dist/assets/tasks/core/index-refresh.yml +4 -0
  12. package/dist/assets/tasks/core/sync.yml +4 -0
  13. package/dist/assets/tasks/core/update-stashes.yml +4 -0
  14. package/dist/assets/tasks/core/version-check.yml +4 -0
  15. package/dist/assets/templates/html/default.html +78 -0
  16. package/dist/assets/templates/html/health.html +560 -0
  17. package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
  18. package/dist/cli/config-migrate.js +6 -6
  19. package/dist/cli/config-validate.js +4 -4
  20. package/dist/cli/confirm.js +3 -3
  21. package/dist/cli/parse-args.js +1 -1
  22. package/dist/cli/shared.js +72 -19
  23. package/dist/cli-node.mjs +26 -0
  24. package/dist/cli.js +206 -3866
  25. package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
  26. package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
  27. package/dist/commands/agent/contribute-cli.js +200 -0
  28. package/dist/commands/completions.js +1 -1
  29. package/dist/commands/config-cli.js +230 -3
  30. package/dist/commands/db-cli.js +2 -2
  31. package/dist/commands/env/env-cli.js +529 -0
  32. package/dist/commands/env/env.js +410 -0
  33. package/dist/commands/env/secret-cli.js +259 -0
  34. package/dist/commands/{secret.js → env/secret.js} +6 -47
  35. package/dist/commands/events.js +4 -4
  36. package/dist/commands/feedback-cli.js +18 -34
  37. package/dist/commands/graph/graph-cli.js +132 -0
  38. package/dist/commands/{graph.js → graph/graph.js} +22 -16
  39. package/dist/commands/health/checks.js +279 -0
  40. package/dist/commands/health/html-report.js +448 -0
  41. package/dist/commands/health.js +189 -266
  42. package/dist/commands/{consolidate.js → improve/consolidate.js} +63 -38
  43. package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
  44. package/dist/commands/{distill.js → improve/distill.js} +39 -18
  45. package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
  46. package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
  47. package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
  48. package/dist/commands/{extract.js → improve/extract.js} +221 -26
  49. package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +30 -4
  50. package/dist/commands/{improve-cli.js → improve/improve-cli.js} +44 -22
  51. package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
  52. package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +1 -1
  53. package/dist/commands/{improve.js → improve/improve.js} +672 -292
  54. package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
  55. package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
  56. package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
  57. package/dist/commands/improve/reflect-noise.js +0 -0
  58. package/dist/commands/{reflect.js → improve/reflect.js} +58 -28
  59. package/dist/commands/improve/session-asset.js +248 -0
  60. package/dist/commands/lint/agent-linter.js +1 -1
  61. package/dist/commands/lint/base-linter.js +55 -37
  62. package/dist/commands/lint/command-linter.js +1 -1
  63. package/dist/commands/lint/default-linter.js +1 -1
  64. package/dist/commands/lint/env-key-rules.js +1 -1
  65. package/dist/commands/lint/index.js +19 -25
  66. package/dist/commands/lint/knowledge-linter.js +1 -1
  67. package/dist/commands/lint/memory-linter.js +1 -1
  68. package/dist/commands/lint/registry.js +8 -8
  69. package/dist/commands/lint/skill-linter.js +1 -1
  70. package/dist/commands/lint/task-linter.js +1 -1
  71. package/dist/commands/lint/workflow-linter.js +1 -1
  72. package/dist/commands/lint.js +1 -1
  73. package/dist/commands/observability-cli.js +244 -0
  74. package/dist/commands/proposal/drain-policies.js +3 -3
  75. package/dist/commands/proposal/drain.js +87 -15
  76. package/dist/commands/proposal/proposal-cli.js +490 -0
  77. package/dist/commands/{proposal.js → proposal/proposal.js} +17 -6
  78. package/dist/commands/{propose.js → proposal/propose.js} +11 -11
  79. package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
  80. package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
  81. package/dist/{core → commands/proposal/validators}/proposals.js +374 -345
  82. package/dist/commands/{curate.js → read/curate.js} +7 -7
  83. package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
  84. package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
  85. package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
  86. package/dist/commands/read/search-cli.js +207 -0
  87. package/dist/commands/{search.js → read/search.js} +22 -27
  88. package/dist/commands/{show.js → read/show.js} +31 -45
  89. package/dist/commands/registry-cli.js +8 -8
  90. package/dist/commands/remember.js +14 -10
  91. package/dist/commands/sources/add-cli.js +293 -0
  92. package/dist/commands/{history.js → sources/history.js} +27 -25
  93. package/dist/commands/{info.js → sources/info.js} +6 -6
  94. package/dist/commands/{init.js → sources/init.js} +6 -6
  95. package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
  96. package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
  97. package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
  98. package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
  99. package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
  100. package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
  101. package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
  102. package/dist/commands/sources/sources-cli.js +305 -0
  103. package/dist/commands/sources/stash-cli.js +219 -0
  104. package/dist/commands/{stash-skeleton.js → sources/stash-skeleton.js} +2 -1
  105. package/dist/commands/tasks/default-tasks.js +173 -0
  106. package/dist/commands/tasks/tasks-cli.js +210 -0
  107. package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
  108. package/dist/commands/wiki-cli.js +307 -0
  109. package/dist/commands/workflow-cli.js +329 -0
  110. package/dist/core/action-contributors.js +1 -1
  111. package/dist/core/assert.js +40 -0
  112. package/dist/core/asset/asset-create.js +54 -0
  113. package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
  114. package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
  115. package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
  116. package/dist/core/{markdown.js → asset/markdown.js} +1 -1
  117. package/dist/core/{stash-meta.js → asset/stash-meta.js} +1 -1
  118. package/dist/core/best-effort.js +64 -0
  119. package/dist/core/common.js +32 -18
  120. package/dist/core/{config-io.js → config/config-io.js} +29 -19
  121. package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
  122. package/dist/core/{config-schema.js → config/config-schema.js} +50 -7
  123. package/dist/core/config/config-types.js +16 -0
  124. package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
  125. package/dist/core/{config.js → config/config.js} +10 -8
  126. package/dist/core/env-secret-ref.js +90 -0
  127. package/dist/core/errors.js +13 -3
  128. package/dist/core/events.js +27 -4
  129. package/dist/core/file-lock.js +1 -1
  130. package/dist/core/improve-types.js +48 -0
  131. package/dist/core/lesson-lint.js +2 -2
  132. package/dist/core/logs-db.js +304 -0
  133. package/dist/core/paths.js +2 -2
  134. package/dist/core/ripgrep/install.js +2 -2
  135. package/dist/core/ripgrep/resolve.js +2 -2
  136. package/dist/core/state-db.js +195 -60
  137. package/dist/core/text-truncation.js +148 -0
  138. package/dist/core/time.js +1 -1
  139. package/dist/core/write-source.js +98 -85
  140. package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
  141. package/dist/indexer/{db.js → db/db.js} +128 -118
  142. package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
  143. package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
  144. package/dist/indexer/ensure-index.js +4 -4
  145. package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
  146. package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
  147. package/dist/indexer/indexer.js +37 -30
  148. package/dist/indexer/init.js +54 -0
  149. package/dist/indexer/manifest.js +10 -10
  150. package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +141 -33
  151. package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
  152. package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
  153. package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
  154. package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
  155. package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
  156. package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
  157. package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
  158. package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
  159. package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
  160. package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
  161. package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
  162. package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
  163. package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
  164. package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
  165. package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
  166. package/dist/indexer/{walker.js → walk/walker.js} +4 -3
  167. package/dist/integrations/agent/builder-shared.js +39 -0
  168. package/dist/integrations/agent/builders.js +14 -81
  169. package/dist/integrations/agent/config.js +6 -4
  170. package/dist/integrations/agent/detect.js +1 -1
  171. package/dist/integrations/agent/index.js +23 -8
  172. package/dist/integrations/agent/prompts.js +2 -3
  173. package/dist/integrations/agent/runner.js +22 -3
  174. package/dist/integrations/agent/spawn.js +9 -10
  175. package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
  176. package/dist/integrations/harnesses/claude/config-import.js +70 -0
  177. package/dist/integrations/harnesses/claude/index.js +64 -0
  178. package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +32 -5
  179. package/dist/integrations/harnesses/index.js +144 -0
  180. package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
  181. package/dist/integrations/harnesses/opencode/config-import.js +82 -0
  182. package/dist/integrations/harnesses/opencode/index.js +59 -0
  183. package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
  184. package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
  185. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
  186. package/dist/integrations/harnesses/types.js +43 -0
  187. package/dist/integrations/lockfile.js +7 -16
  188. package/dist/integrations/session-logs/index.js +82 -9
  189. package/dist/llm/call-ai.js +4 -4
  190. package/dist/llm/client.js +146 -6
  191. package/dist/llm/embedder.js +6 -6
  192. package/dist/llm/embedders/local.js +9 -22
  193. package/dist/llm/embedders/remote.js +2 -2
  194. package/dist/llm/embedders/types.js +1 -1
  195. package/dist/llm/graph-extract.js +31 -12
  196. package/dist/llm/index-passes.js +1 -1
  197. package/dist/llm/memory-infer.js +12 -5
  198. package/dist/llm/metadata-enhance.js +2 -2
  199. package/dist/llm/usage-persist.js +77 -0
  200. package/dist/llm/usage-telemetry.js +103 -0
  201. package/dist/output/context.js +9 -46
  202. package/dist/output/html-render.js +73 -0
  203. package/dist/output/renderers.js +88 -58
  204. package/dist/output/shapes/curate.js +7 -3
  205. package/dist/output/shapes/distill.js +7 -3
  206. package/dist/output/shapes/env-list.js +18 -16
  207. package/dist/output/shapes/events.js +5 -4
  208. package/dist/output/shapes/helpers.js +19 -5
  209. package/dist/output/shapes/history.js +7 -3
  210. package/dist/output/shapes/passthrough.js +8 -11
  211. package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
  212. package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
  213. package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
  214. package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
  215. package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
  216. package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
  217. package/dist/output/shapes/registry-search.js +7 -3
  218. package/dist/output/shapes/registry.js +12 -0
  219. package/dist/output/shapes/search.js +7 -3
  220. package/dist/output/shapes/secret-list.js +18 -16
  221. package/dist/output/shapes/show.js +7 -3
  222. package/dist/output/shapes.js +55 -30
  223. package/dist/output/text/add.js +2 -3
  224. package/dist/output/text/clone.js +2 -3
  225. package/dist/output/text/config.js +2 -3
  226. package/dist/output/text/curate.js +4 -3
  227. package/dist/output/text/distill.js +2 -3
  228. package/dist/output/text/enable-disable.js +5 -4
  229. package/dist/output/text/env.js +13 -0
  230. package/dist/output/text/events.js +5 -4
  231. package/dist/output/text/feedback.js +4 -3
  232. package/dist/output/text/helpers.js +123 -40
  233. package/dist/output/text/history.js +2 -3
  234. package/dist/output/text/import.js +2 -3
  235. package/dist/output/text/index.js +2 -3
  236. package/dist/output/text/info.js +2 -3
  237. package/dist/output/text/init.js +2 -3
  238. package/dist/output/text/list.js +2 -3
  239. package/dist/output/text/proposal/producer.js +9 -0
  240. package/dist/output/text/proposal/proposal.js +13 -0
  241. package/dist/output/text/registry-commands.js +8 -7
  242. package/dist/output/text/registry.js +12 -0
  243. package/dist/output/text/remember.js +4 -3
  244. package/dist/output/text/remove.js +2 -3
  245. package/dist/output/text/save.js +2 -3
  246. package/dist/output/text/search.js +4 -3
  247. package/dist/output/text/show.js +4 -3
  248. package/dist/output/text/update.js +2 -3
  249. package/dist/output/text/upgrade.js +2 -3
  250. package/dist/output/text/wiki.js +12 -11
  251. package/dist/output/text/workflow.js +12 -10
  252. package/dist/output/text.js +66 -32
  253. package/dist/registry/build-index.js +11 -10
  254. package/dist/registry/factory.js +1 -1
  255. package/dist/registry/origin-resolve.js +1 -1
  256. package/dist/registry/providers/index.js +2 -2
  257. package/dist/registry/providers/skills-sh.js +91 -72
  258. package/dist/registry/providers/static-index.js +75 -52
  259. package/dist/registry/resolve.js +3 -3
  260. package/dist/runtime.js +242 -0
  261. package/dist/scripts/migrate-storage.js +1654 -683
  262. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +254 -168
  263. package/dist/setup/detect.js +311 -9
  264. package/dist/setup/harness-config-import.js +6 -120
  265. package/dist/setup/setup.js +454 -43
  266. package/dist/sources/include.js +1 -1
  267. package/dist/sources/provider-factory.js +2 -2
  268. package/dist/sources/providers/filesystem.js +3 -3
  269. package/dist/sources/providers/git.js +9 -9
  270. package/dist/sources/providers/index.js +4 -4
  271. package/dist/sources/providers/npm.js +6 -6
  272. package/dist/sources/providers/provider-utils.js +13 -20
  273. package/dist/sources/providers/sync-from-ref.js +5 -5
  274. package/dist/sources/providers/tar-utils.js +2 -2
  275. package/dist/sources/providers/website.js +2 -2
  276. package/dist/sources/resolve.js +5 -5
  277. package/dist/sources/website-ingest.js +5 -5
  278. package/dist/storage/database.js +102 -0
  279. package/dist/storage/engines/sqlite-migrations.js +42 -0
  280. package/dist/storage/locations.js +25 -0
  281. package/dist/storage/repositories/index-db.js +43 -0
  282. package/dist/storage/repositories/workflow-runs-repository.js +141 -0
  283. package/dist/tasks/backends/cron.js +4 -4
  284. package/dist/tasks/backends/exec-utils.js +32 -0
  285. package/dist/tasks/backends/index.js +3 -3
  286. package/dist/tasks/backends/launchd.js +7 -14
  287. package/dist/tasks/backends/schtasks.js +7 -16
  288. package/dist/tasks/embedded.js +71 -0
  289. package/dist/tasks/parser.js +2 -2
  290. package/dist/tasks/resolveAkmBin.js +1 -1
  291. package/dist/tasks/runner.js +127 -31
  292. package/dist/tasks/schedule.js +1 -1
  293. package/dist/tasks/validator.js +7 -7
  294. package/dist/text-import-hook.mjs +51 -0
  295. package/dist/version.js +2 -1
  296. package/dist/wiki/wiki.js +7 -7
  297. package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
  298. package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
  299. package/dist/workflows/cli.js +1 -1
  300. package/dist/workflows/db.js +54 -32
  301. package/dist/workflows/parser.js +4 -4
  302. package/dist/workflows/renderer.js +5 -5
  303. package/dist/workflows/runtime/agent-identity.js +56 -0
  304. package/dist/workflows/runtime/checkin.js +57 -0
  305. package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
  306. package/dist/workflows/validate-summary.js +82 -0
  307. package/docs/README.md +1 -1
  308. package/docs/data-and-telemetry.md +6 -6
  309. package/package.json +17 -8
  310. package/dist/commands/add-cli.js +0 -279
  311. package/dist/commands/env.js +0 -213
  312. package/dist/integrations/agent/sdk-runner.js +0 -126
  313. package/dist/output/shapes/vault-list.js +0 -19
  314. package/dist/output/text/proposal-producer.js +0 -8
  315. package/dist/output/text/proposal.js +0 -12
  316. package/dist/output/text/vault.js +0 -16
  317. /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
  318. /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
  319. /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
  320. /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
  321. /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
  322. /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
  323. /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
  324. /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
@@ -13,23 +13,31 @@ import fs from "node:fs";
13
13
  import os from "node:os";
14
14
  import path from "node:path";
15
15
  import * as p from "@clack/prompts";
16
- import { akmInit } from "../commands/init";
17
- import { isHttpUrl } from "../core/common";
18
- import { DEFAULT_CONFIG, getDefaultLlmConfig, getEffectiveRegistries, loadUserConfig, saveConfig, } from "../core/config";
19
- import { backupExistingConfig } from "../core/config-io";
20
- import { ConfigError } from "../core/errors";
21
- import { assertSafeStashDir, getConfigPath, getDefaultStashDir, isTransientStashPath } from "../core/paths";
22
- import { warn } from "../core/warn";
23
- import { closeDatabase, isVecAvailable, openDatabase } from "../indexer/db";
24
- import { akmIndex } from "../indexer/indexer";
25
- import { clearSemanticStatus, deriveSemanticProviderFingerprint, writeSemanticStatus, } from "../indexer/semantic-status";
26
- import { detectAgentCliProfiles, pickDefaultAgentProfile } from "../integrations/agent";
27
- import { probeLlmCapabilities } from "../llm/client";
28
- import { checkEmbeddingAvailability, DEFAULT_LOCAL_MODEL, isTransformersAvailable } from "../llm/embedder";
29
- import { detectAgentPlatforms, detectLMStudio, detectOllama } from "./detect";
30
- import { detectHarnessConfigs } from "./harness-config-import";
31
- import { loadSetupStashes } from "./registry-stash-loader";
32
- import { createSetupContext, runSetupSteps } from "./steps";
16
+ import { akmInit } from "../commands/sources/init.js";
17
+ import { detectServerDefault, isCiEnvironment, registerDefaultTasks } from "../commands/tasks/default-tasks.js";
18
+ import { akmTasksAdd, akmTasksList, akmTasksSetEnabled, akmTasksSync } from "../commands/tasks/tasks.js";
19
+ import { isHttpUrl } from "../core/common.js";
20
+ import { DEFAULT_CONFIG, getDefaultLlmConfig, getEffectiveRegistries, loadUserConfig, saveConfig, } from "../core/config/config.js";
21
+ import { backupExistingConfig } from "../core/config/config-io.js";
22
+ import { ConfigError, UsageError } from "../core/errors.js";
23
+ import { assertSafeStashDir, getConfigPath, getDefaultStashDir, isTransientStashPath } from "../core/paths.js";
24
+ import { warn } from "../core/warn.js";
25
+ import { closeDatabase, isVecAvailable, openDatabase } from "../indexer/db/db.js";
26
+ import { akmIndex } from "../indexer/indexer.js";
27
+ import { clearSemanticStatus, deriveSemanticProviderFingerprint, writeSemanticStatus, } from "../indexer/search/semantic-status.js";
28
+ import { detectAgentCliProfiles, pickDefaultAgentProfile } from "../integrations/agent/index.js";
29
+ import { defaultProfileName, v1ProfilePlatform } from "../integrations/harnesses/index.js";
30
+ import { probeLlmCapabilities } from "../llm/client.js";
31
+ import { checkEmbeddingAvailability, DEFAULT_LOCAL_MODEL, isTransformersAvailable } from "../llm/embedder.js";
32
+ import { getDirname, spawn } from "../runtime.js";
33
+ import { saveGitStash } from "../sources/providers/git.js";
34
+ import { backendNameForPlatform } from "../tasks/backends/index.js";
35
+ import { listEmbeddedTasks } from "../tasks/embedded.js";
36
+ import { parseSchedule } from "../tasks/schedule.js";
37
+ import { detectAgentPlatforms, detectEnvironment, detectLMStudio, detectOllama, renderDetectionSummary, } from "./detect.js";
38
+ import { detectHarnessConfigs } from "./harness-config-import.js";
39
+ import { loadSetupStashes } from "./registry-stash-loader.js";
40
+ import { createSetupContext, runSetupSteps } from "./steps.js";
33
41
  // ── Setup sandbox guard ─────────────────────────────────────────────────────
34
42
  /**
35
43
  * Refuse to persist an explicit `--dir /tmp/...` stashDir to the user's
@@ -135,11 +143,28 @@ function applyLegacyAgent(config, agent) {
135
143
  }
136
144
  const v2Profiles = { ...(config.profiles?.agent ?? {}) };
137
145
  for (const [name, profile] of Object.entries(agent.profiles ?? {})) {
138
- const platform = profile.sdkMode
139
- ? "opencode-sdk"
140
- : name.toLowerCase().includes("claude")
141
- ? "claude"
142
- : "opencode";
146
+ // #566: resolve the platform via the harness registry instead of the old
147
+ // `name.includes("claude") ? "claude" : "opencode"` heuristic, which
148
+ // silently mapped Cursor/Copilot/any new harness to "opencode". An explicit
149
+ // sdkMode flag still wins; otherwise we ask the registry. A name the
150
+ // registry does not recognize is surfaced (warn) rather than silently
151
+ // misclassified, then kept as a best-effort "opencode" profile so the user
152
+ // does not lose a profile they explicitly configured.
153
+ let platform;
154
+ if (profile.sdkMode) {
155
+ platform = "opencode-sdk";
156
+ }
157
+ else {
158
+ const resolved = v1ProfilePlatform(name);
159
+ if (resolved) {
160
+ platform = resolved;
161
+ }
162
+ else {
163
+ warn(`[akm setup] Agent profile "${name}" did not match any known harness; ` +
164
+ `defaulting its platform to "opencode". Set its platform explicitly in config if this is wrong.`);
165
+ platform = "opencode";
166
+ }
167
+ }
143
168
  v2Profiles[name] = {
144
169
  platform,
145
170
  ...(profile.bin ? { bin: profile.bin } : {}),
@@ -386,8 +411,8 @@ async function prepareSemanticSearchAssets(config) {
386
411
  const spin = p.spinner();
387
412
  spin.start("Installing @huggingface/transformers...");
388
413
  try {
389
- const pkgRoot = path.resolve(import.meta.dir, "../..");
390
- const proc = Bun.spawn(["bun", "add", "@huggingface/transformers"], {
414
+ const pkgRoot = path.resolve(getDirname(import.meta.url), "../..");
415
+ const proc = spawn(["bun", "add", "@huggingface/transformers"], {
391
416
  cwd: pkgRoot,
392
417
  stdout: "pipe",
393
418
  stderr: "pipe",
@@ -1464,6 +1489,165 @@ export function stepAgentCliDetection(current, detectFn = detectAgentCliProfiles
1464
1489
  return { agent, detections };
1465
1490
  }
1466
1491
  // ── Main Wizard ─────────────────────────────────────────────────────────────
1492
+ /**
1493
+ * Normalise a task id the same way `akm tasks` does (strip a trailing `.yml`
1494
+ * / `.md` suffix, trim) so the wizard can match embedded template ids against
1495
+ * the ids reported by `akmTasksList()`.
1496
+ */
1497
+ function normaliseTaskIdForMatch(raw) {
1498
+ return raw.trim().replace(/\.(yml|md)$/, "");
1499
+ }
1500
+ /**
1501
+ * Interactive-only setup step: enable/disable embedded core tasks.
1502
+ *
1503
+ * Presents a multi-select of the bundled core task templates pre-checked
1504
+ * against the user's currently-enabled tasks. On confirm:
1505
+ * - newly-checked & absent → copy template (with edited schedule) into the
1506
+ * primary stash via `akmTasksAdd`, then `akmTasksSync`, then `akm sync`
1507
+ * (a no-op for non-git stashes).
1508
+ * - newly-checked & present-but-disabled → `akmTasksSetEnabled(id, true)`.
1509
+ * - previously-enabled & now unchecked → `akmTasksSetEnabled(id, false)`
1510
+ * (keeps the stash file, removes the scheduler entry).
1511
+ * - unchanged → no action.
1512
+ *
1513
+ * Exported for testing. Not registered as `nonInteractive`, so `akm init` /
1514
+ * `--yes` never reach it.
1515
+ *
1516
+ * The task primitives + git-sync helper are injected via `deps` (defaulting
1517
+ * to the real implementations) so tests can supply fakes without
1518
+ * `mock.module`-ing the shared `commands/tasks` / `sources/providers/git`
1519
+ * modules — which would leak into unrelated test files (Bun's `mock.module`
1520
+ * is process-global and not reverted by `mock.restore()`).
1521
+ */
1522
+ /**
1523
+ * Setup sub-step (issue #552): idempotently register the default improve task
1524
+ * set. Asks a single "Is this a server install?" question (defaulting per
1525
+ * platform) to decide whether the nightly sweep is enabled, then delegates to
1526
+ * {@link registerDefaultTasks}, which is CI-aware and never duplicates an
1527
+ * existing task. Skipped entirely under CI (the registration helper short-
1528
+ * circuits, and we never even prompt).
1529
+ *
1530
+ * Exported for testing.
1531
+ */
1532
+ export async function stepDefaultImproveTasks(register = registerDefaultTasks) {
1533
+ // CI: register nothing and don't prompt.
1534
+ if (isCiEnvironment()) {
1535
+ p.log.info("CI detected — skipping default improve task registration.");
1536
+ return;
1537
+ }
1538
+ const platformDefault = detectServerDefault();
1539
+ const serverInstall = await prompt(() => p.confirm({
1540
+ message: "Is this a server install? (enables the nightly quality sweep at 2am)",
1541
+ initialValue: platformDefault,
1542
+ }));
1543
+ const result = await register({ serverInstall: serverInstall === true });
1544
+ if (result.skipped)
1545
+ return;
1546
+ const total = result.created.length + result.existing.length;
1547
+ p.log.success(`Default improve tasks registered (${result.created.length} new, ${result.existing.length} already present, ${total} total).`);
1548
+ }
1549
+ const DEFAULT_SCHEDULED_TASKS_DEPS = {
1550
+ list: akmTasksList,
1551
+ add: akmTasksAdd,
1552
+ setEnabled: akmTasksSetEnabled,
1553
+ sync: akmTasksSync,
1554
+ gitSync: saveGitStash,
1555
+ };
1556
+ export async function stepScheduledTasks(deps = DEFAULT_SCHEDULED_TASKS_DEPS) {
1557
+ const embedded = listEmbeddedTasks();
1558
+ if (embedded.length === 0)
1559
+ return;
1560
+ // Snapshot current state so we can diff against the user's selection.
1561
+ let installed = [];
1562
+ try {
1563
+ installed = (await deps.list()).tasks;
1564
+ }
1565
+ catch {
1566
+ // A missing/empty tasks dir is fine — treat as nothing installed.
1567
+ installed = [];
1568
+ }
1569
+ const byId = new Map();
1570
+ for (const t of installed)
1571
+ byId.set(normaliseTaskIdForMatch(t.id), t);
1572
+ // Pre-check tasks that are installed AND enabled.
1573
+ const preChecked = embedded.filter((e) => byId.get(e.id)?.enabled === true).map((e) => e.id);
1574
+ const stateLabel = (e) => {
1575
+ const cur = byId.get(e.id);
1576
+ if (!cur)
1577
+ return "not installed";
1578
+ return cur.enabled ? "enabled" : "disabled";
1579
+ };
1580
+ const selected = await prompt(() => p.multiselect({
1581
+ message: "Enable scheduled core tasks? (space to toggle, enter to confirm)",
1582
+ required: false,
1583
+ initialValues: preChecked,
1584
+ options: embedded.map((e) => ({
1585
+ value: e.id,
1586
+ label: e.label,
1587
+ hint: `${e.description} — ${e.schedule} [${stateLabel(e)}]`,
1588
+ })),
1589
+ }));
1590
+ const selectedSet = new Set(selected);
1591
+ // Resolve per-task schedule edits for newly-checked, not-yet-installed tasks.
1592
+ const scheduleFor = new Map();
1593
+ for (const e of embedded) {
1594
+ const cur = byId.get(e.id);
1595
+ if (selectedSet.has(e.id) && !cur) {
1596
+ const edited = await prompt(() => p.text({
1597
+ message: `Schedule for ${e.label}?`,
1598
+ initialValue: e.schedule,
1599
+ validate(value) {
1600
+ const candidate = (value ?? "").trim() || e.schedule;
1601
+ try {
1602
+ parseSchedule(candidate, backendNameForPlatform());
1603
+ }
1604
+ catch (err) {
1605
+ return err instanceof Error ? err.message : "Invalid schedule.";
1606
+ }
1607
+ return undefined;
1608
+ },
1609
+ }));
1610
+ const sched = (edited ?? "").trim() || e.schedule;
1611
+ scheduleFor.set(e.id, sched);
1612
+ }
1613
+ }
1614
+ let syncNeeded = false;
1615
+ for (const e of embedded) {
1616
+ const cur = byId.get(e.id);
1617
+ const checked = selectedSet.has(e.id);
1618
+ if (checked && !cur) {
1619
+ // New task: copy template into the primary stash + install scheduler entry.
1620
+ const schedule = scheduleFor.get(e.id) ?? e.schedule;
1621
+ await deps.add({
1622
+ id: e.id,
1623
+ schedule,
1624
+ command: e.command,
1625
+ description: e.description,
1626
+ });
1627
+ syncNeeded = true;
1628
+ }
1629
+ else if (checked && cur && !cur.enabled) {
1630
+ // Present but disabled → re-enable.
1631
+ await deps.setEnabled(e.id, true);
1632
+ }
1633
+ else if (!checked && cur?.enabled) {
1634
+ // Previously enabled, now unchecked → disable (keep the stash file).
1635
+ await deps.setEnabled(e.id, false);
1636
+ }
1637
+ // No state change → no action.
1638
+ }
1639
+ if (syncNeeded) {
1640
+ // Reconcile scheduler entries with on-disk YAML, then commit the new file
1641
+ // to git (a no-op for non-git stashes).
1642
+ await deps.sync();
1643
+ try {
1644
+ deps.gitSync(undefined, "akm setup: enable scheduled tasks");
1645
+ }
1646
+ catch {
1647
+ // Non-fatal — the task is installed regardless of git sync outcome.
1648
+ }
1649
+ }
1650
+ }
1467
1651
  /**
1468
1652
  * Build the canonical list of `SetupStep`s for the interactive wizard.
1469
1653
  * Exposed (and exported) so tests and `akm init` can compose subsets.
@@ -1479,8 +1663,9 @@ export function buildSetupSteps(options) {
1479
1663
  let ollamaEndpoint;
1480
1664
  let ollamaChatModels;
1481
1665
  let lmStudioResult;
1482
- // Harness configs detected once and shared with the LLM step.
1483
- const harnessConfigs = detectHarnessConfigs();
1666
+ // Harness configs detected once and shared with the LLM step. Reuse the
1667
+ // aggregate detection's harness configs when available so we detect once.
1668
+ const harnessConfigs = options.detection?.harnessConfigs ?? detectHarnessConfigs();
1484
1669
  const steps = [
1485
1670
  {
1486
1671
  id: "stash-dir",
@@ -1580,6 +1765,16 @@ export function buildSetupSteps(options) {
1580
1765
  ctx.apply({ output });
1581
1766
  },
1582
1767
  },
1768
+ {
1769
+ id: "scheduled-tasks",
1770
+ label: "Scheduled Tasks",
1771
+ // Interactive-only: `akm init` / `--yes` skip this step so headless
1772
+ // runs never enable a scheduled task (see issue #512).
1773
+ async run() {
1774
+ await stepDefaultImproveTasks();
1775
+ await stepScheduledTasks();
1776
+ },
1777
+ },
1583
1778
  ];
1584
1779
  return { steps, outcome };
1585
1780
  }
@@ -1604,11 +1799,28 @@ export async function runSetupWizard(opts) {
1604
1799
  p.log.warn("No network connectivity detected. Skipping Ollama detection and remote embedding checks.\n" +
1605
1800
  "Local-only setup will continue. Re-run `akm setup` when online for full configuration.");
1606
1801
  }
1802
+ // Aggregate environment detection — run once before any prompt and surface
1803
+ // a summary so the user sees what was auto-detected. NAMES only, never
1804
+ // API key values.
1805
+ const detection = await detectEnvironment({ existingStashDir: current.stashDir });
1806
+ p.note(renderDetectionSummary(detection), "Detected environment");
1807
+ // Interactive entry point for `--reset-recommended`: offer to apply the
1808
+ // opinionated, detection-derived defaults and skip the step-by-step wizard.
1809
+ const useRecommended = await prompt(() => p.confirm({
1810
+ message: "Apply recommended defaults from the detected environment (merged into your existing config)?",
1811
+ initialValue: false,
1812
+ }));
1813
+ if (useRecommended) {
1814
+ const result = await runResetRecommended({ dir: opts?.dir, noInit: opts?.noInit });
1815
+ p.outro(`Recommended configuration saved to ${result.configPath}`);
1816
+ return;
1817
+ }
1607
1818
  const ctx = createSetupContext(current, { nonInteractive: false });
1608
1819
  const { steps, outcome } = buildSetupSteps({
1609
1820
  online,
1610
1821
  semanticSearchOutcome: { mode: current.semanticSearchMode, prepareAssets: false },
1611
1822
  preferredStashDir: resolvedStashDir,
1823
+ detection,
1612
1824
  });
1613
1825
  // Wrap each step with a `p.log.step()` header so the wizard UI is
1614
1826
  // unchanged. The canonical `runSetupSteps()` runner is used directly by
@@ -1664,10 +1876,7 @@ export async function runSetupWizard(opts) {
1664
1876
  bail();
1665
1877
  // Save config
1666
1878
  const cfgPath1 = getConfigPath();
1667
- if (fs.existsSync(cfgPath1)) {
1668
- backupExistingConfig(cfgPath1);
1669
- p.log.info(`Config backed up to ~/.cache/akm/config-backups/`);
1670
- }
1879
+ backupAndAnnounce(cfgPath1);
1671
1880
  saveConfig(newConfig);
1672
1881
  if (semanticSearchMode.mode === "off") {
1673
1882
  clearSemanticStatus();
@@ -1751,6 +1960,20 @@ export async function runSetupWizard(opts) {
1751
1960
  p.outro(`Configuration saved to ${configPath}`);
1752
1961
  }
1753
1962
  // ── Non-interactive / scripting entry points ─────────────────────────────────
1963
+ /**
1964
+ * Back up an existing config file and print the real, timestamped backup
1965
+ * location (not a generic display string). On a fresh install where there is
1966
+ * nothing to back up, print a "nothing to back up" notice instead.
1967
+ */
1968
+ function backupAndAnnounce(configPath) {
1969
+ const result = backupExistingConfig(configPath);
1970
+ if (result) {
1971
+ p.log.info(`Config backed up to ${result.timestamped}`);
1972
+ }
1973
+ else {
1974
+ p.log.info("No existing config to back up.");
1975
+ }
1976
+ }
1754
1977
  /**
1755
1978
  * Run setup in non-interactive mode, applying all defaults.
1756
1979
  * Safe to call from CI or scripts. Idempotent — re-running produces the same result.
@@ -1776,19 +1999,41 @@ export async function runSetupWithDefaults(opts) {
1776
1999
  // Ensure stashDir is set
1777
2000
  if (!ctx.config.stashDir)
1778
2001
  ctx.apply({ stashDir });
2002
+ // Aggregate environment detection — apply detected values directly.
2003
+ const env = await detectEnvironment({ existingStashDir: ctx.config.stashDir });
2004
+ // Apply a detected LLM (live local server) when the config has none yet.
2005
+ if (!getDefaultLlmConfig(ctx.config)) {
2006
+ const liveLocal = env.localServers.find((s) => s.available && s.defaultModel);
2007
+ if (liveLocal?.defaultModel) {
2008
+ const llm = {
2009
+ provider: "local",
2010
+ endpoint: `${liveLocal.baseUrl.replace(/\/$/, "")}/v1`,
2011
+ model: liveLocal.defaultModel,
2012
+ };
2013
+ // A required field being unresolvable must fail loudly rather than write
2014
+ // a broken config (--yes acceptance criterion).
2015
+ if (!llm.endpoint?.trim() || !llm.model?.trim()) {
2016
+ throw new UsageError("Detected a local LLM server but could not resolve a required field (endpoint/model). Re-run `akm setup` interactively.", "MISSING_REQUIRED_ARGUMENT");
2017
+ }
2018
+ ctx.apply(applyLegacyLlm(ctx.config, llm));
2019
+ }
2020
+ }
1779
2021
  // Auto-detect agent CLI if not already configured
1780
2022
  if (!ctx.config.defaults?.agent) {
1781
- const detected = detectAgentCliProfiles(undefined);
1782
- const defaultProfile = pickDefaultAgentProfile(detected, undefined);
2023
+ let defaultProfile;
2024
+ if (env.harness !== "none") {
2025
+ defaultProfile = env.harness;
2026
+ }
2027
+ else {
2028
+ const detected = detectAgentCliProfiles(undefined);
2029
+ defaultProfile = pickDefaultAgentProfile(detected, undefined);
2030
+ }
1783
2031
  if (defaultProfile) {
1784
2032
  ctx.apply(applyLegacyAgent(ctx.config, { default: defaultProfile }));
1785
2033
  }
1786
2034
  }
1787
2035
  const cfgPath2 = getConfigPath();
1788
- if (fs.existsSync(cfgPath2)) {
1789
- backupExistingConfig(cfgPath2);
1790
- p.log.info(`Config backed up to ~/.cache/akm/config-backups/`);
1791
- }
2036
+ backupAndAnnounce(cfgPath2);
1792
2037
  saveConfig(ctx.config);
1793
2038
  return {
1794
2039
  configPath: getConfigPath(),
@@ -1799,6 +2044,147 @@ export async function runSetupWithDefaults(opts) {
1799
2044
  ripgrep: initResult?.ripgrep,
1800
2045
  };
1801
2046
  }
2047
+ /**
2048
+ * Recursively merge `incoming` into `base`: plain objects merge key-by-key,
2049
+ * while arrays and scalars replace wholesale. A partial input therefore only
2050
+ * updates the keys it carries and never drops sibling subkeys (e.g. a file
2051
+ * containing `{ output: { format: "text" } }` leaves `output.detail` intact).
2052
+ *
2053
+ * `base` is treated as immutable — a fresh object graph is returned.
2054
+ */
2055
+ function deepMergeConfig(base, incoming) {
2056
+ if (!isPlainObject(incoming))
2057
+ return incoming;
2058
+ const baseObj = isPlainObject(base) ? base : {};
2059
+ const out = { ...baseObj };
2060
+ for (const [key, value] of Object.entries(incoming)) {
2061
+ if (value === undefined)
2062
+ continue;
2063
+ if (isPlainObject(value) && isPlainObject(baseObj[key])) {
2064
+ out[key] = deepMergeConfig(baseObj[key], value);
2065
+ }
2066
+ else {
2067
+ out[key] = value;
2068
+ }
2069
+ }
2070
+ return out;
2071
+ }
2072
+ /** True for non-null, non-array plain objects. */
2073
+ function isPlainObject(value) {
2074
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2075
+ }
2076
+ /**
2077
+ * Run ONLY environment detection and return the typed result. Performs no
2078
+ * config writes and shows no prompts. Backs `akm setup --detect-only`.
2079
+ *
2080
+ * SAFETY: The returned object carries env var NAMES only — never any API key
2081
+ * value.
2082
+ */
2083
+ export async function runDetectOnly() {
2084
+ const current = loadUserConfig();
2085
+ return detectEnvironment({ existingStashDir: current.stashDir });
2086
+ }
2087
+ /**
2088
+ * Derive opinionated defaults from a detection result.
2089
+ *
2090
+ * - Best harness → agent default (when a profile maps to it).
2091
+ * - Fastest live local model, else the first detected cloud key's provider.
2092
+ * - `nomic-embed-text` embeddings when a local LLM is live.
2093
+ * - improve task `0 2 * * *`, index task `0 4 * * *`.
2094
+ *
2095
+ * Returns a partial `AkmConfig`-shaped object plus a legacy `llm` block, ready
2096
+ * to merge. Never includes an API key value.
2097
+ */
2098
+ export function deriveRecommendedConfig(env) {
2099
+ const result = {};
2100
+ // Best harness → agent default. #566: derive the default profile name from
2101
+ // the harness registry instead of a hardcoded if-chain, so a newly added
2102
+ // dispatch-capable harness gets a usable headless default (its canonical id)
2103
+ // automatically. "none" / unknown ids resolve to undefined (no default).
2104
+ const agentDefault = defaultProfileName(env.harness);
2105
+ if (agentDefault)
2106
+ result.agentDefault = agentDefault;
2107
+ // LLM: prefer a live local server, else a detected cloud provider key.
2108
+ const liveLocal = env.localServers.find((s) => s.available && s.defaultModel);
2109
+ if (liveLocal?.defaultModel) {
2110
+ result.llm = {
2111
+ provider: "local",
2112
+ endpoint: `${liveLocal.baseUrl.replace(/\/$/, "")}/v1`,
2113
+ model: liveLocal.defaultModel,
2114
+ };
2115
+ // Local LLM live → use a local embedding model.
2116
+ result.embedding = { provider: "ollama", model: "nomic-embed-text", endpoint: `${liveLocal.baseUrl}/v1` };
2117
+ }
2118
+ else {
2119
+ // Map a detected cloud API-key provider to an llm endpoint. NAMES only —
2120
+ // the value lives in the env var the user already set; we never read it.
2121
+ const cloud = env.providers.find((pr) => pr.kind === "apiKey");
2122
+ if (cloud) {
2123
+ const endpoint = cloudEndpointForProvider(cloud.provider);
2124
+ const model = cloudDefaultModelForProvider(cloud.provider);
2125
+ if (endpoint && model) {
2126
+ result.llm = { provider: cloud.provider, endpoint, model };
2127
+ }
2128
+ }
2129
+ }
2130
+ result.taskSchedules = { improve: "0 2 * * *", index: "0 4 * * *" };
2131
+ return result;
2132
+ }
2133
+ function cloudEndpointForProvider(provider) {
2134
+ switch (provider) {
2135
+ case "anthropic":
2136
+ return "https://api.anthropic.com/v1";
2137
+ case "openai":
2138
+ return "https://api.openai.com/v1";
2139
+ case "gemini":
2140
+ return "https://generativelanguage.googleapis.com/v1beta/openai";
2141
+ case "groq":
2142
+ return "https://api.groq.com/openai/v1";
2143
+ default:
2144
+ return undefined;
2145
+ }
2146
+ }
2147
+ function cloudDefaultModelForProvider(provider) {
2148
+ switch (provider) {
2149
+ case "anthropic":
2150
+ return "claude-sonnet-4-5";
2151
+ case "openai":
2152
+ return "gpt-4o-mini";
2153
+ case "gemini":
2154
+ return "gemini-1.5-flash";
2155
+ case "groq":
2156
+ return "llama-3.3-70b-versatile";
2157
+ default:
2158
+ return undefined;
2159
+ }
2160
+ }
2161
+ /**
2162
+ * `akm setup --reset-recommended`: merge opinionated, detection-derived
2163
+ * defaults into the existing config WITHOUT removing pre-existing custom keys.
2164
+ * Uses the same merge path as {@link runSetupFromConfig} so custom keys survive
2165
+ * (follows #511 semantics).
2166
+ */
2167
+ export async function runResetRecommended(opts) {
2168
+ const current = loadUserConfig();
2169
+ const env = await detectEnvironment({ existingStashDir: current.stashDir });
2170
+ const recommended = deriveRecommendedConfig(env);
2171
+ const incoming = {};
2172
+ if (recommended.llm)
2173
+ incoming.llm = recommended.llm;
2174
+ if (recommended.embedding)
2175
+ incoming.embedding = recommended.embedding;
2176
+ if (recommended.agentDefault)
2177
+ incoming.agent = { default: recommended.agentDefault };
2178
+ if (recommended.taskSchedules) {
2179
+ incoming.setup = { taskSchedules: recommended.taskSchedules };
2180
+ }
2181
+ return runSetupFromConfig({
2182
+ configJson: JSON.stringify(incoming),
2183
+ dir: opts.dir,
2184
+ noInit: opts.noInit,
2185
+ probe: opts.probe,
2186
+ });
2187
+ }
1802
2188
  /**
1803
2189
  * Apply a JSON config blob non-interactively, merging it with the current config.
1804
2190
  * Validates required sub-fields and strips unknown/restricted keys.
@@ -1821,6 +2207,7 @@ export async function runSetupFromConfig(opts) {
1821
2207
  "output",
1822
2208
  "profiles",
1823
2209
  "defaults",
2210
+ "setup",
1824
2211
  ]);
1825
2212
  for (const key of Object.keys(incoming)) {
1826
2213
  if (!ALLOWED_KEYS.has(key)) {
@@ -1852,12 +2239,16 @@ export async function runSetupFromConfig(opts) {
1852
2239
  assertSetupSandbox(stashDir, stashDirExplicit);
1853
2240
  applyStashIsolationToEnv(stashDir, stashDirExplicit);
1854
2241
  let merged = { ...current, stashDir };
1855
- // Apply non-llm/agent keys directly.
1856
- const mergedRec = merged;
2242
+ // Deep-merge non-llm/agent keys: nested objects merge key-by-key so a
2243
+ // partial `--file` only updates the keys it carries and never drops sibling
2244
+ // subkeys (e.g. output.detail survives an output.format-only file). Arrays
2245
+ // and scalars replace wholesale.
1857
2246
  for (const key of Object.keys(incoming)) {
1858
2247
  if (key === "llm" || key === "agent")
1859
2248
  continue;
1860
- mergedRec[key] = incoming[key];
2249
+ const incomingVal = incoming[key];
2250
+ const mergedRec = merged;
2251
+ mergedRec[key] = deepMergeConfig(mergedRec[key], incomingVal);
1861
2252
  }
1862
2253
  // Translate legacy llm/agent inputs into the new shape.
1863
2254
  if (incoming.llm) {
@@ -1866,6 +2257,29 @@ export async function runSetupFromConfig(opts) {
1866
2257
  if (incoming.agent) {
1867
2258
  merged = { ...merged, ...applyLegacyAgent(merged, incoming.agent) };
1868
2259
  }
2260
+ // With `--yes`, fill keys still missing after the merge with non-interactive
2261
+ // defaults. Steps start from `merged` and their nonInteractive path only
2262
+ // populates absent values, so nothing the file or existing config supplied
2263
+ // is overwritten.
2264
+ if (opts.applyDefaults) {
2265
+ const ctx = createSetupContext(merged, { nonInteractive: true });
2266
+ const { steps } = buildSetupSteps({
2267
+ online: false,
2268
+ semanticSearchOutcome: { mode: merged.semanticSearchMode, prepareAssets: false },
2269
+ preferredStashDir: stashDir,
2270
+ });
2271
+ await runSetupSteps(steps, ctx);
2272
+ if (!ctx.config.stashDir)
2273
+ ctx.apply({ stashDir });
2274
+ if (!ctx.config.defaults?.agent) {
2275
+ const detected = detectAgentCliProfiles(undefined);
2276
+ const defaultProfile = pickDefaultAgentProfile(detected, undefined);
2277
+ if (defaultProfile) {
2278
+ ctx.apply(applyLegacyAgent(ctx.config, { default: defaultProfile }));
2279
+ }
2280
+ }
2281
+ merged = ctx.config;
2282
+ }
1869
2283
  // Bootstrap directory structure
1870
2284
  let initResult;
1871
2285
  if (!opts.noInit) {
@@ -1891,10 +2305,7 @@ export async function runSetupFromConfig(opts) {
1891
2305
  }
1892
2306
  }
1893
2307
  const cfgPath3 = getConfigPath();
1894
- if (fs.existsSync(cfgPath3)) {
1895
- backupExistingConfig(cfgPath3);
1896
- p.log.info(`Config backed up to ~/.cache/akm/config-backups/`);
1897
- }
2308
+ backupAndAnnounce(cfgPath3);
1898
2309
  saveConfig(merged);
1899
2310
  return {
1900
2311
  configPath: getConfigPath(),
@@ -3,7 +3,7 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
- import { isWithin } from "../core/common";
6
+ import { isWithin } from "../core/common.js";
7
7
  // ── Helpers ─────────────────────────────────────────────────────────────────
8
8
  /** Key to check in package.json for akm include configuration. */
9
9
  const INCLUDE_CONFIG_KEYS = ["akm"];
@@ -1,8 +1,8 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { getSources } from "../core/config";
5
- import { createProviderRegistry } from "../registry/create-provider-registry";
4
+ import { getSources } from "../core/config/config.js";
5
+ import { createProviderRegistry } from "../registry/create-provider-registry.js";
6
6
  // ── Factory map ─────────────────────────────────────────────────────────────
7
7
  const registry = createProviderRegistry();
8
8
  export function registerSourceProvider(type, factory) {
@@ -1,9 +1,9 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { resolveStashDir } from "../../core/common";
5
- import { ConfigError } from "../../core/errors";
6
- import { registerSourceProvider } from "../provider-factory";
4
+ import { resolveStashDir } from "../../core/common.js";
5
+ import { ConfigError } from "../../core/errors.js";
6
+ import { registerSourceProvider } from "../provider-factory.js";
7
7
  /**
8
8
  * Filesystem source — points at a directory the user already manages.
9
9
  *