akm-cli 0.9.11 → 0.9.13

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 (134) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/STABILITY.md +6 -1
  3. package/dist/assets/hints/cli-hints-full.md +1 -1
  4. package/dist/assets/improve-strategies/consolidate.json +1 -1
  5. package/dist/assets/improve-strategies/default.json +1 -1
  6. package/dist/assets/improve-strategies/thorough.json +1 -2
  7. package/dist/assets/workflows/workflow-template.md +4 -0
  8. package/dist/cli/shared.js +16 -4
  9. package/dist/cli.js +15 -13
  10. package/dist/commands/agent/agent-dispatch.js +8 -0
  11. package/dist/commands/command/execution-source-loader.js +25 -22
  12. package/dist/commands/command/portable-template.js +4 -26
  13. package/dist/commands/config-cli.js +10 -4
  14. package/dist/commands/env/env-binding.js +10 -3
  15. package/dist/commands/env/env-cli.js +7 -0
  16. package/dist/commands/env/secret-cli.js +15 -4
  17. package/dist/commands/health/checks.js +186 -71
  18. package/dist/commands/health.js +16 -4
  19. package/dist/commands/improve/distill/quality-gate.js +2 -2
  20. package/dist/commands/improve/distill.js +28 -12
  21. package/dist/commands/improve/execution.js +1 -2
  22. package/dist/commands/improve/extract.js +82 -56
  23. package/dist/commands/improve/improve-strategies.js +26 -8
  24. package/dist/commands/improve/improve.js +14 -0
  25. package/dist/commands/improve/preparation.js +9 -6
  26. package/dist/commands/improve/reflect.js +61 -77
  27. package/dist/commands/lint/base-linter.js +10 -0
  28. package/dist/commands/lint/index.js +3 -1
  29. package/dist/commands/migrate-cli.js +6 -4
  30. package/dist/commands/proposal/drain-policies.js +22 -2
  31. package/dist/commands/proposal/drain.js +48 -6
  32. package/dist/commands/proposal/proposal-cli.js +1 -0
  33. package/dist/commands/proposal/repository.js +4 -4
  34. package/dist/commands/proposal/validators/proposal-quality-validators.js +23 -2
  35. package/dist/commands/proposal/validators/proposals.js +10 -19
  36. package/dist/commands/read/show.js +42 -31
  37. package/dist/commands/registry-cli.js +4 -2
  38. package/dist/commands/sources/init.js +4 -8
  39. package/dist/commands/sources/self-update.js +2 -2
  40. package/dist/commands/sources/source-clone.js +5 -7
  41. package/dist/commands/sources/sources-cli.js +3 -5
  42. package/dist/commands/tasks/tasks-cli.js +4 -12
  43. package/dist/commands/tasks/tasks.js +38 -35
  44. package/dist/commands/workflow-cli.js +17 -15
  45. package/dist/core/activation-policy.js +31 -3
  46. package/dist/core/adapter/execution-source.js +39 -11
  47. package/dist/core/asset/stash-meta.js +7 -41
  48. package/dist/core/common.js +8 -17
  49. package/dist/core/config/config-schema.js +3 -23
  50. package/dist/core/config/config-walker.js +56 -6
  51. package/dist/core/config/config.js +42 -17
  52. package/dist/core/config/legacy-source-shape-shim.js +79 -0
  53. package/dist/core/config/schema/embedding.js +2 -2
  54. package/dist/core/config/schema/engines.js +2 -2
  55. package/dist/core/config/schema/index-config.js +19 -21
  56. package/dist/core/config/schema/primitives.js +27 -10
  57. package/dist/core/config/schema/sources-bundles.js +1 -6
  58. package/dist/core/errors.js +4 -3
  59. package/dist/core/improve-types.js +17 -0
  60. package/dist/core/json-schema.js +1 -11
  61. package/dist/core/maintenance-barrier.js +17 -2
  62. package/dist/core/paths.js +12 -15
  63. package/dist/core/state/migrations.js +28 -0
  64. package/dist/core/state-db.js +28 -1
  65. package/dist/core/write-source.js +6 -6
  66. package/dist/indexer/bundle-identity-guard.js +3 -0
  67. package/dist/indexer/ensure-index.js +5 -0
  68. package/dist/indexer/indexer.js +11 -3
  69. package/dist/indexer/lookup/adapter-concept-owner.js +14 -3
  70. package/dist/indexer/passes/metadata.js +16 -5
  71. package/dist/indexer/search/search-fields.js +1 -30
  72. package/dist/integrations/agent/engine-resolution.js +15 -1
  73. package/dist/integrations/agent/model-map.js +16 -10
  74. package/dist/integrations/agent/prompts.js +13 -6
  75. package/dist/integrations/lockfile.js +22 -7
  76. package/dist/llm/client.js +28 -8
  77. package/dist/llm/embedders/remote.js +3 -2
  78. package/dist/llm/index-passes.js +3 -2
  79. package/dist/output/shapes/passthrough.js +9 -3
  80. package/dist/output/shapes.js +50 -3
  81. package/dist/output/text/proposal-format.js +5 -0
  82. package/dist/output/text/workflow-format.js +8 -1
  83. package/dist/scripts/akm-migrate-node.js +1737 -1392
  84. package/dist/scripts/akm-migrate.js +1736 -1391
  85. package/dist/setup/setup.js +14 -21
  86. package/dist/sources/include.js +150 -20
  87. package/dist/sources/providers/git-install.js +14 -12
  88. package/dist/sources/providers/git-provider.js +3 -3
  89. package/dist/sources/snapshot-fetchers/website-ingest.js +54 -16
  90. package/dist/sources/website-url.js +12 -4
  91. package/dist/storage/engines/sqlite-migrations.js +40 -10
  92. package/dist/storage/like-pattern.js +7 -0
  93. package/dist/storage/repositories/extract-sessions-repository.js +23 -0
  94. package/dist/storage/repositories/index-connection.js +27 -10
  95. package/dist/storage/repositories/index-entry-schema.js +19 -2
  96. package/dist/storage/repositories/index-schema.js +30 -9
  97. package/dist/storage/repositories/proposals-repository.js +2 -1
  98. package/dist/storage/repositories/task-history-repository.js +14 -7
  99. package/dist/storage/repositories/workflow-runs-repository.js +133 -11
  100. package/dist/storage/sqlite-read-snapshot.js +11 -9
  101. package/dist/tasks/backends/cron.js +34 -5
  102. package/dist/tasks/backends/launchd.js +23 -26
  103. package/dist/tasks/backends/schtasks.js +50 -3
  104. package/dist/tasks/frozen-script.js +2 -0
  105. package/dist/tasks/prepare/prepare.js +2 -7
  106. package/dist/tasks/prepare/script-capture.js +38 -6
  107. package/dist/tasks/schedule.js +154 -13
  108. package/dist/tasks/source/task-source-v3-frozen.js +0 -1
  109. package/dist/tasks/source/task-source-v4.js +0 -1
  110. package/dist/workflows/exec/child-workflow.js +2 -3
  111. package/dist/workflows/exec/exec-unit.js +3 -4
  112. package/dist/workflows/exec/run-workflow.js +20 -11
  113. package/dist/workflows/exec/step-work.js +76 -56
  114. package/dist/workflows/freeze/resolve-steps.js +19 -11
  115. package/dist/workflows/freeze/source-freeze.js +7 -0
  116. package/dist/workflows/freeze/targets/child-workflow.js +12 -18
  117. package/dist/workflows/freeze/targets/command.js +14 -2
  118. package/dist/workflows/ir/environment-v4.js +4 -2
  119. package/dist/workflows/ir/freeze-v4.js +2 -5
  120. package/dist/workflows/ir/plan-hash.js +0 -3
  121. package/dist/workflows/ir/schema-v4.js +14 -9
  122. package/dist/workflows/ir/schema.js +1 -3
  123. package/dist/workflows/parser.js +1 -1
  124. package/dist/workflows/resource-limits.js +35 -48
  125. package/dist/workflows/runtime/plan-classifier.js +89 -41
  126. package/dist/workflows/runtime/run-outputs.js +1 -21
  127. package/dist/workflows/runtime/runs.js +104 -154
  128. package/dist/workflows/source-files.js +28 -54
  129. package/dist/workflows/source-ir/program.js +2 -2
  130. package/dist/workflows/source-ir/semantics.js +5 -23
  131. package/docs/migration/v0.9.1-to-v0.9.2.md +20 -0
  132. package/docs/reference/cli.md +92 -17
  133. package/package.json +1 -1
  134. package/schemas/akm-config.json +5 -10
package/CHANGELOG.md CHANGED
@@ -4,6 +4,233 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [0.9.13] - 2026-09-04
8
+
9
+ ### Added
10
+
11
+ - **A stable `results` alias on every list-returning command (#922).** `search`,
12
+ `curate`, `proposal list`, `bundle list`, `env list`, `secret list`,
13
+ `registry search`, `registry list`, `workflow list`, `task history` and
14
+ `log list` each keep their existing semantic key (`hits`, `items`,
15
+ `proposals`, …) and now also expose the same array as `results`, in every
16
+ shape including `--shape agent`. It is the same array, not a copy. A caller
17
+ reading `hits` from a `curate` response previously got nothing back and could
18
+ reasonably read that as "no results" — there was no error and the `summary`
19
+ alongside it still reported that results were selected. Commands carrying
20
+ several heterogeneous collections (`health`, `task doctor`) are deliberately
21
+ excluded.
22
+ - **Engine and embedding credentials can resolve from the secret store
23
+ (#917).** `apiKey` accepts `secret://<name>` alongside `$VAR` / `${VAR}`,
24
+ resolved through the existing store-backed resolver. A detached SessionEnd
25
+ hook or a cron job — the two contexts least able to supply an environment
26
+ variable and most likely to need extraction — no longer requires editing the
27
+ login environment for a value akm already stores. Literal keys in
28
+ `config.json` are still refused, and an unresolvable reference fails loudly,
29
+ naming the reference and never the secret.
30
+ - **`akm proposal drain` reports what failed (#921).** The envelope carries a
31
+ `failed[]` naming each proposal and why it was refused (stale target,
32
+ validation), instead of reporting `failed: 0` while the same run printed five
33
+ failures to stderr. `--dry-run` now applies the same stale-target check the
34
+ real run does, so its prediction stops disagreeing with the outcome. The
35
+ refusals themselves are correct and unchanged: declining to overwrite a target
36
+ modified since the proposal was created is the desired behaviour. `akm
37
+ improve`'s triage pre-pass reports the same count.
38
+ - **Workflow step output is checked against its declared schema (#923).** A step
39
+ whose returned object does not match its `outputSchema` now says so, naming
40
+ the step and the specific problem (a missing required field, say). This is a
41
+ warning: the run continues and its status is unchanged, because a workflow is
42
+ a guide rather than a contract. Previously an author got neither enforcement
43
+ nor feedback — the only signal was a notice saying akm was not checking, which
44
+ is a different fact from whether the output matched.
45
+
46
+ ### Changed
47
+
48
+ - **A held run lease no longer reports as database corruption (#924).** `akm
49
+ workflow run` surfaced raw SQLite text — `database is locked`, `database disk
50
+ image is malformed`, `disk I/O error` — for what was simply another
51
+ invocation holding the lease. `disk image is malformed` in particular reads as
52
+ data loss and sent one reporter through integrity checks and a WAL review
53
+ before finding the real cause. The lease message now appears at default
54
+ verbosity and carries a dedicated `RUN_LEASE_HELD` code, so a wrapper can tell
55
+ "retry shortly" from "you passed bad input". Genuine SQLite errors still
56
+ report as themselves.
57
+ - **`akm lint` stops flagging templated output paths (#927).** A documented
58
+ run-time filename such as `reports/review-<timestamp>.md` is no longer
59
+ reported as a `stale-path` broken reference. Angle-bracket and brace
60
+ placeholders, `${VAR}`, date-format runs like `YYYYMMDD`, and glob characters
61
+ are all recognised as parameterised rather than missing. Genuinely broken
62
+ literal paths are still reported.
63
+ - **The workflow level-2 heading rule is discoverable before you trip it
64
+ (#926).** The `workflow create` template states that `##` headings are step
65
+ ids and points at `###` for cross-cutting notes, and the compiler's rejection
66
+ now carries the remedy rather than only the diagnosis.
67
+
68
+ ## [0.9.12] - 2026-09-03
69
+
70
+ ### Added
71
+
72
+ - **`akm health` probes LLM engine reachability (#914).** `default-llm-engine`
73
+ and `configured-engines` now send one bounded `GET` to the endpoint's
74
+ `/models` route (3 s timeout, one probe per distinct endpoint per
75
+ invocation, no cross-run cache) instead of only checking that a credential is
76
+ present. Any HTTP response counts as reachable, so a cold local server is
77
+ never asked to load a model just to be checked. An unreachable default LLM engine is a hard `fail` naming the
78
+ connection error; an unreachable non-default engine is a `warn`. Pass
79
+ `--no-probe` on an offline or air-gapped host to keep the credential-only
80
+ verdict; the message then says reachability was not probed.
81
+ - **`akm proposal extract` reports the engine it resolved (#913).** The
82
+ envelope carries `engine` and `engineKind` (`llm`, `sdk`, or `agent`), every
83
+ `sessions[]` entry carries `engine`, and the `extract_sessions_seen` ledger
84
+ metadata records it, so "which engine did that run actually use" is one
85
+ field instead of config archaeology. `akm health`'s `active-improve-strategy`
86
+ check names the engine each improve process resolved to, which makes a
87
+ strategy-level `engine` pin that shadows `defaults.llmEngine` visible.
88
+ - **`akm workflow run <ref>` says when it resumes, and `--new` starts fresh
89
+ (#919).** Resolving a ref to an already-active run in the current scope is
90
+ unchanged, but the `workflow-run` envelope now carries `resumed: true` and
91
+ the text output leads with `resuming existing run <id> for <ref>; pass --new
92
+ to start a fresh run`. `--new` starts a second run and leaves the active one
93
+ untouched. `status`, `abandon`, `resume`, and `run <id>` all accept a unique
94
+ run-id prefix of eight or more characters.
95
+
96
+ ### Changed
97
+
98
+ - **A run that skipped every session for an infrastructure reason is visible
99
+ in the extract envelope (#912).** `warnings[]` gains one aggregate line per
100
+ infrastructure skip reason (`llm_unavailable`, `read_failed`, `exception`,
101
+ `locked_concurrent`), for example `25 of 25 sessions skipped: llm_unavailable
102
+ (engine "default")`, and a `skipReasons` count map is present whenever
103
+ `sessionsSkipped > 0`. `ok` keeps meaning "the command ran" and the exit code
104
+ does not change; that meaning is now written down on the type.
105
+ - **`akm health`'s `session-extraction` check reads the extraction ledger
106
+ (#914).** It used to read only `improve_runs`, which the hook-driven
107
+ `akm proposal extract --session-id` never writes, so a plugin-driven machine
108
+ reported "not active" as `pass` forever. It now derives its verdict from the
109
+ last seven days of `extract_sessions_seen`: `unknown` when nothing was
110
+ recorded, `warn` naming the reason and engine when every session was skipped
111
+ or failed for an infrastructure reason (`llm_unavailable`, `read_failed`,
112
+ `exception`, `locked_concurrent`), otherwise `pass` with per-outcome counts.
113
+ - **Every passthrough success envelope carries `ok: true` (#918).**
114
+ `akm config set` and `akm config unset` printed the resulting config with no
115
+ `ok` field while their failure envelope had `ok: false`, so a caller
116
+ branching on `.ok` read success as failure. The shared passthrough stamp now
117
+ adds `ok: true` when a result has no `ok` of its own; commands whose exit
118
+ code grades the outcome (`task sync`, `task sync --dry-run`, `task prune`,
119
+ `workflow run`, `upgrade`, and the `migrate` subcommands) set `ok` and the
120
+ exit code from the same value, so the two cannot disagree. `--silent` still
121
+ prints nothing.
122
+ - **The unsupported-plan error names the real situation (#919).** A frozen
123
+ workflow plan with an `irVersion` above the current one no longer reports
124
+ "pre-irVersion-5 ... after the 0.9.2 upgrade"; it says the plan was probably
125
+ written by a newer akm. The reported `irVersion 111` did not reproduce
126
+ against this tree (the column is only ever written as `5` and no path
127
+ rewrites it on a source edit); a regression test pins that editing a
128
+ workflow source leaves its in-flight run executable.
129
+
130
+ ### Fixed
131
+
132
+ - **`akm proposal accept` no longer rewrites your content.** Two "repairs" ran
133
+ before validation: one deleted every body line matching `description:` or
134
+ `when_to_use:`, the other deleted every `---` in a body that had
135
+ frontmatter. Both fired inside fenced code blocks, so any asset documenting
136
+ frontmatter — a note about akm, Claude Code skills, Jekyll, Hugo — was
137
+ silently gutted on accept, and the rewritten bytes were saved back over the
138
+ original in the proposals database. Nothing was printed. Both repairs are
139
+ gone; the truncated-description repair, which only ever rewrote a
140
+ frontmatter value, stays.
141
+ - **Prose-quality findings no longer block `proposal accept`.** A description
142
+ that read like a heading, an odd number of backticks, or a reflect revision
143
+ outside the size ratio refused the promotion and told the user to "fix the
144
+ proposal payload and try again" — but there is no `akm proposal edit` and
145
+ `accept` has no `--force`, so the only way out was hand-editing the
146
+ proposals database. These findings are now reported as warnings on a
147
+ command a human typed. Structural defects that genuinely cannot be written
148
+ (empty content, an unparseable ref, malformed frontmatter, a broken
149
+ workflow shape) still block.
150
+ - **akm no longer refuses to open a state database migrated by a newer akm.**
151
+ Two akm versions sharing one data directory is a supported deployment — a
152
+ bundled CLI beside a newer global install — and the old binary was bricked
153
+ for every command that touches `state.db`, not degraded. It protected
154
+ nothing: an older binary's entire migration registry is already applied, so
155
+ it has no pending migration to run. It now opens, warns once naming the
156
+ migrations it does not know, and reads and writes the tables it knows. A
157
+ ledger that genuinely diverges (a migration this akm has was never applied
158
+ and something else was applied in its place) is still refused.
159
+ - **`akm agent --prompt` no longer rejects prose that looks like a template.**
160
+ A prompt was validated as if it were a portable command template, so any
161
+ prompt containing `}}` from compact JSON, a `$VAR`, a `${...}`, a `$(...)`
162
+ shell snippet, an `@path`, or a `` !` `` was rejected with "unsupported
163
+ portable template construct" before the agent started. akm substitutes
164
+ nothing into a prompt, so it is now sent verbatim. The template language is
165
+ unchanged for stored command files, which are templates.
166
+
167
+ - **The 0.9.2 `claude-code` -> `claude` harness rename left the ledgers split
168
+ (#915).** State migration `027-extract-sessions-seen-harness-rename` moves
169
+ `extract_sessions_seen` and `workflow_runs.agent_harness` rows off the old
170
+ key, keeping a session already recorded under `claude` as the authoritative
171
+ row, and empties the old key space. `PERSISTED_HARNESS_IDS` in the harness
172
+ registry is now pinned by a test so the next rename cannot ship without a
173
+ migration. Scripts querying the ledger by `claude-code` will find it empty
174
+ after upgrade; see the 0.9.1 -> 0.9.2 migration guide.
175
+
176
+ ### Changed — refusals that now degrade
177
+
178
+ A repo-wide audit reviewed every defensive refusal in the codebase against
179
+ three tests: has it demonstrably helped a real user, does its failure mode cost
180
+ less than the hazard it guards, and is the hazard already gated behind a
181
+ deliberate human command. Refusals that failed those tests were removed or
182
+ downgraded to a warning, in that order of preference. Machinery that prevents
183
+ data loss or corruption — atomic writes, backups, write-path validation, path
184
+ containment — was explicitly out of scope and is unchanged.
185
+
186
+ The user-visible effect is that akm stops aborting on conditions it can
187
+ survive. Highlights:
188
+
189
+ - **Config load no longer bricks every command over one bad key.** Retired
190
+ vocabulary (`profiles`, `llm`, `agent`, `features`, `stashes`,
191
+ `modelAliases`, `bindings`, top-level `writable`) warns and passes through,
192
+ and the pre-`bundles` `stashDir`/`sources[]`/`installed[]` shape folds into
193
+ the current shape in memory. An unknown `config set` key warns and stores.
194
+ - **A newer or unfamiliar state.db migration ledger no longer refuses to
195
+ open.** A ledger carrying migrations this binary does not know warns once and
196
+ degrades; only a genuinely inconsistent ledger still aborts. This is what let
197
+ a bundled older akm keep working against a newer host's data directory.
198
+ A `plan_ir_version` of NULL (a row predating the column) decodes normally.
199
+ - **Free text is no longer validated as code.** `akm agent` accepts prompts
200
+ containing `{{`/`}}`; inline workflow `akm/command` content and portable
201
+ command templates accept `$HOME`, `@file`, `${...}` and the rest as the prose
202
+ they are. Only `$ARGUMENTS[N]`, which merely looks like the one placeholder
203
+ akm expands, still warns.
204
+ - **Version skew stops being treated as corruption.** A newer index is left
205
+ alone rather than wiped, a stale indexed workflow identity falls back, and
206
+ frozen-plan spine drift from a formatting change between releases warns
207
+ instead of marking every in-flight run corrupt.
208
+ - **Deliberate commands stop being second-guessed.** `--since` bypasses
209
+ `extract`'s per-run cap the way `--force` already did; `workflow create
210
+ --force` no longer also demands `--reset`; scheduler writes, `setup --dir`
211
+ and `bundle create --dir` under transient paths warn instead of refusing.
212
+ - **Symlinks are followed on read paths** (task sources, `models.json`, stash
213
+ meta, `akm.include` entries) with realpath containment doing the actual
214
+ safety work, rather than being refused outright.
215
+ - **Blocks that had no escape hatch got one, or got removed.** `env run` gained
216
+ `--allow-insecure` for the third-party dangerous-key block that previously
217
+ had no bypass; `registry add` accepts a credentialed URL with a warning that
218
+ redacts the credential; promotion lint findings on `proposal accept` report
219
+ instead of blocking, since there is no `proposal edit` and no `accept
220
+ --force` to work around them.
221
+ - **Limits that bounded nothing real were deleted.** Workflow plan and embedded
222
+ child-plan byte caps, step-evidence tombstoning, search-text truncation, and
223
+ the JSON-schema node budget are gone; the akm.lock acquisition budget went
224
+ from 300 ms to ~30 s with backoff and a "waiting for another akm process"
225
+ notice.
226
+ - **A cron line too long for vixie-cron now spills into a wrapper script**
227
+ instead of refusing the install. The length limit stays, because a truncated
228
+ cron line would execute a partial command.
229
+
230
+ Guards that passed the three tests were kept and documented, including the
231
+ registry-URL credential inspection limit, the third-party dangerous-key block
232
+ itself, `MAX_SCHTASKS_TRIGGERS`, and every path-containment check.
233
+
7
234
  ## [0.9.11] - 2026-09-03
8
235
 
9
236
  ### Added
package/STABILITY.md CHANGED
@@ -219,6 +219,10 @@ enumeration of the whole `proposal` noun group.
219
219
  | `4` | Health warning (`akm health` only) |
220
220
  | `70` | Internal / unclassified |
221
221
  | `78` | Configuration error |
222
+
223
+ **From 0.9.12**, every success envelope produced by the passthrough stamp
224
+ (`config`, `clone`, `models`, `task-*`, `workflow-*`, `registry-*`, …) also
225
+ carries `ok: true`; a command that already computes its own `ok` keeps it.
222
226
  - **Install scripts** — `install.sh` and `install.ps1` URLs; the `--prefix`
223
227
  / `AKM_INSTALL_DIR` environment override.
224
228
  - **Runtime** — the npm package requires Node.js >= 22 as its bootstrap and
@@ -439,7 +443,6 @@ on them.
439
443
  | `AKM_SQLITE_JOURNAL_MODE` | SQLite journal mode (network filesystems) |
440
444
  | `AKM_BIN` | Absolute `akm` path for scheduler registration |
441
445
  | `AKM_INSTALL_DIR` | Install-script prefix |
442
- | `AKM_FORCE_SETUP_TMP_STASH` | Documented escape hatch for intentional temp-directory bundles |
443
446
  | `AKM_UPGRADE_SKIP_CHECKSUM` | Recovery hatch for a broken upgrade checksum |
444
447
 
445
448
  **Internal** — no compatibility guarantee, may vanish without notice:
@@ -481,6 +484,8 @@ lives in this repo). **D3** shipped too, in the end: `akm mv` was removed in
481
484
  0.9.0 (see the Renames bullet above), with `scripts/rekey-asset-ref.ts` as the
482
485
  Internal replacement for the one capability nothing else covered.
483
486
 
487
+ - **0.10 — `config set`/`config unset` may drop the config dump** in favor of
488
+ a compact `{ok, shape, key}` result; `akm config list` remains the full read.
484
489
  - **0.10 — migration extraction.** The migration machinery leaves the CLI for
485
490
  a separately published `akm-migrate` package (see Internal above).
486
491
  - **0.10 — `--auto-accept` hard error.** It is currently accepted-and-warned;
@@ -414,7 +414,7 @@ Result-envelope commands accept `--format`, `--detail`, and `--shape` flags:
414
414
  - `--detail full` — includes scores, paths, timing, debug info
415
415
  - `--shape human` (default) — standard projection
416
416
  - `--shape agent` — agent-optimized output: strips non-actionable fields
417
- - `--shape summary` — metadata only (no content/template/prompt), under 200 tokens; only valid on `akm show`
417
+ - `--shape summary` — metadata only (no content/template/prompt), under 200 tokens; only `akm show` has a dedicated summary projection — elsewhere it falls back to `agent` with a warning
418
418
 
419
419
  Run `akm help <command>` or `akm <command> -h` for per-command help. Run
420
420
  `akm --help` for the sectioned command overview.
@@ -3,7 +3,7 @@
3
3
  "processes": {
4
4
  "reflect": { "enabled": false },
5
5
  "distill": { "enabled": false },
6
- "consolidate": { "enabled": true, "allowedTypes": ["memory"], "maxChunkSize": 25, "minPoolSize": 500 },
6
+ "consolidate": { "enabled": true, "allowedTypes": ["memory"], "maxChunkSize": 25 },
7
7
  "memoryInference": { "enabled": false },
8
8
  "graphExtraction": { "enabled": false },
9
9
  "extract": { "enabled": false },
@@ -7,7 +7,7 @@
7
7
  "allowedTypes": ["agent", "command", "knowledge", "lesson", "memory", "skill", "workflow"]
8
8
  },
9
9
  "distill": { "enabled": true, "allowedTypes": ["memory"], "requirePlannedRefs": true },
10
- "consolidate": { "enabled": true, "allowedTypes": ["memory"], "minPoolSize": 500 },
10
+ "consolidate": { "enabled": true, "allowedTypes": ["memory"] },
11
11
  "memoryInference": { "enabled": true },
12
12
  "graphExtraction": { "enabled": true },
13
13
  "extract": { "enabled": false, "triage": { "enabled": true, "minScore": 2 } },
@@ -13,8 +13,7 @@
13
13
  },
14
14
  "consolidate": {
15
15
  "enabled": true,
16
- "allowedTypes": ["memory"],
17
- "minPoolSize": 500
16
+ "allowedTypes": ["memory"]
18
17
  },
19
18
  "memoryInference": {
20
19
  "enabled": true
@@ -16,6 +16,10 @@ steps:
16
16
  Free preamble prose describing what this workflow does. It is indexed for
17
17
  search and shown in `akm show`, but it is never dispatched to a step.
18
18
 
19
+ Level-2 (`##`) headings below are step ids and must exactly match one
20
+ declared in `steps:` above — for cross-cutting notes that aren't a step
21
+ (shared context, prerequisites), use a level-3 (`###`) heading instead.
22
+
19
23
  ## first-step
20
24
 
21
25
  Describe what to do in this step. Refer to run parameters in plain
@@ -150,13 +150,14 @@ export const GLOBAL_OUTPUT_ARGS = {
150
150
  // R-050(c): single-sourced with the root command's own `--shape` help
151
151
  // (`main.args.shape` in src/cli.ts, which spreads this object) so the
152
152
  // caveat is visible from every leaf's own `--help`, not only the top-level
153
- // one. `summary` outside `show` is a hard usage error (exit 2,
154
- // INVALID_SHAPE_VALUE), enforced at startup in src/cli.ts before any
155
- // command body runs.
153
+ // one. `summary` outside `show` falls back to `agent` with a warning
154
+ // (shapeForCommand in src/output/shapes.ts) `--shape` is a global flag,
155
+ // so a script that passes it to a mixed batch of commands still works.
156
156
  shape: {
157
157
  type: "string",
158
158
  description: "Output projection: human|agent|summary (global flag). 'agent' trims to agent-essential fields; " +
159
- "'summary' is only valid on 'akm show' (a usage error, exit 2, everywhere else). Default: human.",
159
+ "'summary' only has a dedicated projection on 'akm show' elsewhere it falls back to 'agent' with a " +
160
+ "warning. Default: human.",
160
161
  },
161
162
  output: {
162
163
  type: "string",
@@ -269,6 +270,17 @@ export function defineGroupCommand(def) {
269
270
  * document is written to that file instead of stdout (jsonl excepted — it is
270
271
  * a line-streaming protocol and always goes to stdout).
271
272
  */
273
+ /**
274
+ * Emit a result whose success is graded by an exit code. `ok` on the envelope
275
+ * and `process.exitCode` are set from the same value here, so the two cannot
276
+ * disagree (#918); `exitCode` undefined or 0 means success.
277
+ */
278
+ export function outputWithExitCode(command, result, exitCode) {
279
+ const failed = exitCode !== undefined && exitCode !== EXIT_CODES.SUCCESS;
280
+ output(command, { ...result, ok: !failed });
281
+ if (failed)
282
+ process.exitCode = exitCode;
283
+ }
272
284
  export function output(command, result) {
273
285
  const mode = getOutputMode();
274
286
  const shaped = shapeForCommand(command, result, mode.detail, mode.shape);
package/dist/cli.js CHANGED
@@ -57,7 +57,7 @@ process.on("uncaughtException", (err) => {
57
57
  });
58
58
  import fs from "node:fs";
59
59
  import { defineCommand, parseArgs, renderUsage, runCommand, showUsage } from "citty";
60
- import { findCittyTopLevelCommand, findCittyTopLevelCommandIndex, getParsedInvocation, parseAllFlagValues, resolveHelpMigrateVersionArg, setParsedInvocation, } from "./cli/invocation.js";
60
+ import { findCittyTopLevelCommandIndex, getParsedInvocation, parseAllFlagValues, resolveHelpMigrateVersionArg, setParsedInvocation, } from "./cli/invocation.js";
61
61
  import { retiredCommandHint } from "./cli/retired-commands.js";
62
62
  import { defineGroupCommand, EXIT_CODES, emitJsonError, GLOBAL_OUTPUT_ARGS, output, runWithJsonErrors, } from "./cli/shared.js";
63
63
  import { assertKnownFlags, closestMatch } from "./cli/unknown-flags.js";
@@ -284,6 +284,18 @@ const healthCommand = defineCommand({
284
284
  description: "Fetch the full report dataset: per-run rows, trend deltas vs the prior window, and the pending proposal queue. Renders as the rich report under --format md/html and as complete data under any other format.",
285
285
  default: false,
286
286
  },
287
+ // #914: citty strips any `--no-X` argument and treats it as negating `X`
288
+ // (see `parseArgs` in citty's dist), regardless of whether an arg literally
289
+ // named "no-X" is declared — declaring "no-probe" directly would silently
290
+ // never populate `args["no-probe"]` and the flag would do nothing. Declare
291
+ // the positive flag instead; `--no-probe` is citty's automatic negation of
292
+ // it (rendered in `--help` via `negativeDescription`).
293
+ probe: {
294
+ type: "boolean",
295
+ default: true,
296
+ description: "Probe default-llm-engine / configured-engines reachability (on by default).",
297
+ negativeDescription: "Skip the reachability probes (for an offline or air-gapped host).",
298
+ },
287
299
  },
288
300
  async run({ args }) {
289
301
  let resultStatus;
@@ -316,11 +328,12 @@ const healthCommand = defineCommand({
316
328
  const sinceIsDuration = args.since !== undefined && parseDuration(args.since, DURATION_UNITS) !== null;
317
329
  const implicitCompare = explicitWindows ? undefined : ((sinceIsDuration ? args.since : undefined) ?? "24h");
318
330
  const windowCompare = report ? (args["window-compare"] ?? implicitCompare) : args["window-compare"];
319
- const base = akmHealth({
331
+ const base = await akmHealth({
320
332
  since: args.since,
321
333
  groupBy: report ? "run" : groupBy,
322
334
  windowCompare,
323
335
  windows,
336
+ probe: args.probe !== false,
324
337
  });
325
338
  const reportCompare = windowCompare ??
326
339
  (explicitWindows
@@ -945,18 +958,7 @@ async function runCli() {
945
958
  emitJsonError(error);
946
959
  return;
947
960
  }
948
- // `--shape summary` is only meaningful on `akm show`. Reject it up front for
949
- // every other command so a write command (e.g. `akm proposal accept …`)
950
- // fails fast BEFORE performing its mutation, rather than throwing at
951
- // output-shaping time after the side effect has already happened. The
952
- // shape-registry gate in shapeForCommand() remains as defense-in-depth (and
953
- // covers the in-process test harness, which skips this startup block).
954
961
  const commandPath = resolveCittyCommandPath(main, process.argv.slice(2));
955
- const topLevelCommand = commandPath[0] ?? findCittyTopLevelCommand(process.argv.slice(2), MAIN_TOP_LEVEL_ARGS);
956
- if (getOutputMode().shape === "summary" && topLevelCommand !== "show") {
957
- emitJsonError(new UsageError("'--shape summary' is only valid on 'akm show'.", "INVALID_SHAPE_VALUE"));
958
- return;
959
- }
960
962
  // D7 — every command that renders through output() honours all six --format
961
963
  // values. The declared exempt set (src/output/format-exempt.ts) does not
962
964
  // render an envelope at all, so warn rather than pretend: silently ignoring
@@ -32,10 +32,18 @@ function rejectInvalidAgentRef(agentRef) {
32
32
  return;
33
33
  throw new UsageError(`agent expects an agent asset ref under agents/...; received ${JSON.stringify(agentRef)}.`, "INVALID_FLAG_VALUE");
34
34
  }
35
+ /**
36
+ * Dispatch a `--prompt` / `--prompt-stdin` task through the canonical command
37
+ * path. The prompt is a person's free text, not a template: it is sent to the
38
+ * agent verbatim (`inlineContentMode: "literal"`), so prose containing `}}`
39
+ * from compact JSON, a `$VAR`, a shell snippet, or an `@path` reaches the
40
+ * agent instead of being rejected as an unsupported template construct.
41
+ */
35
42
  async function delegateCanonicalCommand(options, seams, action) {
36
43
  const execute = seams.executeCommand ?? executeCommandInvocation;
37
44
  const result = await execute({
38
45
  action,
46
+ inlineContentMode: "literal",
39
47
  config: options.agentConfig,
40
48
  current: canonicalCurrent(options),
41
49
  });
@@ -10,6 +10,7 @@ import { 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";
13
+ import { warnOnce } from "../../core/warn.js";
13
14
  import { lookupBundleRef } from "../../indexer/indexer.js";
14
15
  import { deriveInstallations } from "../../indexer/installations.js";
15
16
  import { resolveEntryContentDir, resolveSourceEntries } from "../../indexer/search/search-source.js";
@@ -66,10 +67,10 @@ function implicitComponentForEntry(entry, config, realRoot) {
66
67
  continue;
67
68
  const canonicalRoot = realDirectory(component.root, `Canonical implicit source root for ${entry.bundleId}`);
68
69
  if (canonicalRoot !== realRoot) {
69
- throw new ConfigError(`Canonical implicit source root drift for ${JSON.stringify(entry.itemRef)}; the indexed root no longer matches the working bundle source.`, "INVALID_CONFIG_FILE", "Run `akm index --full` after changing AKM_BUNDLE_DIR or the default bundle directory.");
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.`);
70
71
  }
71
72
  if (component.adapter !== entry.adapterId) {
72
- throw new ConfigError(`Implicit adapter drift for ${JSON.stringify(entry.itemRef)}: the index records ${JSON.stringify(entry.adapterId)} but the canonical working source selects ${JSON.stringify(component.adapter)}.`, "INVALID_CONFIG_FILE", "Run `akm index --full` after changing the working source adapter.");
73
+ 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.`);
73
74
  }
74
75
  return Object.freeze({
75
76
  id: installation.id,
@@ -91,31 +92,33 @@ function componentForEntry(entry, config, realRoot) {
91
92
  const component = bundleComponentConfig(configured);
92
93
  const configuredAdapter = component?.adapter;
93
94
  if (configuredAdapter && configuredAdapter !== entry.adapterId) {
94
- throw new ConfigError(`Configured adapter drift for ${JSON.stringify(entry.itemRef)}: the index records ${JSON.stringify(entry.adapterId)} but the bundle config selects ${JSON.stringify(configuredAdapter)}.`, "INVALID_CONFIG_FILE", "Run `akm index --full` after changing a bundle adapter.");
95
+ warnOnce(`execution-source-adapter-drift:${entry.itemRef}`, `[command] Indexed adapter ${JSON.stringify(entry.adapterId)} for ${JSON.stringify(entry.itemRef)} no longer matches the bundle config's adapter ${JSON.stringify(configuredAdapter)}; re-resolving from live config. Run \`akm index --full\` to refresh it.`);
95
96
  }
96
97
  const configuredSource = bundlesToSourceEntries(config)?.find((source) => source.name === entry.bundleId);
97
98
  const configuredContentRoot = configuredSource ? resolveEntryContentDir(configuredSource) : undefined;
98
99
  if (!configuredSource || !configuredContentRoot) {
99
- throw new ConfigError(`Configured source for ${JSON.stringify(entry.itemRef)} no longer resolves to materialized content.`, "INVALID_CONFIG_FILE", "Restore or update the bundle, then run `akm index --full` before dispatching this asset.");
100
+ warnOnce(`execution-source-unresolved:${entry.itemRef}`, `[command] Configured source for ${JSON.stringify(entry.itemRef)} no longer resolves to materialized content; dispatching from the indexed location instead. Run \`akm index --full\` after restoring or updating the bundle.`);
100
101
  }
101
- const lexicalSourceRoot = path.resolve(configuredContentRoot);
102
- const lexicalConfiguredRoot = path.resolve(configuredContentRoot, component?.root ?? ".");
103
- if (!isLexicallyWithin(lexicalConfiguredRoot, lexicalSourceRoot)) {
104
- throw new ConfigError(`Configured component root for ${JSON.stringify(entry.itemRef)} resolves outside its materialized bundle source.`, "INVALID_CONFIG_FILE", "Keep component roots inside their owning bundle source, then run `akm index --full`.");
105
- }
106
- const configuredSourceRoot = realDirectory(lexicalSourceRoot, `Configured source root for ${entry.bundleId}`);
107
- let configuredRoot;
108
- try {
109
- configuredRoot = realDirectory(lexicalConfiguredRoot, `Configured component root for ${entry.bundleId}`);
110
- }
111
- catch {
112
- throw new ConfigError(`Configured ${JSON.stringify(configuredSource.type)} source for ${JSON.stringify(entry.itemRef)} is not materialized at its current component root.`, "INVALID_CONFIG_FILE", "Restore or update the bundle, then run `akm index --full` before dispatching this asset.");
113
- }
114
- if (!isWithin(configuredRoot, configuredSourceRoot)) {
115
- 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`.");
116
- }
117
- if (configuredRoot !== realRoot) {
118
- throw new ConfigError(`Configured source or component root drift for ${JSON.stringify(entry.itemRef)}; the indexed root no longer matches the ${JSON.stringify(configuredSource.type)} bundle source.`, "INVALID_CONFIG_FILE", "Run `akm index --full` after changing a bundle source or component root.");
102
+ else {
103
+ const lexicalSourceRoot = path.resolve(configuredContentRoot);
104
+ const lexicalConfiguredRoot = path.resolve(configuredContentRoot, component?.root ?? ".");
105
+ if (!isLexicallyWithin(lexicalConfiguredRoot, lexicalSourceRoot)) {
106
+ throw new ConfigError(`Configured component root for ${JSON.stringify(entry.itemRef)} resolves outside its materialized bundle source.`, "INVALID_CONFIG_FILE", "Keep component roots inside their owning bundle source, then run `akm index --full`.");
107
+ }
108
+ const configuredSourceRoot = realDirectory(lexicalSourceRoot, `Configured source root for ${entry.bundleId}`);
109
+ let configuredRoot;
110
+ try {
111
+ configuredRoot = realDirectory(lexicalConfiguredRoot, `Configured component root for ${entry.bundleId}`);
112
+ }
113
+ catch {
114
+ throw new ConfigError(`Configured ${JSON.stringify(configuredSource.type)} source for ${JSON.stringify(entry.itemRef)} is not materialized at its current component root.`, "INVALID_CONFIG_FILE", "Restore or update the bundle, then run `akm index --full` before dispatching this asset.");
115
+ }
116
+ if (!isWithin(configuredRoot, configuredSourceRoot)) {
117
+ 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
+ }
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
+ }
119
122
  }
120
123
  const writable = component?.writable ?? configured.writable ?? configured.path !== undefined;
121
124
  return Object.freeze({
@@ -1,22 +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 { UsageError } from "../../core/errors.js";
4
+ import { warn } from "../../core/warn.js";
5
5
  export const PORTABLE_ARGUMENTS_PLACEHOLDER = "$ARGUMENTS";
6
- const UNSUPPORTED_TEMPLATE_CONSTRUCTS = Object.freeze([
7
- { label: "$ARGUMENTS[N]", pattern: /\$ARGUMENTS\s*\[/u },
8
- { label: "```! ... ```", pattern: /(?:^|\r?\n)[\t ]*```!/u },
9
- { label: "!`...`", pattern: /!`/u },
10
- { label: "@file", pattern: /(?<![A-Za-z0-9._%+-])@(?:\.{0,2}\/)?[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*/u },
11
- { label: "$" + "{...}", pattern: /\$\{/u },
12
- { label: "$(...)", pattern: /\$\(/u },
13
- { label: "$N", pattern: /\$\d/u },
14
- { label: "$NAME", pattern: /\$[A-Za-z_][A-Za-z0-9_]*/u },
15
- { label: "{{...}}", pattern: /\{\{|\}\}/u },
16
- ]);
17
- function unsupportedTemplate(source, label) {
18
- return new UsageError(`Command ${JSON.stringify(source)} uses unsupported portable template construct ${label}.`, "INVALID_FLAG_VALUE", `AKM command execution supports only the literal ${PORTABLE_ARGUMENTS_PLACEHOLDER} placeholder. Invoke native-only templates through their owning tool instead.`);
19
- }
6
+ const INDEXED_ARGUMENTS_PATTERN = /\$ARGUMENTS\s*\[/u;
20
7
  /**
21
8
  * Validate the deliberately small portable command-template language.
22
9
  *
@@ -28,17 +15,8 @@ export function validatePortableCommandTemplate(template, source) {
28
15
  throw new TypeError("command template must be a string");
29
16
  if (typeof source !== "string" || source.length === 0)
30
17
  throw new TypeError("command source must be a string");
31
- // Check native extensions of the portable spelling before masking the one
32
- // supported token. AKM never interprets indexed native placeholders.
33
- const indexedArguments = UNSUPPORTED_TEMPLATE_CONSTRUCTS[0];
34
- if (indexedArguments?.pattern.test(template))
35
- throw unsupportedTemplate(source, indexedArguments.label);
36
- // Mask only the exact portable token. Everything else remains visible to the
37
- // unsupported-construct detectors, including `$ARGUMENTS_SUFFIX`.
38
- const portableMasked = template.replace(/\$ARGUMENTS(?![A-Za-z0-9_])/gu, "");
39
- for (const construct of UNSUPPORTED_TEMPLATE_CONSTRUCTS.slice(1)) {
40
- if (construct.pattern.test(portableMasked))
41
- throw unsupportedTemplate(source, construct.label);
18
+ if (INDEXED_ARGUMENTS_PATTERN.test(template)) {
19
+ warn(`Command ${JSON.stringify(source)} uses "$ARGUMENTS[N]", which akm does not support. Only the literal $ARGUMENTS placeholder is expanded; the indexed form is left as-is.`);
42
20
  }
43
21
  }
44
22
  /**
@@ -111,7 +111,10 @@ export const configCommand = defineGroupCommand({
111
111
  },
112
112
  }),
113
113
  set: defineJsonCommand({
114
- meta: { name: "set", description: "Set a configuration value by key" },
114
+ meta: {
115
+ name: "set",
116
+ description: "Set a configuration value by key; prints the resulting config with ok: true",
117
+ },
115
118
  args: {
116
119
  key: {
117
120
  type: "positional",
@@ -124,7 +127,7 @@ export const configCommand = defineGroupCommand({
124
127
  // writes don't pollute their host's output stream.
125
128
  silent: {
126
129
  type: "boolean",
127
- description: "Suppress the post-write config dump on stdout. Use from hooks and CI scripts; the write still happens and errors still print.",
130
+ description: "Suppress the post-write config dump on stdout entirely (prints nothing; exit code is the status). Use from hooks and CI scripts; the write still happens and errors still print.",
128
131
  default: false,
129
132
  },
130
133
  },
@@ -136,12 +139,15 @@ export const configCommand = defineGroupCommand({
136
139
  },
137
140
  }),
138
141
  unset: defineJsonCommand({
139
- meta: { name: "unset", description: "Unset an optional configuration key or whole embedding/engine section" },
142
+ meta: {
143
+ name: "unset",
144
+ description: "Unset an optional configuration key or whole embedding/engine section; prints the resulting config with ok: true",
145
+ },
140
146
  args: {
141
147
  key: { type: "positional", required: true, description: "Config key to unset" },
142
148
  silent: {
143
149
  type: "boolean",
144
- description: "Suppress the post-write config dump on stdout.",
150
+ description: "Suppress the post-write config dump on stdout entirely (prints nothing; exit code is the status).",
145
151
  default: false,
146
152
  },
147
153
  },
@@ -84,13 +84,20 @@ export function resolveEnvBinding(target, options = {}) {
84
84
  const dangerous = keys.filter(isDangerousEnvKey);
85
85
  if (dangerous.length > 0) {
86
86
  const detail = `Env "${envRef}" injects process-hijacking variable(s): ${dangerous.join(", ")}.`;
87
- const decision = decideDangerousEnvInjection({ dangerousKeys: dangerous, thirdParty: Boolean(source.registryId) });
87
+ const decision = decideDangerousEnvInjection({
88
+ dangerousKeys: dangerous,
89
+ thirdParty: Boolean(source.registryId),
90
+ allowInsecure: options.allowInsecure,
91
+ });
88
92
  if (decision === "block") {
89
93
  throw new UsageError(`Refusing to inject env from a third-party stash. ${detail}\n` +
90
- ` Review the file, then copy the values into a first-party env if you trust them.`, "INVALID_FLAG_VALUE");
94
+ ` Review the file, then copy the values into a first-party env if you trust them, ` +
95
+ `or pass --allow-insecure once you have.`, "INVALID_FLAG_VALUE");
91
96
  }
92
97
  if (decision === "warn") {
93
- warn(`${detail} Injecting anyway (first-party stash).`);
98
+ warn(options.allowInsecure && source.registryId
99
+ ? `${detail} Injecting anyway (--allow-insecure).`
100
+ : `${detail} Injecting anyway (first-party stash).`);
94
101
  }
95
102
  }
96
103
  // Audit trail: keys only, never values.