akm-cli 0.10.261012-beta → 0.10.261013-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 (146) hide show
  1. package/CHANGELOG.md +216 -0
  2. package/SECURITY.md +2 -2
  3. package/STABILITY.md +2 -2
  4. package/dist/assets/hints/cli-hints-full.md +6 -7
  5. package/dist/assets/hints/cli-hints-short.md +3 -4
  6. package/dist/commands/command/execution-source-loader.js +10 -14
  7. package/dist/commands/env/env-binding.js +47 -58
  8. package/dist/commands/env/env-cli.js +20 -22
  9. package/dist/commands/env/env.js +10 -58
  10. package/dist/commands/env/secret-cli.js +9 -27
  11. package/dist/commands/env/secret.js +3 -10
  12. package/dist/commands/health/scheduler-binary.js +1 -1
  13. package/dist/commands/improve/distill.js +0 -2
  14. package/dist/commands/improve/eligibility.js +0 -2
  15. package/dist/commands/lint/env-key-rules.js +7 -6
  16. package/dist/commands/lint/index.js +1 -1
  17. package/dist/commands/proposal/validators/proposal-validators.js +1 -4
  18. package/dist/commands/read/show.js +4 -25
  19. package/dist/commands/sources/add-cli.js +8 -63
  20. package/dist/commands/sources/bundle-cli.js +8 -9
  21. package/dist/commands/sources/installed-stashes.js +117 -61
  22. package/dist/commands/sources/schema-repair.js +0 -2
  23. package/dist/commands/sources/source-clone.js +31 -7
  24. package/dist/commands/tasks/explain.js +1 -1
  25. package/dist/commands/tasks/tasks.js +38 -56
  26. package/dist/commands/ui/redact.js +4 -4
  27. package/dist/commands/ui/workspace-actions.js +1 -1
  28. package/dist/commands/workflow/plan.js +47 -8
  29. package/dist/commands/workflow-cli.js +37 -41
  30. package/dist/core/adapter/adapters/akm-adapter.js +3 -3
  31. package/dist/core/adapter/adapters/akm-lint.js +3 -4
  32. package/dist/core/adapter/adapters/akm-metadata.js +2 -2
  33. package/dist/core/adapter/adapters/akm-task-adapter.js +5 -6
  34. package/dist/core/adapter/adapters/dotenv-adapter.js +4 -3
  35. package/dist/core/asset/resolve-ref.js +5 -0
  36. package/dist/core/bundle-rename.js +12 -4
  37. package/dist/core/common.js +4 -52
  38. package/dist/core/config/config-walker.js +1 -1
  39. package/dist/core/config/config.js +55 -18
  40. package/dist/core/config/schema/primitives.js +12 -6
  41. package/dist/core/config/schema/sources-bundles.js +2 -2
  42. package/dist/core/env-secret-ref.js +84 -73
  43. package/dist/core/errors.js +1 -1
  44. package/dist/core/paths.js +1 -6
  45. package/dist/core/redaction.js +16 -3
  46. package/dist/core/ref-token.js +82 -0
  47. package/dist/core/secret-value.js +14 -0
  48. package/dist/core/write-source.js +20 -4
  49. package/dist/indexer/indexer.js +27 -36
  50. package/dist/indexer/installations.js +1 -8
  51. package/dist/indexer/lookup/adapter-concept-owner.js +40 -14
  52. package/dist/indexer/materialize-embeddings.js +1 -1
  53. package/dist/indexer/scan/drain-dir.js +3 -3
  54. package/dist/indexer/search/search-source.js +4 -2
  55. package/dist/indexer/walk/path-resolver.js +13 -68
  56. package/dist/indexer/walk/walker.js +18 -22
  57. package/dist/integrations/agent/engine-resolution.js +9 -7
  58. package/dist/integrations/agent/runner-dispatch.js +4 -5
  59. package/dist/integrations/agent/runner.js +2 -0
  60. package/dist/llm/client.js +1 -1
  61. package/dist/llm/embedders/remote.js +4 -3
  62. package/dist/output/renderers.js +3 -4
  63. package/dist/output/shapes/helpers.js +0 -2
  64. package/dist/output/text/command-format.js +12 -2
  65. package/dist/output/text/show-directives.js +16 -28
  66. package/dist/output/text/workflow-format.js +37 -7
  67. package/dist/scripts/akm-migrate-node.js +45865 -44692
  68. package/dist/scripts/akm-migrate.js +45804 -44630
  69. package/dist/setup/steps/tasks.js +1 -2
  70. package/dist/sources/providers/provider-utils.js +12 -0
  71. package/dist/sources/resolve.js +12 -16
  72. package/dist/sources/snapshot-fetchers/secret-seam.js +2 -4
  73. package/dist/sources/snapshot-fetchers/x.js +1 -1
  74. package/dist/storage/repositories/proposals-repository.js +2 -2
  75. package/dist/storage/repositories/task-history-repository.js +2 -2
  76. package/dist/storage/repositories/workflow-runs-repository.js +38 -116
  77. package/dist/tasks/embedded.js +1 -1
  78. package/dist/tasks/prepare/prepare-support.js +3 -2
  79. package/dist/tasks/prepare/prepare.js +3 -4
  80. package/dist/tasks/prepare/script-capture.js +8 -22
  81. package/dist/tasks/run/load-task.js +46 -14
  82. package/dist/tasks/run/run-native-task.js +0 -8
  83. package/dist/tasks/run/run-workflow-task.js +2 -2
  84. package/dist/tasks/scheduler-sync.js +0 -1
  85. package/dist/tasks/source/bounded-document.js +0 -41
  86. package/dist/tasks/source/parse-task-source.js +0 -1
  87. package/dist/tasks/source/task-source-v4.js +1 -4
  88. package/dist/workflows/compile.js +1 -15
  89. package/dist/workflows/exec/child-workflow.js +1 -2
  90. package/dist/workflows/exec/dispatch-redaction.js +24 -6
  91. package/dist/workflows/exec/environment.js +11 -74
  92. package/dist/workflows/exec/exec-unit.js +31 -23
  93. package/dist/workflows/exec/frozen-judge.js +17 -3
  94. package/dist/workflows/exec/native-executor.js +70 -26
  95. package/dist/workflows/exec/run-workflow.js +31 -18
  96. package/dist/workflows/exec/step-work.js +4 -3
  97. package/dist/workflows/exec/unit-dispatch.js +1 -8
  98. package/dist/workflows/freeze/environment.js +14 -119
  99. package/dist/workflows/freeze/freeze.js +0 -1
  100. package/dist/workflows/freeze/step-values.js +1 -2
  101. package/dist/workflows/freeze/targets/child-workflow.js +3 -5
  102. package/dist/workflows/freeze/targets/command.js +6 -9
  103. package/dist/workflows/freeze/targets/shell.js +0 -3
  104. package/dist/workflows/parser.js +6 -25
  105. package/dist/workflows/plan.js +6 -2
  106. package/dist/workflows/renderer.js +77 -4
  107. package/dist/workflows/runtime/run-plan.js +37 -23
  108. package/dist/workflows/runtime/runs.js +41 -84
  109. package/dist/workflows/runtime/workflow-asset-loader.js +55 -86
  110. package/dist/workflows/source-files.js +7 -19
  111. package/docs/architecture/0.10-process-rules.md +6 -6
  112. package/docs/architecture/architecture.md +6 -6
  113. package/docs/architecture/comparisons/claude-code-vs-akm-workflows-full.md +12 -18
  114. package/docs/architecture/decisions/0003-child-env-allowlist-and-provenance.md +6 -8
  115. package/docs/architecture/internals/storage-locations.md +4 -4
  116. package/docs/architecture/persisted-data-compat.md +3 -2
  117. package/docs/architecture/specs/0.9.0-public-api-issue-backlog.md +1 -1
  118. package/docs/architecture/specs/akm-format-neutral-bundle-workspace-spec.md +3 -3
  119. package/docs/architecture/testing/manual-testing-checklist.md +46 -78
  120. package/docs/architecture/testing/task-workflow-manual-tests.md +22 -40
  121. package/docs/architecture/testing/testing-workflow.md +9 -11
  122. package/docs/architecture/workflow-engine.md +41 -27
  123. package/docs/guides/author-bundles.md +14 -17
  124. package/docs/guides/author-workflows.md +43 -9
  125. package/docs/guides/bundles.md +16 -11
  126. package/docs/guides/claude-code-vs-akm-workflows.md +1 -1
  127. package/docs/guides/run-workflows.md +25 -13
  128. package/docs/guides/scheduling.md +1 -1
  129. package/docs/maintainers/README.md +2 -3
  130. package/docs/maintainers/release-checklist.md +73 -126
  131. package/docs/migration/v0.9-to-v0.10.md +5 -1
  132. package/docs/reference/cli.md +124 -112
  133. package/docs/reference/configuration.md +16 -9
  134. package/docs/reference/data-and-telemetry.md +1 -1
  135. package/docs/reference/env-and-secrets.md +66 -15
  136. package/docs/reference/registry.md +6 -11
  137. package/docs/reference/tasks.md +11 -3
  138. package/docs/reference/ui-api.md +3 -3
  139. package/docs/reference/workflow-schema.md +109 -36
  140. package/package.json +1 -1
  141. package/schemas/akm-task.json +2 -4
  142. package/dist/commands/sources/dangerous-env-audit.js +0 -134
  143. package/dist/core/activation-policy.js +0 -67
  144. package/dist/execution/directory-identity.js +0 -78
  145. package/dist/workflows/authoring/scope-key.js +0 -79
  146. package/dist/workflows/source-semantics.js +0 -110
package/CHANGELOG.md CHANGED
@@ -6,6 +6,222 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.10.261013] - 2026-10-23
10
+
11
+ ### Fixed
12
+
13
+ - A secret is now also redacted when it appears in its JSON-escaped spelling
14
+ (#1238). Text redaction matched only the exact and URL-encoded forms, so a child
15
+ that printed `JSON.stringify({ token })` for a secret with a quote, backslash,
16
+ newline or non-ASCII character left it in the unit journal when the output failed
17
+ its schema, had no schema, or came with a non-zero exit. The shared redactor now
18
+ also matches the `\"`, `\\`, `\n` and `\uXXXX` forms (lowercase or uppercase
19
+ hex, DEL included), for workflow, engine and task-output redaction alike.
20
+ - `akm bundle rename` now rewrites the refs of a bundle whose id contains an
21
+ emoji or other character outside the Basic Multilingual Plane correctly
22
+ (#1239). It used to cut into the `//` separator, storing `team/workflows/x`
23
+ for `💼//workflows/x` and stranding the bundle's workflow runs, task history
24
+ and proposals.
25
+ - A task's `uses:` script or workflow in an npm, website or read-only bundle now
26
+ resolves (#1238). It used to fail with "unsupported kind for writes", because
27
+ the lookup went through the write-target resolver. An unknown bundle in such a
28
+ ref is reported as not configured, without naming a flag.
29
+ - `akm bundle update` no longer re-clones a git or npm bundle whose upstream
30
+ revision has not changed. Re-cloning replaced the bundle's directory under any
31
+ running task or workflow, and left an empty revision directory behind after
32
+ each real change. An unchanged bundle is now reported as unchanged and left
33
+ alone (`--force` still fetches it again). After a real change, the previous
34
+ revision is kept and older revision directories are removed (#1235).
35
+ - Scheduled tasks in the `AKM_BUNDLE_DIR` working stash were silently dropped by
36
+ an unscoped `akm task sync` (and `--rebind`) once any other bundle was
37
+ configured (#1236). Task sync now uses the same set of active bundles as
38
+ search, show and workflows, so the working stash is synced the same way
39
+ whether you sync everything or name it with `--bundle`.
40
+ - A `bundle update` while a workflow is running no longer fails the run (#1238,
41
+ #1235). A running workflow used to remember the directory it was started in, so
42
+ once the update moved the bundle to a new revision directory, the next step
43
+ failed with `PATH_ESCAPE_VIOLATION` and the only remedy was `abandon`. The
44
+ run now finds its bundle again at each step and carries on from the new
45
+ directory. It continues from its frozen plan even if the new revision renamed
46
+ or removed the workflow file, and a step fails only when the run's bundle is
47
+ no longer configured.
48
+ - `exec.cwd` is applied once (#1238). `cwd: sub` used to run in `<bundle>/sub/sub`.
49
+ It now runs in `<bundle>/sub`.
50
+
51
+ ### Changed
52
+
53
+ - `akm workflow plan --format text` now shows each step's argv, cwd and env refs,
54
+ and the bundle-qualified ref that every `${ref}` token, `env:` entry and child
55
+ `workflow:` resolves to; a ref no bundle owns, in the workflow or in a child
56
+ workflow it runs, fails the plan and names it.
57
+ `akm workflow status <id> --units` shows the same argv, cwd and resolved refs
58
+ for a failed unit (#1238).
59
+ - A workflow exec step's command now receives `AKM_CONFIG_DIR`, `AKM_DATA_DIR`,
60
+ `AKM_CACHE_DIR` and `AKM_STATE_DIR` set to the directories the akm that started
61
+ it resolved, including ones it found through `XDG_*_HOME`, and `AKM_BUNDLE_DIR`
62
+ when it had one. A step that runs `akm` uses the same bundle and state without
63
+ `pass_env` (#1238).
64
+ - `akm env`, `akm secret`, workflow `env:` bindings and child workflows, task
65
+ `uses:` assets, and `improve`/`distill`/schema-repair file lookups now find an
66
+ asset with the same lookup `akm show` uses, instead of each keeping its own
67
+ copy of it (#1238). The visible differences: a missing child workflow fails
68
+ with the ordinary "Workflow not found" error, an env ref in a workflow does
69
+ not resolve into a disabled bundle, and `improve` finds a concept in a
70
+ bundle whose adapter names the file (such as an OKF table) from disk as well
71
+ as from the index.
72
+ - An asset is addressed by its akm ref and nothing else (#1238). `akm secret
73
+ set|run` and `akm env create|run|path|export|remove` now take a full ref
74
+ (`secrets/gitea_token`, `team//env/prod`); a bare name such as `gitea_token`
75
+ or `prod` is a usage error that names the full form. `akm env create prod` is
76
+ now `akm env create env/prod`.
77
+ - One substitution token, `${ref}`, replaces `${secret:NAME}` in env values and
78
+ `secret://name` in config credentials (#1238). A `${secrets/name}` token is
79
+ replaced with the secret's value and any other asset ref, such as
80
+ `${scripts/lab/fetch.sh}`, with its absolute path; the ref may name any bundle
81
+ (`${team//secrets/discord_bot_token}`) rather than only the env file's own
82
+ bundle. A ref into a bundle of any adapter works when it is qualified and the
83
+ bundle is installed (`${docs//tables/customers}` in an OKF bundle). A `${...}`
84
+ body that is not an akm ref, such as `${HOME}` or shell substitution like
85
+ `${v//-/_}`, is left as text. `engines.*.apiKey`, `embedding.apiKey`,
86
+ `search.curateRerank.apiKey` and a git bundle's `credential` accept `$VAR`,
87
+ `${VAR}` or `${secrets/<name>}` (in any bundle, such as `${team//secrets/x}`); a
88
+ `${...}` naming any other kind of asset, such as `${scripts/x}`, is rejected
89
+ when the config loads. An env file that still holds `${secret:NAME}` keeps
90
+ working: akm reads it as `${<the env file's bundle>//secrets/NAME}` and warns
91
+ once, naming the env ref. The file is not rewritten, so write the full ref.
92
+ - A workflow unit's `env:` bindings are now read at dispatch with the same
93
+ function `akm env run` uses, so a bundle's env asset, its `${ref}` tokens and
94
+ the secrets they name behave the same in a workflow as on the command line
95
+ (#1238). An edit to an env file after a run started is picked up by the next
96
+ dispatch instead of failing with "key set changed".
97
+ - Only secret values are redacted from workflow output (#1238). A value
98
+ substituted from a `secrets/` ref is still `[REDACTED]` in stdout, stderr,
99
+ diagnostics and the journal, but the rest of an env asset, such as a URL or a
100
+ channel id, now reaches the journal and the next step intact. A credential
101
+ typed straight into an env file is no longer redacted; keep credentials in
102
+ `secrets/`.
103
+ - A secret's value is read the same way everywhere (#1238): the file content with
104
+ surrounding whitespace trimmed. `akm secret run` and env tokens used to pass
105
+ the raw bytes, trailing newline included, while config credentials and the
106
+ website fetchers trimmed.
107
+ - Installing a bundle is now the trust decision (#1237). A bundle whose `env/`
108
+ files set a key such as `PATH` installs, updates unattended, and works in
109
+ `akm env run`, `akm secret run` and workflow `env:` bindings with no flag, no
110
+ prompt and no block, whichever bundle it comes from. `akm lint` still reports
111
+ `dangerous-env-key` findings, non-blocking.
112
+ - An agent's own `tools` list is now honoured for an agent in any bundle, not
113
+ only the primary stash. `execution.allowedTools` still caps what a run grants.
114
+ - A task `env:` value that looks like a credential is now a warning, in the same
115
+ style workflow params give, instead of an error that stopped the task. The
116
+ warning points to `akm secret run` or `akm env run` in the task's `run:`.
117
+ - `--allow-dangerous-env-keys` on `bundle add`, `bundle update` and `env run` is
118
+ now a deprecated no-op, accepted through 0.10 so existing scripts keep working.
119
+ It is an unknown flag from 0.11.
120
+ - Reads follow symlinks (#1237). An env file or secret that is a symlink to a
121
+ file outside its bundle directory now resolves through `akm env`, `akm secret`,
122
+ `${ref}` substitution and a workflow `env:` binding, and a ref
123
+ resolved by path does the same, instead of failing with "escapes the secrets
124
+ directory" or "Ref resolves outside the stash root", so a secret can point at
125
+ a container's `/run/secrets/<name>`. A command, agent or script that is such a
126
+ symlink also resolves when a workflow, task or `akm agent` runs it, and
127
+ `akm index` indexes a file symlink like any other file, so `akm show` and
128
+ search find it, and `akm env list` and `akm secret list` list it. A symlinked
129
+ script runs under the interpreter its own name selects, and cloning a skill
130
+ copies a symlinked file by content. A directory symlink is still never
131
+ descended. Names with `..`
132
+ segments or absolute names are still refused, and writes still never follow a
133
+ symlink out of the bundle.
134
+ - `akm workflow run <ref>` always starts a new run, and `akm workflow run <run-id>`
135
+ continues one (#1239). `akm workflow resume <run-id>` only re-activates a blocked
136
+ or failed run so `run <run-id>` can continue it. Before, running a ref
137
+ silently resumed any active run of that ref started from the same directory,
138
+ which is why scripts passed `--new` and why a scheduled task and a shell could
139
+ each start their own run. Now, when other runs of the ref are still active, the
140
+ start prints one warning naming their ids and the `akm workflow run <id>` /
141
+ `akm workflow abandon <id>` commands, and starts anyway. Parameters are accepted
142
+ on every ref start, and `--new` is accepted and does nothing (deprecated,
143
+ through 0.10).
144
+ - Workflow runs are no longer tied to the directory you run from (#1239).
145
+ `akm workflow list` and `akm workflow status <ref>` show every run on the host,
146
+ most recently updated first, from any directory, so the runs of a scheduled
147
+ task are listed like any other. `--all-scopes` is accepted and does nothing
148
+ (deprecated, through 0.10), and the `list` result no longer carries a
149
+ `scopeKey`. Runs started by 0.10 are not visible to a 0.9.15 binary reading the
150
+ same `state.db`.
151
+ - `akm workflow run <ref> --skip-if-locked` also skips (exit 0, nothing started)
152
+ when an active run of that ref is being driven by a live process (#1239). Without
153
+ the flag a start is never refused.
154
+ - `akm bundle rename` rewrites the workflow ref on that bundle's workflow runs, so
155
+ `akm workflow list` shows the old runs under the new name, and lists the bundle's
156
+ active and blocked runs in its output, each with its status (#1239). Their frozen
157
+ plans keep the old refs; continue or abandon them.
158
+ - `akm workflow create` no longer indexes the new workflow, and a new run no longer
159
+ records an index entry id (#1239).
160
+ - A workflow plan records the refs it uses and no locations (#1238). New runs
161
+ freeze plan `irVersion` 7, which holds no bundle directory, no directory
162
+ identity and, for each `env:` ref, only its bundle-qualified ref: the env
163
+ file, its keys and the secrets it names are read at each step. Runs frozen
164
+ by earlier releases (`irVersion` 5 and 6) carry those fields, which are never
165
+ read but are kept as stored, so a run interrupted before the upgrade finds the
166
+ child workflow run it had already started instead of starting a second one, and
167
+ continues the same way. A plan frozen by a newer akm is refused with a message
168
+ that says to upgrade akm, whether or not this akm could decode it. A plan with an
169
+ agent step records an empty directory identity, so a 0.10 pre-release that finds
170
+ the run stops at that step instead of running the agent in the wrong directory.
171
+ `akm workflow plan` lists an env ref without its `keys` and `secretNames`.
172
+ - A workflow's exec units, agents and gate judges work in the root of the bundle
173
+ that holds the workflow, found at each step, and `exec.cwd` is joined onto it (#1238).
174
+ `exec.cwd` may now be absolute, and it is no longer required to stay inside the
175
+ bundle: a command can reach any directory anyway. A step whose workflow no
176
+ longer resolves to a bundle fails naming the workflow (a step with a gate is
177
+ blocked by its judge first, for `akm workflow resume`) instead of running in
178
+ akm's own working directory.
179
+ - An exec step's argv and `exec.cwd` can name an asset in any bundle with the same
180
+ `${ref}` token env assets use (#1238): `["bun", "${homelab//scripts/lab-status/fetch.ts}"]`.
181
+ A `secrets/` ref is replaced by the secret's value, which is redacted from the
182
+ unit's output after it is parsed, like a secret bound through an env asset; any
183
+ other asset ref by its absolute path. A ref no bundle owns
184
+ fails the unit, naming it, before anything is spawned. Prompts are not
185
+ substituted. A workflow in bundle A can now run a script from bundle B with an
186
+ env asset from bundle C that names a secret in bundle D.
187
+ - A task no longer captures its working directory's identity when it is prepared
188
+ and re-checks it before it runs (#1238). A `run:` task runs in
189
+ `<bundle>/<working-directory>`, and a `uses: scripts/<file>` target that is a
190
+ symlink to a file outside the bundle works: the task freezes that file's
191
+ bytes.
192
+ - A task's `working-directory` follows the same rule as a workflow's `exec.cwd`
193
+ (#1238): a non-empty path, relative to the task's bundle root or absolute, and
194
+ no longer required to stay inside the bundle. A symlink to a directory outside
195
+ the bundle, an absolute path and a path with `..` all work, and `akm lint` and
196
+ `akm task sync` no longer need the bundle root to check it.
197
+
198
+ ### Deprecated
199
+
200
+ - `secret://name` in config is retired in favour of `${secrets/name}` (#1238). A
201
+ config that still holds it loads as if it said `${secrets/name}`, with one
202
+ warning naming the key, and `akm migrate apply` rewrites `config.json`.
203
+ `akm config set` and `bundle add --credential` no longer accept it. A workflow run
204
+ frozen before the upgrade keeps resolving its engine credential when it is
205
+ continued, and keeps the `secret://` text it stored.
206
+
207
+ ### Removed
208
+
209
+ - The dangerous-key audit on `bundle add` and `bundle update`, its prompt, its
210
+ rollback of a freshly installed bundle, and the `blocked` per-bundle status in
211
+ `bundle update --all`. The `DANGEROUS_ENV_KEY` and `DANGEROUS_ENV_AUDIT_FAILED`
212
+ error codes are gone with them (#1237).
213
+ - The first-party and third-party distinction for env and secret injection
214
+ (`src/core/activation-policy.ts`) and the unused `trusted` field on a derived
215
+ bundle installation (#1237).
216
+ - `akm show` no longer reports an `activeRun` field. Run state is under
217
+ `akm workflow` (#1239).
218
+ - The "indexed root no longer matches the bundle source" warnings when a command
219
+ or persona is dispatched (#1238). A bundle at a different location than the
220
+ index recorded is no longer reported as drift.
221
+ - The `exec_cwd_escape` failure reason and the checks behind it: the parser, the
222
+ compiler and the exec runner no longer reject an `exec.cwd` that leaves the
223
+ workflow's bundle (#1238).
224
+
9
225
  ## [0.10.261012] - 2026-10-22
10
226
 
11
227
  ### Changed
package/SECURITY.md CHANGED
@@ -75,8 +75,8 @@ The built-in YouTube snapshot fetcher (`src/sources/snapshot-fetchers/youtube.ts
75
75
  and `<stash>/secrets/`. They are protected against other local users by
76
76
  filesystem permissions but are not encrypted at rest. Do not commit these
77
77
  files to source control. Normal `akm env` and `akm secret` output never echoes
78
- values; materialize values only at the command boundary with `akm env run`,
79
- `akm secret run`, or `akm secret path`.
78
+ values; materialize values only at the command boundary with `akm env run`
79
+ or `akm secret run`.
80
80
 
81
81
  The knowledge graph extracted from your assets (entities/relations) lives
82
82
  only in `$DATA/index.db` and is surfaced as aggregate counts via `akm health`
package/STABILITY.md CHANGED
@@ -100,9 +100,9 @@ enumeration of the whole `proposal` noun group.
100
100
  | `akm search` | Stable | |
101
101
  | `akm curate` | Stable | |
102
102
  | `akm show` | Stable | |
103
- | `akm workflow status` | Experimental | `--all-scopes` added in 0.9.15 (#942), additive. |
103
+ | `akm workflow status` | Experimental | `--all-scopes` added in 0.9.15 (#942); a deprecated no-op since 0.10 (#1239), accepted through 0.10. |
104
104
  | `akm workflow plan` | Experimental | New in 0.9.2; secret-free provenance output; envelope shape may change. |
105
- | `akm workflow list` | Experimental | `--all-scopes` and a top-level `scopeKey` envelope field added in 0.9.15 (#942), both additive. |
105
+ | `akm workflow list` | Experimental | `--all-scopes` and a top-level `scopeKey` envelope field added in 0.9.15 (#942); since 0.10 (#1239) `--all-scopes` is a deprecated no-op, accepted through 0.10, and the `scopeKey` field is removed from the list result. |
106
106
  | `akm workflow create` | Experimental | |
107
107
  | `akm workflow resume` | Experimental | |
108
108
  | `akm workflow abandon` | Experimental | |
@@ -92,7 +92,7 @@ akm import https://example.com/docs/auth # Fetch one URL and import it as
92
92
  akm import ./doc.md --bundle my-other-bundle # Route import to a named writable bundle source
93
93
  akm workflow create ship-release # Create a workflow asset in the bundle
94
94
  akm lint --type workflows # Parse and compile every workflow source; list every error
95
- akm workflow run workflows/ship-release # Start or resume and execute the workflow
95
+ akm workflow run workflows/ship-release # Start a new run and execute it
96
96
  akm feedback skills/code-review --positive # Record that an asset helped (ranks it higher; no rewrite)
97
97
  akm feedback agents/reviewer --negative --reason "says to run jest; the suite runs on vitest" # Content wrong or stale: lowers its ranking; improve may repair its frontmatter
98
98
  akm feedback knowledge/opencode-server --negative --reason "the default port is 4096, not 8000" --replace "port 8000" --with "port 4096" --source "https://opencode.ai/docs/server/" # Apply an exact fix
@@ -150,8 +150,8 @@ the index (comments can contain commented-out credentials). akm does not edit
150
150
  entries — you edit the file with your own editor and akm loads it.
151
151
 
152
152
  ```sh
153
- akm env create prod # Create an empty env file
154
- akm env create prod --from-file ./.env # Ingest an existing .env
153
+ akm env create env/prod # Create an empty env file
154
+ akm env create env/prod --from-file ./.env # Ingest an existing .env
155
155
  akm env list # List all env files across bundles with key names
156
156
  akm show env/prod # Inspect key names (never values or comments)
157
157
  akm env run env/prod -- ./deploy.sh # Run a command with the whole .env injected (the safe path)
@@ -186,10 +186,10 @@ workflow. Direct run-id commands still target the exact run.
186
186
  akm workflow create ship-release --print # Print a starter workflow template, without writing
187
187
  akm workflow create ship-release # Scaffold a new workflow asset
188
188
  akm workflow run workflows/ship-release --version=1.2.3 # Start and execute with exact-name parameter flags
189
- akm workflow run <run-id> --max-retries 2 --timeout 10m # Resume with invocation-wide controls
189
+ akm workflow run <run-id> --max-retries 2 --timeout 10m # Continue a run with invocation-wide controls
190
190
  akm workflow status <run-id> # Show the exact run by id
191
- akm workflow resume <run-id> # Resume a blocked or failed run
192
- akm workflow list # List workflow runs in the current scope
191
+ akm workflow resume <run-id> # Re-activate a blocked or failed run, then `run <run-id>`
192
+ akm workflow list # List workflow runs on this host
193
193
  ```
194
194
 
195
195
  ## Clone
@@ -282,7 +282,6 @@ akm bundle list --kind git # Filter by provider (files
282
282
  akm bundle remove <target> # Remove by id, ref, path, or name
283
283
  akm bundle update --all # Update all managed sources
284
284
  akm bundle update <target> --force # Force re-download
285
- akm bundle update <target> --allow-dangerous-env-keys # Approve reviewed dangerous env keys
286
285
  akm bundle update --all --skip-if-locked # Scheduled refresh; exit 0 on DB contention
287
286
  ```
288
287
 
@@ -15,9 +15,8 @@ For workflow tasks:
15
15
  2. `akm workflow run workflows/<name> --param_name=value` — execute every step and verification gate
16
16
  3. `akm workflow status <run-id> --units` — inspect a stopped or failed run
17
17
 
18
- Workflow runs are scoped to your current project/worktree/directory. Ref-based
19
- commands like `workflow run workflows/<name>`, `workflow status workflows/<name>`,
20
- and `workflow list` operate within the current scope only.
18
+ A workflow ref (`workflows/<name>`) starts a new run; a run id drives that exact
19
+ run. `workflow list` and `workflow status` see every run on this host.
21
20
 
22
21
  ## Quick Reference
23
22
 
@@ -29,7 +28,7 @@ akm search "memories/projectA/" # List a subtree (conceptId prefix
29
28
  akm search "<query>" --from all # Also search registries
30
29
  akm show <ref> # View asset details
31
30
  akm help <command> # Show a command's options and subcommands
32
- akm workflow run <ref> # Start or resume and execute a workflow
31
+ akm workflow run <ref> # Start a new run and execute it
33
32
  akm remember "Deployment needs VPN access" # Record a memory in your bundle
34
33
  akm remember "note" --bundle my-bundle # Route write to a named writable bundle source
35
34
  akm remember "note" --xref knowledge/auth-flow # Cite provenance in frontmatter xrefs (repeatable)
@@ -6,7 +6,7 @@ import fs from "node:fs";
6
6
  import path from "node:path";
7
7
  import { adapterForId } from "../../core/adapter/registry.js";
8
8
  import { parseBundleRef } from "../../core/asset/asset-ref.js";
9
- import { isWithin } from "../../core/common.js";
9
+ import { isContainedResolvedPath, isWithin } from "../../core/common.js";
10
10
  import { loadConfig } from "../../core/config/config.js";
11
11
  import { bundleComponentConfig, bundlesToSourceEntries } from "../../core/config/config-sources.js";
12
12
  import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
@@ -55,7 +55,7 @@ function isLexicallyWithin(candidate, root) {
55
55
  const relative = path.relative(path.resolve(root), path.resolve(candidate));
56
56
  return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
57
57
  }
58
- function implicitComponentForEntry(entry, config, realRoot) {
58
+ function implicitComponentForEntry(entry, config) {
59
59
  const sources = resolveSourceEntries(undefined, config);
60
60
  const installations = deriveInstallations(sources);
61
61
  for (const [index, installation] of installations.entries()) {
@@ -66,9 +66,6 @@ function implicitComponentForEntry(entry, config, realRoot) {
66
66
  if (!source || !component)
67
67
  continue;
68
68
  const canonicalRoot = realDirectory(component.root, `Canonical implicit source root for ${entry.bundleId}`);
69
- if (canonicalRoot !== realRoot) {
70
- warnOnce(`execution-source-root-drift:${entry.itemRef}`, `[command] Indexed root for ${JSON.stringify(entry.itemRef)} no longer matches the working bundle source; re-resolving from live config. Run \`akm index --full\` to refresh it.`);
71
- }
72
69
  if (component.adapter !== entry.adapterId) {
73
70
  warnOnce(`execution-source-adapter-drift:${entry.itemRef}`, `[command] Indexed adapter ${JSON.stringify(entry.adapterId)} for ${JSON.stringify(entry.itemRef)} no longer matches the working source's adapter ${JSON.stringify(component.adapter)}; re-resolving from live config. Run \`akm index --full\` to refresh it.`);
74
71
  }
@@ -84,7 +81,7 @@ function implicitComponentForEntry(entry, config, realRoot) {
84
81
  function componentForEntry(entry, config, realRoot) {
85
82
  const configured = config.bundles?.[entry.bundleId];
86
83
  if (!configured) {
87
- const implicit = implicitComponentForEntry(entry, config, realRoot);
84
+ const implicit = implicitComponentForEntry(entry, config);
88
85
  if (implicit)
89
86
  return implicit;
90
87
  throw new ConfigError(`Indexed execution source ${JSON.stringify(entry.itemRef)} belongs to unconfigured bundle ${JSON.stringify(entry.bundleId)}.`, "INVALID_CONFIG_FILE", "Refresh the bundle configuration and rebuild the index before dispatching this asset.");
@@ -116,9 +113,6 @@ function componentForEntry(entry, config, realRoot) {
116
113
  if (!isWithin(configuredRoot, configuredSourceRoot)) {
117
114
  throw new ConfigError(`Configured component root for ${JSON.stringify(entry.itemRef)} resolves outside its materialized bundle source.`, "INVALID_CONFIG_FILE", "Remove the escaping symlink or component path, then run `akm index --full`.");
118
115
  }
119
- if (configuredRoot !== realRoot) {
120
- warnOnce(`execution-source-root-drift:${entry.itemRef}`, `[command] Indexed root for ${JSON.stringify(entry.itemRef)} no longer matches the ${JSON.stringify(configuredSource.type)} bundle source; re-resolving from live config. Run \`akm index --full\` to refresh it.`);
121
- }
122
116
  }
123
117
  const writable = component?.writable ?? configured.writable ?? configured.path !== undefined;
124
118
  return Object.freeze({
@@ -131,8 +125,8 @@ function componentForEntry(entry, config, realRoot) {
131
125
  function expectedIndexedType(kind) {
132
126
  return kind === "command" ? "command" : "agent";
133
127
  }
134
- function assertRenderedIdentity(source, entry, realRoot, realFile, content) {
135
- const expectedFile = path.relative(realRoot, realFile).replace(/\\/g, "/");
128
+ function assertRenderedIdentity(source, entry, content) {
129
+ const expectedFile = path.relative(path.resolve(entry.stashDir), path.resolve(entry.filePath)).replace(/\\/g, "/");
136
130
  const expectedHash = createHash("sha256").update(content, "utf8").digest("hex");
137
131
  if (source.identity.ref !== entry.itemRef ||
138
132
  source.identity.bundle !== entry.bundleId ||
@@ -157,8 +151,10 @@ export async function loadAdapterExecutionSource(refInput, expectedKind, options
157
151
  }
158
152
  const config = await effectiveConfig(options.config);
159
153
  const realRoot = realDirectory(entry.stashDir, `Indexed component root for ${entry.itemRef}`);
160
- const realFile = realRegularFile(entry.filePath, `Indexed execution source ${entry.itemRef}`);
161
- if (!isWithin(realFile, realRoot) || realFile === realRoot) {
154
+ realRegularFile(entry.filePath, `Indexed execution source ${entry.itemRef}`);
155
+ // An index row must name a file inside its own component root. Checked on the
156
+ // authored path, so a file that is a symlink to one elsewhere still loads.
157
+ if (!isContainedResolvedPath(path.resolve(entry.filePath), path.resolve(entry.stashDir))) {
162
158
  throw new ConfigError(`Indexed execution source ${JSON.stringify(entry.itemRef)} resolves outside its component root.`, "INVALID_CONFIG_FILE", "Remove the escaping path and rebuild the index.");
163
159
  }
164
160
  const component = componentForEntry(entry, config, realRoot);
@@ -174,6 +170,6 @@ export async function loadAdapterExecutionSource(refInput, expectedKind, options
174
170
  if (!rendered || rendered.kind !== expectedKind) {
175
171
  throw new ConfigError(`Bundle adapter ${JSON.stringify(entry.adapterId)} did not render ${JSON.stringify(entry.itemRef)} as a ${expectedKind}.`, "INVALID_CONFIG_FILE", "Rebuild the index; if the mismatch remains, repair the source file or owning adapter.");
176
172
  }
177
- assertRenderedIdentity(rendered, entry, realRoot, realFile, fileContext.content());
173
+ assertRenderedIdentity(rendered, entry, fileContext.content());
178
174
  return rendered;
179
175
  }
@@ -9,37 +9,43 @@
9
9
  * REUSES the exact same machinery instead of forking it. Every safety
10
10
  * invariant carries over unchanged:
11
11
  *
12
- * - `${secret:NAME}` tokens resolve against the env's own stash; a missing
13
- * secret is a hard error and NOTHING is injected (no partial injection).
14
- * - Known process-hijacking keys (LD_PRELOAD, PATH, …) are blocked for
15
- * third-party-sourced stashes and warned about for first-party ones.
12
+ * - `${ref}` tokens resolve through the one ref walk (a `secrets/` ref to the
13
+ * secret's trimmed value, any other asset ref to its absolute path); a
14
+ * missing ref is a hard error and NOTHING is injected (no partial injection).
16
15
  * - An `env_access` audit event records key NAMES only, never values.
17
16
  * - Resolved values must never be written to stdout or logs by callers.
18
17
  */
19
- import fs from "node:fs";
20
- import path from "node:path";
21
- import { decideDangerousEnvInjection } from "../../core/activation-policy.js";
22
- import { assetPathForName } from "../../core/asset/asset-placement.js";
23
- import { isWithin } from "../../core/common.js";
24
- import { makeEnvRef, makeSecretRef, resolveEnvPath } from "../../core/env-secret-ref.js";
18
+ import { isInstalledBundle, makeEnvRef, resolveEnvPath, resolveRefOwner } from "../../core/env-secret-ref.js";
25
19
  import { NotFoundError, UsageError } from "../../core/errors.js";
26
20
  import { appendEvent } from "../../core/events.js";
27
- import { isDangerousEnvKey } from "../lint/env-key-rules.js";
28
- import { loadEnv, resolveSecretTokens } from "./env.js";
29
- import { readValue } from "./secret.js";
21
+ import { substituteRefTokens } from "../../core/ref-token.js";
22
+ import { readValue } from "../../core/secret-value.js";
23
+ import { warnOnce } from "../../core/warn.js";
24
+ import { loadEnv } from "./env.js";
25
+ /** The retired `${secret:NAME}` env-value token (see `resolveEnvBinding`). */
26
+ const LEGACY_SECRET_TOKEN_RE = /\$\{secret:([^}\s]+)\}/g;
27
+ /**
28
+ * What a `${ref}` token stands for: a `secrets/` ref yields the secret's
29
+ * trimmed value, any other asset ref its absolute path, a ref no bundle owns
30
+ * `undefined`. The one resolver behind env values and exec argv.
31
+ */
32
+ export function resolveRefToken(ref) {
33
+ const owner = resolveRefOwner(ref);
34
+ if (owner === undefined)
35
+ return undefined;
36
+ return owner.type === "secret"
37
+ ? { kind: "secret", value: readValue(owner.absPath) }
38
+ : { kind: "path", path: owner.absPath };
39
+ }
30
40
  /**
31
41
  * Resolve an env ref to its injectable values: load the file, apply key
32
- * filtering, substitute `${secret:NAME}` tokens from the sibling secrets
33
- * directory, enforce the dangerous-key policy, and append the keys-only
42
+ * filtering, substitute `${ref}` tokens, and append the keys-only
34
43
  * `env_access` audit event.
35
44
  */
36
45
  export function resolveEnvBinding(target, options = {}) {
37
46
  const warn = options.warn ?? ((message) => process.stderr.write(`warning: ${message}\n`));
38
- const { name, absPath, source } = resolveEnvPath(target);
47
+ const { name, absPath, source, bundleId } = resolveEnvPath(target);
39
48
  const envRef = makeEnvRef(name, source);
40
- if (!fs.existsSync(absPath)) {
41
- throw new NotFoundError(`Env not found: ${envRef}`);
42
- }
43
49
  const allValues = loadEnv(absPath);
44
50
  // Value-safe key filtering (--only / --except operate on key NAMES only).
45
51
  let envValues = allValues;
@@ -58,49 +64,32 @@ export function resolveEnvBinding(target, options = {}) {
58
64
  const excluded = new Set(options.except);
59
65
  envValues = Object.fromEntries(Object.entries(allValues).filter(([k]) => !excluded.has(k)));
60
66
  }
61
- // Substitute `${secret:NAME}` tokens with the sibling secret asset in the
62
- // SAME stash. A missing secret is a hard error — inject NOTHING.
63
- const secretsRoot = path.join(source.path, "secrets");
64
- const resolveSecret = (secretName) => {
65
- const secretPath = assetPathForName("secret", secretsRoot, secretName);
66
- // Defense-in-depth: ensure the resolved path stays inside the secrets dir.
67
- if (!isWithin(secretPath, secretsRoot)) {
68
- throw new UsageError(`Secret name "${secretName}" escapes the secrets directory.`);
69
- }
70
- if (!fs.existsSync(secretPath))
71
- return undefined;
72
- // Match `secret run`: read utf8, do not trim (stay consistent with that path).
73
- return readValue(secretPath).toString("utf8");
74
- };
75
- const { values: substituted, missing } = resolveSecretTokens(envValues, resolveSecret);
67
+ // Read-compat: env files written for 0.9 / early 0.10 spell a secret
68
+ // `${secret:NAME}`, meaning `secrets/NAME` in this env asset's own bundle.
69
+ // Read it as the `${ref}` it stands for; the file is bundle content and is
70
+ // never rewritten.
71
+ let firstLegacy;
72
+ envValues = Object.fromEntries(Object.entries(envValues).map(([key, value]) => [
73
+ key,
74
+ value.replace(LEGACY_SECRET_TOKEN_RE, (_token, secretName) => {
75
+ const spelling = `\${${bundleId}//secrets/${secretName}}`;
76
+ firstLegacy ??= spelling;
77
+ return spelling;
78
+ }),
79
+ ]));
80
+ if (firstLegacy !== undefined) {
81
+ warnOnce(`legacy-secret-token:${envRef}`, `Env "${envRef}" uses the retired \${secret:NAME} spelling; reading it as ${firstLegacy}. Write the full ref in the env file.`);
82
+ }
83
+ // Substitute `${ref}` tokens. A missing ref is a hard error — inject NOTHING.
84
+ const { values: substituted, missing, secretValues, } = substituteRefTokens(envValues, resolveRefToken, isInstalledBundle);
76
85
  if (missing.length > 0) {
77
- throw new NotFoundError(`Env "${envRef}" references secret(s) not found in its stash: ${missing.map((n) => makeSecretRef(n)).join(", ")}. Nothing was injected.`, "FILE_NOT_FOUND", `Create the missing secret, e.g. \`akm secret set ${makeSecretRef(missing[0])}\`.`);
86
+ throw new NotFoundError(`Env "${envRef}" references asset(s) that no bundle owns: ${missing.join(", ")}. Nothing was injected.`, "FILE_NOT_FOUND",
87
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: literal `${ref}` token syntax documented for users, not interpolation
88
+ "Write each `${ref}` as a full akm ref that exists, e.g. `${secrets/name}` or `${team//scripts/run.sh}`.");
78
89
  }
79
90
  envValues = substituted;
80
91
  const keys = Object.keys(envValues);
81
- // Scan injected keys for known process-hijacking variables. The workspace
82
- // activation policy decides block (third-party, registryId set) vs. warn
83
- // (first-party stash, where the operator owns the file).
84
- const dangerous = keys.filter(isDangerousEnvKey);
85
- if (dangerous.length > 0) {
86
- const detail = `Env "${envRef}" injects process-hijacking variable(s): ${dangerous.join(", ")}.`;
87
- const decision = decideDangerousEnvInjection({
88
- dangerousKeys: dangerous,
89
- thirdParty: Boolean(source.registryId),
90
- allowDangerousEnvKeys: options.allowDangerousEnvKeys,
91
- });
92
- if (decision === "block") {
93
- throw new UsageError(`Refusing to inject env from a third-party stash. ${detail}\n` +
94
- ` Review the file, then copy the values into a first-party env if you trust them, ` +
95
- `or pass --allow-dangerous-env-keys once you have.`, "INVALID_FLAG_VALUE");
96
- }
97
- if (decision === "warn") {
98
- warn(options.allowDangerousEnvKeys && source.registryId
99
- ? `${detail} Injecting anyway (--allow-dangerous-env-keys).`
100
- : `${detail} Injecting anyway (first-party stash).`);
101
- }
102
- }
103
92
  // Audit trail: keys only, never values.
104
93
  appendEvent({ eventType: "env_access", ref: envRef, metadata: { keys } });
105
- return { ref: envRef, values: envValues, keys };
94
+ return { ref: envRef, values: envValues, keys, secretValues };
106
95
  }