akm-cli 0.9.1 → 0.9.2-alpha.2

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 (350) hide show
  1. package/CHANGELOG.md +103 -28
  2. package/README.md +3 -1
  3. package/SECURITY.md +1 -1
  4. package/STABILITY.md +1 -1
  5. package/dist/akm +2 -2
  6. package/dist/akm-migrate +2 -2
  7. package/dist/assets/hints/cli-hints-full.md +14 -9
  8. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
  9. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  10. package/dist/assets/models.json +35 -0
  11. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
  12. package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
  13. package/dist/assets/tasks/core/extract.yml +6 -5
  14. package/dist/assets/tasks/core/improve.yml +6 -5
  15. package/dist/assets/tasks/core/index-refresh.yml +6 -5
  16. package/dist/assets/tasks/core/sync.yml +6 -5
  17. package/dist/assets/tasks/core/version-check.yml +6 -5
  18. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
  19. package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
  20. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
  21. package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
  22. package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
  23. package/dist/cli/confirm.js +2 -2
  24. package/dist/cli/parse-args.js +3 -24
  25. package/dist/cli/retired-commands.js +1 -1
  26. package/dist/cli/shared.js +2 -2
  27. package/dist/cli.js +11 -9
  28. package/dist/commands/agent/agent-dispatch.js +55 -89
  29. package/dist/commands/agent/contribute-cli.js +12 -45
  30. package/dist/commands/command/builtin-action.js +32 -0
  31. package/dist/commands/command/command-cli.js +99 -0
  32. package/dist/commands/command/command-execution.js +308 -0
  33. package/dist/commands/command/execution-source-loader.js +176 -0
  34. package/dist/commands/command/portable-template.js +60 -0
  35. package/dist/commands/config-cli.js +10 -4
  36. package/dist/commands/env/env.js +4 -2
  37. package/dist/commands/feedback-cli.js +1 -1
  38. package/dist/commands/health/checks.js +241 -29
  39. package/dist/commands/health/html-report.js +0 -14
  40. package/dist/commands/health/report-view-model.js +0 -1
  41. package/dist/commands/health/surfaces.js +6 -7
  42. package/dist/commands/health/types.js +0 -2
  43. package/dist/commands/health.js +63 -18
  44. package/dist/commands/improve/collapse-detector.js +5 -6
  45. package/dist/commands/improve/consolidate.js +251 -214
  46. package/dist/commands/improve/distill/promote-memory.js +71 -34
  47. package/dist/commands/improve/distill/quality-gate.js +17 -5
  48. package/dist/commands/improve/distill.js +232 -155
  49. package/dist/commands/improve/eligibility.js +112 -79
  50. package/dist/commands/improve/execution.js +57 -0
  51. package/dist/commands/improve/extract-cli.js +5 -5
  52. package/dist/commands/improve/extract-prompt.js +64 -22
  53. package/dist/commands/improve/extract.js +608 -360
  54. package/dist/commands/improve/improve-strategies.js +43 -14
  55. package/dist/commands/improve/improve.js +249 -29
  56. package/dist/commands/improve/loop-stages.js +11 -17
  57. package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
  58. package/dist/commands/improve/outcome-loop.js +22 -38
  59. package/dist/commands/improve/planner.js +134 -0
  60. package/dist/commands/improve/preparation.js +730 -409
  61. package/dist/commands/improve/reflect.js +386 -223
  62. package/dist/commands/improve/run-context.js +3 -4
  63. package/dist/commands/improve/salience.js +6 -58
  64. package/dist/commands/improve/session-asset.js +12 -12
  65. package/dist/commands/lint/index.js +101 -29
  66. package/dist/commands/migrate-cli.js +11 -69
  67. package/dist/commands/migration-tool.js +6 -9
  68. package/dist/commands/models-cli.js +27 -0
  69. package/dist/commands/proposal/drain.js +258 -186
  70. package/dist/commands/proposal/proposal-cli.js +32 -10
  71. package/dist/commands/proposal/proposal.js +2 -5
  72. package/dist/commands/proposal/propose.js +192 -172
  73. package/dist/commands/proposal/repository.js +54 -91
  74. package/dist/commands/proposal/validators/proposal-validators.js +9 -7
  75. package/dist/commands/read/curate.js +53 -22
  76. package/dist/commands/read/registry-search.js +25 -9
  77. package/dist/commands/read/remember-cli.js +14 -2
  78. package/dist/commands/read/search.js +10 -4
  79. package/dist/commands/read/show.js +139 -153
  80. package/dist/commands/registry-cli.js +16 -7
  81. package/dist/commands/remember.js +33 -18
  82. package/dist/commands/sources/add-cli.js +19 -178
  83. package/dist/commands/sources/bundle-cli.js +15 -3
  84. package/dist/commands/sources/dangerous-env-audit.js +135 -0
  85. package/dist/commands/sources/info.js +2 -1
  86. package/dist/commands/sources/installed-stashes.js +901 -177
  87. package/dist/commands/sources/schema-repair.js +174 -95
  88. package/dist/commands/sources/self-update.js +30 -74
  89. package/dist/commands/sources/source-add.js +3 -5
  90. package/dist/commands/sources/sources-cli.js +2 -15
  91. package/dist/commands/sources/update-transaction.js +220 -0
  92. package/dist/commands/tasks/tasks-cli.js +3 -3
  93. package/dist/commands/tasks/tasks.js +736 -317
  94. package/dist/commands/workflow-cli.js +2 -2
  95. package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
  96. package/dist/core/adapter/adapters/akm-adapter.js +85 -35
  97. package/dist/core/adapter/adapters/akm-lint.js +54 -39
  98. package/dist/core/adapter/adapters/akm-metadata.js +45 -45
  99. package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
  100. package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
  101. package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
  102. package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
  103. package/dist/core/adapter/adapters/index.js +0 -9
  104. package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
  105. package/dist/core/adapter/adapters/okf-adapter.js +4 -0
  106. package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
  107. package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
  108. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
  109. package/dist/core/adapter/execution-source.js +308 -0
  110. package/dist/core/adapter/recognize-match.js +36 -13
  111. package/dist/core/adapter/registry.js +0 -9
  112. package/dist/core/asset/stash-meta.js +94 -4
  113. package/dist/core/common.js +6 -11
  114. package/dist/core/config/config-io.js +3 -3
  115. package/dist/core/config/config-schema.js +18 -40
  116. package/dist/core/config/config-sources.js +11 -21
  117. package/dist/core/config/config-walker.js +31 -13
  118. package/dist/core/config/config.js +23 -26
  119. package/dist/core/config/schema/engines.js +8 -7
  120. package/dist/core/config/schema/improve-processes.js +29 -5
  121. package/dist/core/config/schema/index-config.js +0 -27
  122. package/dist/core/config/schema/primitives.js +1 -23
  123. package/dist/core/config/schema/sources-bundles.js +13 -16
  124. package/dist/core/errors.js +2 -0
  125. package/dist/core/events.js +68 -32
  126. package/dist/core/extra-params.js +1 -0
  127. package/dist/core/improve-result.js +315 -0
  128. package/dist/core/lesson-lint.js +0 -6
  129. package/dist/core/maintenance-barrier.js +4 -4
  130. package/dist/core/network-policy.js +152 -0
  131. package/dist/core/paths.js +1 -1
  132. package/dist/core/recognition-util.js +4 -4
  133. package/dist/core/registry-url.js +456 -0
  134. package/dist/core/state/migrations.js +161 -47
  135. package/dist/core/state-db.js +453 -80
  136. package/dist/core/system-error.js +32 -0
  137. package/dist/core/time.js +2 -12
  138. package/dist/core/write-source.js +0 -18
  139. package/dist/execution/directory-identity.js +52 -0
  140. package/dist/execution/executable-identity.js +107 -0
  141. package/dist/execution/guarded-source.js +398 -0
  142. package/dist/execution/json.js +95 -0
  143. package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
  144. package/dist/execution/record.js +55 -0
  145. package/dist/execution/resolved-request.js +730 -0
  146. package/dist/execution/source.js +320 -0
  147. package/dist/indexer/bundle-identity-guard.js +5 -4
  148. package/dist/indexer/db/graph-db.js +33 -0
  149. package/dist/indexer/graph/graph-boost.js +3 -4
  150. package/dist/indexer/graph/graph-extraction.js +562 -373
  151. package/dist/indexer/index-written-assets.js +78 -39
  152. package/dist/indexer/indexer.js +471 -432
  153. package/dist/indexer/installations.js +6 -0
  154. package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
  155. package/dist/indexer/materialize-embeddings.js +155 -0
  156. package/dist/indexer/passes/memory-inference.js +227 -174
  157. package/dist/indexer/passes/metadata.js +263 -118
  158. package/dist/indexer/scan/doc-to-entry.js +7 -10
  159. package/dist/indexer/scan/drain-dir.js +51 -23
  160. package/dist/indexer/search/db-search.js +156 -50
  161. package/dist/indexer/search/fts-query.js +40 -40
  162. package/dist/indexer/search/ranking.js +36 -1
  163. package/dist/indexer/search/search-attribution.js +3 -1
  164. package/dist/indexer/search/search-fields.js +23 -14
  165. package/dist/indexer/search/search-hit-enrichers.js +1 -1
  166. package/dist/indexer/search/search-source.js +7 -16
  167. package/dist/indexer/search/semantic-status.js +10 -1
  168. package/dist/indexer/usage/show-usage.js +105 -0
  169. package/dist/indexer/usage/usage-events.js +7 -2
  170. package/dist/indexer/walk/matchers.js +40 -10
  171. package/dist/indexer/walk/path-resolver.js +5 -2
  172. package/dist/indexer/walk/walker.js +20 -2
  173. package/dist/integrations/agent/builder-shared.js +3 -6
  174. package/dist/integrations/agent/conversation-fallback.js +16 -0
  175. package/dist/integrations/agent/engine-resolution.js +87 -87
  176. package/dist/integrations/agent/execution-cascade.js +566 -0
  177. package/dist/integrations/agent/execution-definitions.js +211 -0
  178. package/dist/integrations/agent/execution-lowering.js +811 -0
  179. package/dist/integrations/agent/execution-preparation.js +67 -0
  180. package/dist/integrations/agent/index.js +0 -2
  181. package/dist/integrations/agent/inline-execution.js +74 -0
  182. package/dist/integrations/agent/model-map.js +515 -0
  183. package/dist/integrations/agent/persona-fallback.js +30 -0
  184. package/dist/integrations/agent/request-lowering.js +186 -0
  185. package/dist/integrations/agent/runner-dispatch.js +230 -37
  186. package/dist/integrations/agent/runner.js +12 -83
  187. package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
  188. package/dist/integrations/harnesses/aider/index.js +0 -1
  189. package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
  190. package/dist/integrations/harnesses/amazonq/index.js +0 -1
  191. package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
  192. package/dist/integrations/harnesses/claude/index.js +1 -5
  193. package/dist/integrations/harnesses/claude/session-log.js +3 -33
  194. package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
  195. package/dist/integrations/harnesses/codex/index.js +0 -1
  196. package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
  197. package/dist/integrations/harnesses/copilot/index.js +0 -1
  198. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
  199. package/dist/integrations/harnesses/gemini/index.js +0 -1
  200. package/dist/integrations/harnesses/index.js +4 -44
  201. package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
  202. package/dist/integrations/harnesses/opencode/index.js +0 -2
  203. package/dist/integrations/harnesses/opencode/session-log.js +14 -204
  204. package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
  205. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
  206. package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
  207. package/dist/integrations/harnesses/openhands/index.js +0 -1
  208. package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
  209. package/dist/integrations/harnesses/pi/index.js +0 -1
  210. package/dist/integrations/harnesses/shared.js +0 -1
  211. package/dist/integrations/harnesses/types.js +1 -3
  212. package/dist/integrations/lockfile.js +82 -79
  213. package/dist/integrations/session-logs/index.js +6 -17
  214. package/dist/integrations/session-logs/provider-base.js +1 -29
  215. package/dist/llm/client.js +10 -5
  216. package/dist/llm/embedder.js +6 -7
  217. package/dist/llm/embedders/local.js +37 -88
  218. package/dist/llm/embedders/types.js +1 -1
  219. package/dist/llm/graph-extract.js +75 -50
  220. package/dist/llm/index-passes.js +43 -5
  221. package/dist/llm/memory-infer.js +8 -6
  222. package/dist/llm/metadata-enhance.js +5 -3
  223. package/dist/llm/structured-call.js +122 -25
  224. package/dist/output/format-exempt.js +1 -1
  225. package/dist/output/render-registry.js +0 -16
  226. package/dist/output/renderers.js +12 -7
  227. package/dist/output/shapes/curate.js +1 -0
  228. package/dist/output/shapes/helpers.js +10 -2
  229. package/dist/output/shapes/passthrough.js +2 -0
  230. package/dist/output/text/command-format.js +31 -33
  231. package/dist/output/text/health-format.js +1 -29
  232. package/dist/output/text/migrate.js +6 -56
  233. package/dist/output/text/proposal-format.js +16 -1
  234. package/dist/output/text/workflow-format.js +16 -0
  235. package/dist/registry/network.js +279 -0
  236. package/dist/registry/pinned-request-helper.js +247 -0
  237. package/dist/registry/pinned-transport.js +717 -0
  238. package/dist/registry/providers/skills-sh.js +18 -6
  239. package/dist/registry/providers/static-index.js +20 -7
  240. package/dist/registry/resolve.js +53 -28
  241. package/dist/scripts/akm-migrate-node.js +19334 -52269
  242. package/dist/scripts/akm-migrate.js +19270 -51612
  243. package/dist/setup/registry-stash-loader.js +64 -20
  244. package/dist/setup/semantic-assets.js +9 -34
  245. package/dist/setup/setup.js +12 -30
  246. package/dist/setup/source-identity.js +17 -0
  247. package/dist/setup/steps/sources.js +36 -15
  248. package/dist/setup/steps/tasks.js +39 -11
  249. package/dist/sources/providers/git-provider.js +3 -3
  250. package/dist/sources/providers/npm.js +2 -2
  251. package/dist/sources/providers/provider-utils.js +4 -3
  252. package/dist/sources/providers/website.js +11 -7
  253. package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
  254. package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
  255. package/dist/sources/website-url.js +73 -0
  256. package/dist/storage/engines/sqlite-migrations.js +81 -26
  257. package/dist/storage/managed-db.js +27 -24
  258. package/dist/storage/repositories/events-repository.js +3 -0
  259. package/dist/storage/repositories/index-connection.js +42 -10
  260. package/dist/storage/repositories/index-entries-repository.js +203 -229
  261. package/dist/storage/repositories/index-entry-mapper.js +8 -12
  262. package/dist/storage/repositories/index-entry-schema.js +255 -0
  263. package/dist/storage/repositories/index-fts-repository.js +64 -71
  264. package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
  265. package/dist/storage/repositories/index-meta-repository.js +0 -11
  266. package/dist/storage/repositories/index-schema.js +74 -350
  267. package/dist/storage/repositories/index-utility-repository.js +12 -17
  268. package/dist/storage/repositories/index-vec-repository.js +56 -7
  269. package/dist/storage/repositories/proposals-repository.js +4 -127
  270. package/dist/storage/repositories/registry-cache.js +2 -1
  271. package/dist/storage/repositories/task-history-repository.js +20 -40
  272. package/dist/storage/repositories/workflow-runs-repository.js +228 -129
  273. package/dist/storage/sqlite-read-snapshot.js +148 -0
  274. package/dist/tasks/backends/cron.js +170 -42
  275. package/dist/tasks/backends/index.js +1 -1
  276. package/dist/tasks/backends/launchd.js +787 -202
  277. package/dist/tasks/backends/schtasks.js +282 -83
  278. package/dist/tasks/embedded.js +7 -7
  279. package/dist/tasks/frozen-script.js +50 -0
  280. package/dist/tasks/resolve-akm-bin.js +5 -1
  281. package/dist/tasks/runner.js +239 -251
  282. package/dist/tasks/runtime-v3.js +281 -0
  283. package/dist/tasks/scheduler-binding.js +272 -0
  284. package/dist/tasks/scheduler-invocation.js +57 -43
  285. package/dist/tasks/scheduler-sync.js +654 -0
  286. package/dist/tasks/source-v3.js +752 -0
  287. package/dist/tasks/standalone-script-entry.js +5 -0
  288. package/dist/tasks/task-id.js +29 -0
  289. package/dist/workflows/authoring/authoring.js +15 -32
  290. package/dist/workflows/exec/dispatch-redaction.js +14 -8
  291. package/dist/workflows/exec/exec-unit.js +7 -28
  292. package/dist/workflows/exec/frozen-judge.js +57 -89
  293. package/dist/workflows/exec/lowering-notices.js +23 -0
  294. package/dist/workflows/exec/native-executor.js +301 -458
  295. package/dist/workflows/exec/param-secrets.js +4 -3
  296. package/dist/workflows/exec/run-workflow.js +26 -32
  297. package/dist/workflows/exec/step-work.js +105 -109
  298. package/dist/workflows/exec/unit-dispatch.js +103 -27
  299. package/dist/workflows/exec/unit-writer.js +3 -3
  300. package/dist/workflows/exec/worktree.js +2 -2
  301. package/dist/workflows/ir/compile.js +86 -72
  302. package/dist/workflows/ir/environment-v4.js +328 -0
  303. package/dist/workflows/ir/freeze-v4.js +122 -0
  304. package/dist/workflows/ir/plan-hash.js +13 -7
  305. package/dist/workflows/ir/schema-v4.js +525 -0
  306. package/dist/workflows/ir/schema.js +25 -284
  307. package/dist/workflows/ir/source-freeze-v4.js +506 -0
  308. package/dist/workflows/parser.js +27 -24
  309. package/dist/workflows/program/schema.js +1 -2
  310. package/dist/workflows/renderer.js +42 -29
  311. package/dist/workflows/resource-limits.js +4 -5
  312. package/dist/workflows/runtime/agent-identity.js +11 -13
  313. package/dist/workflows/runtime/plan-classifier.js +8 -8
  314. package/dist/workflows/runtime/runs.js +27 -43
  315. package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
  316. package/dist/workflows/source-files.js +373 -0
  317. package/dist/workflows/source-ir/compile.js +196 -0
  318. package/dist/workflows/source-ir/github-yaml.js +577 -0
  319. package/dist/workflows/source-ir/ordering.js +38 -0
  320. package/dist/workflows/source-ir/program.js +50 -0
  321. package/dist/workflows/source-ir/result.js +26 -0
  322. package/dist/workflows/source-ir/schema.js +772 -0
  323. package/dist/workflows/source-ir/semantics.js +242 -0
  324. package/dist/workflows/source-ir/uses.js +14 -0
  325. package/docs/README.md +2 -0
  326. package/docs/migration/README.md +3 -1
  327. package/docs/migration/release-notes/0.9.2.md +55 -0
  328. package/docs/migration/release-notes/README.md +5 -0
  329. package/docs/migration/v0.8-to-v0.9.md +76 -1077
  330. package/docs/migration/v0.9.0-troubleshooting.md +104 -516
  331. package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
  332. package/docs/reference/README.md +1 -0
  333. package/docs/reference/cli.md +230 -98
  334. package/docs/reference/configuration.md +159 -36
  335. package/docs/reference/data-and-telemetry.md +19 -1
  336. package/docs/reference/supported-formats.md +23 -3
  337. package/docs/reference/tasks.md +182 -0
  338. package/docs/reference/workflow-schema.md +91 -40
  339. package/docs/reference/workflows.md +33 -6
  340. package/package.json +10 -6
  341. package/schemas/akm-config.json +372 -224
  342. package/schemas/akm-task.json +324 -80
  343. package/schemas/akm-workflow.json +6 -9
  344. package/dist/core/migration-operation.js +0 -75
  345. package/dist/integrations/agent/model-aliases.js +0 -74
  346. package/dist/tasks/parser.js +0 -380
  347. package/dist/tasks/schema.js +0 -123
  348. package/dist/tasks/validator.js +0 -80
  349. package/dist/workflows/ir/freeze.js +0 -320
  350. package/dist/workflows/runtime/document-cache.js +0 -13
@@ -1,16 +1,12 @@
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 fs from "node:fs";
5
- import path from "node:path";
6
- import * as p from "../../cli/clack.js";
7
4
  import { getStringArg, parsePositiveIntFlag } from "../../cli/parse-args.js";
8
5
  import { defineJsonCommand, output } from "../../cli/shared.js";
9
- import { decideDangerousKeyInstall } from "../../core/activation-policy.js";
10
6
  import { UsageError } from "../../core/errors.js";
11
7
  import { appendEvent } from "../../core/events.js";
12
8
  import { warn } from "../../core/warn.js";
13
- import { sanitizeString } from "../../sources/providers/provider-utils.js";
9
+ import { auditStashForDangerousKeys } from "./dangerous-env-audit.js";
14
10
  import { akmRemove } from "./installed-stashes.js";
15
11
  import { akmAdd } from "./source-add.js";
16
12
  import { addStash } from "./source-manage.js";
@@ -39,185 +35,30 @@ export function shouldWarnOnPlainHttp(ref) {
39
35
  return true;
40
36
  }
41
37
  }
42
- /**
43
- * Recursively enumerate every real env FILE under `rootDir` (a stash's
44
- * `env/` directory). "Real env file" is the SAME test used everywhere else
45
- * in akm decides what `akm env run` / `akm env list` / the indexer will
46
- * actually load as environment variables — `fileName === ".env" ||
47
- * fileName.endsWith(".env")` — see `src/indexer/walk/matchers.ts` and
48
- * `listEnvsRecursive` in `src/commands/env/env-cli.ts`. Reusing that exact
49
- * rule here (rather than inventing a second, possibly-divergent one) is what
50
- * closes the nested-directory bypass: `env/nested/inner.env` IS loaded by
51
- * `akm env run` today via that same recursive walk, so it must be scanned
52
- * too (previously only a flat, non-recursive `env/*.env` listing was
53
- * scanned).
54
- *
55
- * Deliberately excludes files that do NOT end in `.env` (e.g.
56
- * `env/notes.txt`). Such a file is never sourced as environment variables by
57
- * any akm codepath, so a dangerous key sitting in its contents cannot hijack
58
- * process execution via `akm env run` — there is no live attack through this
59
- * gate for it. Residual gap: this scanner does not protect against an
60
- * operator later renaming/copying that file to a `*.env` name, or feeding it
61
- * into `akm env run` through some other mechanism outside akm; closing that
62
- * would mean content-sniffing every byte of every file in the stash (README,
63
- * lockfiles, binaries, …) for a vector that requires a subsequent human
64
- * action to become live, which is a materially different and much more
65
- * expensive check than "is this a file `akm` will actually source".
66
- */
67
- function collectEnvFilePathsRecursive(rootDir) {
68
- const results = [];
69
- const walk = (dir) => {
70
- let entries;
71
- try {
72
- entries = fs.readdirSync(dir, { withFileTypes: true });
73
- }
74
- catch {
75
- return;
76
- }
77
- for (const entry of entries) {
78
- const full = path.join(dir, entry.name);
79
- if (entry.isDirectory()) {
80
- walk(full);
81
- continue;
82
- }
83
- if (!entry.isFile())
84
- continue;
85
- if (entry.name !== ".env" && !entry.name.endsWith(".env"))
86
- continue;
87
- results.push(full);
88
- }
89
- };
90
- walk(rootDir);
91
- return results;
92
- }
93
- /** Scan every real env file (recursively, see {@link collectEnvFilePathsRecursive}) in the freshly-installed stash for dangerous env keys. */
94
- function collectDangerousKeyFindings(installedStashRoot, checkEnvForDangerousKeys) {
95
- const allFindings = [];
96
- const subdir = "env";
97
- const prefix = "env";
98
- const dir = path.join(installedStashRoot, subdir);
99
- if (!fs.existsSync(dir))
100
- return allFindings;
101
- for (const envPath of collectEnvFilePathsRecursive(dir)) {
102
- const relToEnvDir = path.relative(dir, envPath);
103
- // Only the LAST path segment carries the ".env" suffix / dotfile-default
104
- // naming rule; intermediate directory segments pass through unchanged.
105
- // e.g. "nested/inner.env" -> "nested/inner", "nested/.env" ->
106
- // "nested/default" (not "nested/", which is what naively stripping the
107
- // suffix from the whole relative path would produce).
108
- const segments = relToEnvDir.split(path.sep);
109
- const lastSegment = segments[segments.length - 1] ?? "";
110
- const lastSegmentBase = lastSegment.endsWith(".env") ? lastSegment.slice(0, -".env".length) : lastSegment;
111
- const refSegments = [...segments.slice(0, -1), lastSegmentBase === "" ? "default" : lastSegmentBase];
112
- const envRef = `${prefix}/${refSegments.join("/")}`;
113
- const relPath = path.join(subdir, relToEnvDir);
114
- const findings = checkEnvForDangerousKeys(envPath, relPath, envRef);
115
- for (const finding of findings) {
116
- // Extract the key name from the detail string for the summary line.
117
- const keyMatch = finding.detail.match(/Env key `([^`]+)`/);
118
- const keyName = keyMatch ? keyMatch[1] : finding.file;
119
- allFindings.push({ envRef, keyName, relPath });
120
- }
121
- }
122
- return allFindings;
123
- }
124
38
  /**
125
39
  * Audit a freshly-installed stash for dangerous env keys and decide whether the
126
- * install must be blocked. Returns a typed decision instead of calling
127
- * `process.exit`, so the abort cannot be lost to a swallowed exception. See the
128
- * block comment above for the security rationale.
40
+ * install must be blocked. The scanner and decision policy are shared with
41
+ * `bundle update` in dangerous-env-audit.ts.
129
42
  */
130
43
  export async function auditInstalledStashForDangerousKeys(opts) {
131
44
  const { installedStashRoot, ref, allowDangerousKeys, rollbackTarget, isTTY } = opts;
132
- // Best-effort scan: if collecting findings itself throws (corrupt env file,
133
- // fs error) there is nothing concrete to block on, so fail soft. Crucially,
134
- // this soft path runs BEFORE any findings exist — it can never re-open an
135
- // already-detected dangerous install.
136
- let allFindings;
137
- try {
138
- const { checkEnvForDangerousKeys } = await import("../lint/env-key-rules.js");
139
- allFindings = collectDangerousKeyFindings(installedStashRoot, checkEnvForDangerousKeys);
140
- }
141
- catch {
142
- return { blocked: false };
143
- }
144
- // The workspace activation policy fixes the baseline stance; the interactive
145
- // confirm / rollback below is layered on top of the `"gate"` stance only.
146
- const stance = decideDangerousKeyInstall({
147
- findingsPresent: allFindings.length > 0,
148
- allowInsecure: allowDangerousKeys,
149
- });
150
- if (stance === "allow")
151
- return { blocked: false };
152
- if (stance === "warn-allow") {
153
- // Operator has explicitly accepted the risk — warn and continue.
154
- for (const f of allFindings) {
155
- warn(`[dangerous-env-key] ${f.relPath}: key \`${f.keyName}\` in ${f.envRef} can hijack process execution via \`akm env run\`. Proceeding because --allow-insecure was set.`);
156
- }
157
- return { blocked: false };
158
- }
159
- // Helper: roll the install back before aborting. Rollback is best-effort; a
160
- // failed rollback never UN-blocks the install — we still abort, just with a
161
- // warning telling the operator to remove the stash manually.
162
- async function rollback() {
163
- try {
164
- await akmRemove({ target: rollbackTarget });
165
- return undefined;
166
- }
167
- catch (_rollbackErr) {
168
- return (`Rollback failed — stash may still be installed at ${installedStashRoot}. ` +
169
- `Remove it manually with: akm bundle remove ${rollbackTarget}`);
170
- }
171
- }
172
- if (isTTY) {
173
- // Interactive path: show findings and ask the user to confirm.
174
- // Guard on stdin (not stdout) because p.confirm() reads from stdin;
175
- // stdout may be a TTY while stdin is piped, which would cause a hang.
176
- const stashLabel = ref;
177
- const groupedByEnv = new Map();
178
- for (const f of allFindings) {
179
- const existing = groupedByEnv.get(f.envRef) ?? [];
180
- existing.push(f.keyName);
181
- groupedByEnv.set(f.envRef, existing);
182
- }
183
- for (const [envRef, keys] of groupedByEnv) {
184
- // envRef and keys come from filenames and KEY names inside a downloaded or
185
- // cloned bundle, i.e. attacker-controllable. Tar validation rejects NUL but
186
- // not ESC/CSI, and git checkout allows them in filenames on Linux/macOS —
187
- // so printing them raw let a crafted bundle rewrite this security prompt
188
- // with terminal escapes right before an "Install anyway?" confirmation.
189
- warn(`[warn] Env "${sanitizeString(envRef)}" in stash "${sanitizeString(stashLabel)}" contains potentially dangerous keys:`);
190
- for (const key of keys) {
191
- warn(` - ${sanitizeString(key)}: can hijack process execution via \`akm env run\``);
45
+ return auditStashForDangerousKeys({
46
+ stashRoot: installedStashRoot,
47
+ ref,
48
+ allowDangerousKeys,
49
+ isTTY,
50
+ operation: "install",
51
+ rollback: async () => {
52
+ try {
53
+ await akmRemove({ target: rollbackTarget });
54
+ return undefined;
192
55
  }
193
- }
194
- const confirmed = await p.confirm({
195
- message: "Install anyway?",
196
- initialValue: false,
197
- });
198
- if (p.isCancel(confirmed) || confirmed !== true) {
199
- const rollbackWarning = await rollback();
200
- console.error(JSON.stringify({
201
- ok: false,
202
- error: "Install aborted: stash contains dangerous env keys. Remove the keys or re-run with --allow-insecure to bypass.",
203
- code: "DANGEROUS_ENV_KEY",
204
- ...(rollbackWarning ? { rollbackWarning } : {}),
205
- }, null, 2));
206
- return { blocked: true, exitCode: 1 };
207
- }
208
- // Operator confirmed at the prompt — allow the install to proceed.
209
- return { blocked: false };
210
- }
211
- // Non-interactive path without bypass flag: fail hard.
212
- const rollbackWarning = await rollback();
213
- const keyList = allFindings.map((f) => ` - ${f.keyName} (${f.envRef})`).join("\n");
214
- console.error(JSON.stringify({
215
- ok: false,
216
- error: `Install blocked: stash "${ref}" contains dangerous env keys that can hijack process execution via \`akm env run\`:\n${keyList}\nRe-run with --allow-insecure to bypass this check after reviewing the env file.`,
217
- code: "DANGEROUS_ENV_KEY",
218
- ...(rollbackWarning ? { rollbackWarning } : {}),
219
- }, null, 2));
220
- return { blocked: true, exitCode: 1 };
56
+ catch {
57
+ return (`Rollback failed stash may still be installed at ${installedStashRoot}. ` +
58
+ `Remove it manually with: akm bundle remove ${rollbackTarget}`);
59
+ }
60
+ },
61
+ });
221
62
  }
222
63
  // ── Command definition ────────────────────────────────────────────────────────
223
64
  export const addCommand = defineJsonCommand({
@@ -118,11 +118,16 @@ const removeCommand = defineJsonCommand({
118
118
  },
119
119
  });
120
120
  const updateCommand = defineJsonCommand({
121
- meta: { name: "update", description: "Update one or all managed bundles" },
121
+ meta: { name: "update", description: "Refresh one or all configured bundles and reconcile their index" },
122
122
  args: {
123
123
  target: { type: "positional", description: "Bundle to update (id or ref)", required: false },
124
- all: { type: "boolean", description: "Update all installed entries", default: false },
124
+ all: { type: "boolean", description: "Update all configured bundles and report each outcome", default: false },
125
125
  force: { type: "boolean", description: "Force fresh download even if version is unchanged", default: false },
126
+ "allow-insecure": {
127
+ type: "boolean",
128
+ description: "Allow an update containing dangerous env keys (e.g. LD_PRELOAD, PATH). Use only after explicitly reviewing the staged bundle.",
129
+ default: false,
130
+ },
126
131
  // F1/R-058: gates ONLY the rare branch where the resolved content
127
132
  // directory moves and the previous `localRoot` is deleted — a normal
128
133
  // refresh (the overwhelming majority of updates) deletes nothing and
@@ -135,13 +140,20 @@ const updateCommand = defineJsonCommand({
135
140
  },
136
141
  },
137
142
  async run({ args }) {
138
- const result = await akmUpdate({ target: args.target, all: args.all, force: args.force, yes: args.yes });
143
+ const result = await akmUpdate({
144
+ target: args.target,
145
+ all: args.all,
146
+ force: args.force,
147
+ yes: args.yes,
148
+ allowInsecure: args["allow-insecure"],
149
+ });
139
150
  appendEvent({
140
151
  eventType: "update",
141
152
  metadata: {
142
153
  target: args.target ?? null,
143
154
  all: args.all === true,
144
155
  force: args.force === true,
156
+ allowInsecure: args["allow-insecure"] === true,
145
157
  processed: Array.isArray(result.processed)
146
158
  ? result.processed.length
147
159
  : 0,
@@ -0,0 +1,135 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import * as p from "../../cli/clack.js";
7
+ import { decideDangerousKeyInstall } from "../../core/activation-policy.js";
8
+ import { ConfigError } from "../../core/errors.js";
9
+ import { warn } from "../../core/warn.js";
10
+ import { sanitizeString } from "../../sources/providers/provider-utils.js";
11
+ import { scanEnvKeyNames } from "../env/env.js";
12
+ import { isDangerousEnvKey } from "../lint/env-key-rules.js";
13
+ let scannerOverride;
14
+ /** TEST-ONLY. Inject a scanner fault at the audit boundary; undefined restores. */
15
+ export function _setDangerousKeyScannerForTests(scanner) {
16
+ scannerOverride = scanner;
17
+ }
18
+ function collectEnvFilePathsRecursive(rootDir) {
19
+ const results = [];
20
+ const walk = (dir) => {
21
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
22
+ for (const entry of entries) {
23
+ const full = path.join(dir, entry.name);
24
+ if (entry.isDirectory()) {
25
+ walk(full);
26
+ continue;
27
+ }
28
+ if (!entry.isFile())
29
+ continue;
30
+ if (entry.name !== ".env" && !entry.name.endsWith(".env"))
31
+ continue;
32
+ results.push(full);
33
+ }
34
+ };
35
+ walk(rootDir);
36
+ return results;
37
+ }
38
+ function collectDangerousKeyFindings(installedStashRoot, checkEnvForDangerousKeys) {
39
+ const allFindings = [];
40
+ if (!fs.statSync(installedStashRoot).isDirectory()) {
41
+ throw new Error(`Expected ${installedStashRoot} to be a directory.`);
42
+ }
43
+ const dir = path.join(installedStashRoot, "env");
44
+ try {
45
+ if (!fs.statSync(dir).isDirectory()) {
46
+ throw new Error(`Expected ${dir} to be a directory.`);
47
+ }
48
+ }
49
+ catch (error) {
50
+ if (error.code === "ENOENT")
51
+ return allFindings;
52
+ throw error;
53
+ }
54
+ for (const envPath of collectEnvFilePathsRecursive(dir)) {
55
+ const relToEnvDir = path.relative(dir, envPath);
56
+ const segments = relToEnvDir.split(path.sep);
57
+ const lastSegment = segments[segments.length - 1] ?? "";
58
+ const lastSegmentBase = lastSegment.endsWith(".env") ? lastSegment.slice(0, -".env".length) : lastSegment;
59
+ const refSegments = [...segments.slice(0, -1), lastSegmentBase === "" ? "default" : lastSegmentBase];
60
+ const envRef = `env/${refSegments.join("/")}`;
61
+ const relPath = path.join("env", relToEnvDir);
62
+ for (const finding of checkEnvForDangerousKeys(envPath, relPath, envRef)) {
63
+ const keyMatch = finding.detail.match(/Env key `([^`]+)`/);
64
+ allFindings.push({ envRef, keyName: keyMatch?.[1] ?? finding.file, relPath });
65
+ }
66
+ }
67
+ return allFindings;
68
+ }
69
+ /** Scan only; callers that already obtained approval use this for a final materialization fence. */
70
+ export function scanStashForDangerousKeys(installedStashRoot) {
71
+ const scanner = scannerOverride ??
72
+ ((envPath, relPath) => scanEnvKeyNames(fs.readFileSync(envPath, "utf8"))
73
+ .filter(isDangerousEnvKey)
74
+ .map((key) => ({ file: relPath, detail: `Env key \`${key}\`` })));
75
+ return collectDangerousKeyFindings(installedStashRoot, scanner);
76
+ }
77
+ export async function auditStashForDangerousKeys(opts) {
78
+ let allFindings;
79
+ try {
80
+ allFindings = scanStashForDangerousKeys(opts.stashRoot);
81
+ }
82
+ catch (error) {
83
+ const rollbackWarning = await opts.rollback?.();
84
+ throw new ConfigError(`Dangerous environment-key audit failed for "${opts.ref}"; refusing to ${opts.operation} unverified content. ${error instanceof Error ? error.message : String(error)}${rollbackWarning ? ` ${rollbackWarning}` : ""}`, "DANGEROUS_ENV_AUDIT_FAILED");
85
+ }
86
+ const stance = decideDangerousKeyInstall({
87
+ findingsPresent: allFindings.length > 0,
88
+ allowInsecure: opts.allowDangerousKeys,
89
+ });
90
+ if (stance === "allow")
91
+ return { blocked: false, findings: allFindings };
92
+ if (stance === "warn-allow") {
93
+ for (const finding of allFindings) {
94
+ warn(`[dangerous-env-key] ${finding.relPath}: key \`${finding.keyName}\` in ${finding.envRef} can hijack process execution via \`akm env run\`. Proceeding because --allow-insecure was set.`);
95
+ }
96
+ return { blocked: false, findings: allFindings };
97
+ }
98
+ const operationTitle = opts.operation === "install" ? "Install" : "Update";
99
+ const operationPast = opts.operation === "install" ? "installed" : "updated";
100
+ let error;
101
+ if (opts.isTTY) {
102
+ const groupedByEnv = new Map();
103
+ for (const finding of allFindings) {
104
+ const existing = groupedByEnv.get(finding.envRef) ?? [];
105
+ existing.push(finding.keyName);
106
+ groupedByEnv.set(finding.envRef, existing);
107
+ }
108
+ for (const [envRef, keys] of groupedByEnv) {
109
+ warn(`[warn] Env "${sanitizeString(envRef)}" in stash "${sanitizeString(opts.ref)}" contains potentially dangerous keys:`);
110
+ for (const key of keys)
111
+ warn(` - ${sanitizeString(key)}: can hijack process execution via \`akm env run\``);
112
+ }
113
+ const confirmed = await p.confirm({ message: `${operationTitle} anyway?`, initialValue: false });
114
+ if (!p.isCancel(confirmed) && confirmed === true)
115
+ return { blocked: false, findings: allFindings };
116
+ error = `${operationTitle} aborted: stash contains dangerous env keys. Remove the keys or re-run with --allow-insecure to bypass.`;
117
+ }
118
+ else {
119
+ const keyList = allFindings.map((finding) => ` - ${finding.keyName} (${finding.envRef})`).join("\n");
120
+ error = `${operationTitle} blocked: stash "${opts.ref}" contains dangerous env keys that can hijack process execution via \`akm env run\`:\n${keyList}\nRe-run with --allow-insecure to bypass this check after reviewing the env file.`;
121
+ }
122
+ const rollbackWarning = await opts.rollback?.();
123
+ if (opts.renderBlockedError !== false) {
124
+ console.error(JSON.stringify({
125
+ ok: false,
126
+ error,
127
+ code: "DANGEROUS_ENV_KEY",
128
+ ...(rollbackWarning ? { rollbackWarning } : {}),
129
+ }, null, 2));
130
+ }
131
+ if (rollbackWarning) {
132
+ warn(`The blocked ${opts.operation} could not be fully rolled back after content was ${operationPast}: ${rollbackWarning}`);
133
+ }
134
+ return { blocked: true, exitCode: 1, code: "DANGEROUS_ENV_KEY", error, findings: allFindings };
135
+ }
@@ -6,6 +6,7 @@ import { resolveStashDir } from "../../core/common.js";
6
6
  import { getSources, loadConfig } from "../../core/config/config.js";
7
7
  import { classifyPathAccess, describeInaccessiblePath } from "../../core/path-access.js";
8
8
  import { getDbPath } from "../../core/paths.js";
9
+ import { formatRegistryUrl } from "../../core/registry-url.js";
9
10
  import { error } from "../../core/warn.js";
10
11
  import { getEffectiveSemanticStatus, readSemanticStatus } from "../../indexer/search/semantic-status.js";
11
12
  import { closeDatabase, openExistingDatabase } from "../../storage/repositories/index-connection.js";
@@ -37,7 +38,7 @@ export function assembleInfo(options) {
37
38
  }
38
39
  // Registries (strip sensitive fields like apiKey from options)
39
40
  const registries = (config.registries ?? []).map((r) => ({
40
- url: r.url,
41
+ url: formatRegistryUrl(r.url),
41
42
  ...(r.name ? { name: r.name } : {}),
42
43
  ...(r.provider ? { provider: r.provider } : {}),
43
44
  ...(r.enabled !== undefined ? { enabled: r.enabled } : {}),