akm-cli 0.9.16-alpha.1 → 0.9.16
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.
- package/CHANGELOG.md +56 -132
- package/dist/assets/hints/cli-hints-full.md +13 -6
- package/dist/assets/tasks/core/index-refresh.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +3 -6
- package/dist/cli/retired-commands.js +0 -4
- package/dist/cli/unknown-flags.js +3 -36
- package/dist/commands/env/env-binding.js +4 -4
- package/dist/commands/env/env-cli.js +3 -3
- package/dist/commands/improve/collapse-detector.js +2 -2
- package/dist/commands/improve/consolidate.js +4 -6
- package/dist/commands/improve/improve-cli.js +20 -15
- package/dist/commands/improve/reflect.js +23 -2
- package/dist/commands/lint/base-linter.js +9 -0
- package/dist/commands/lint/env-key-rules.js +2 -2
- package/dist/commands/proposal/propose.js +15 -1
- package/dist/commands/proposal/repository.js +3 -12
- package/dist/commands/proposal/validators/proposal-quality-validators.js +40 -3
- package/dist/commands/proposal/validators/proposal-validators.js +5 -4
- package/dist/commands/read/curate.js +44 -34
- package/dist/commands/read/search.js +35 -54
- package/dist/commands/read/show.js +21 -2
- package/dist/commands/registry-cli.js +5 -5
- package/dist/commands/sources/add-cli.js +59 -16
- package/dist/commands/sources/bundle-cli.js +35 -11
- package/dist/commands/sources/bundle-config-ops.js +30 -0
- package/dist/commands/sources/dangerous-env-audit.js +4 -4
- package/dist/commands/sources/info.js +8 -8
- package/dist/commands/sources/installed-stashes.js +55 -61
- package/dist/commands/sources/source-add.js +39 -38
- package/dist/commands/sources/source-manage.js +34 -12
- package/dist/commands/sources/stash-cli.js +111 -119
- package/dist/commands/sources/stash-skeleton.js +6 -3
- package/dist/commands/tasks/explain.js +4 -1
- package/dist/commands/tasks/tasks-cli.js +31 -9
- package/dist/commands/tasks/tasks.js +239 -194
- package/dist/commands/tasks/validate.js +20 -32
- package/dist/core/activation-policy.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +8 -35
- package/dist/core/adapter/adapters/akm-metadata.js +1 -11
- package/dist/core/adapter/execution-source.js +10 -29
- package/dist/core/asset/asset-placement.js +0 -35
- package/dist/core/config/config-schema.js +64 -8
- package/dist/core/config/config-sources.js +96 -2
- package/dist/core/config/config.js +190 -24
- package/dist/core/config/legacy-source-shape-shim.js +9 -0
- package/dist/core/config/schema/embedding.js +30 -7
- package/dist/core/config/schema/execution.js +23 -0
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/scheduler.js +20 -0
- package/dist/core/config/schema/search.js +10 -12
- package/dist/core/config/schema/sources-bundles.js +32 -1
- package/dist/core/content-safety.js +52 -0
- package/dist/core/errors.js +2 -5
- package/dist/core/maintenance-barrier.js +11 -13
- package/dist/core/paths.js +11 -0
- package/dist/core/run-lock.js +2 -5
- package/dist/core/state/migrations.js +1 -26
- package/dist/core/state-db.js +27 -63
- package/dist/core/type-presentation.js +1 -1
- package/dist/core/write-source.js +13 -8
- package/dist/indexer/bundle-identity-guard.js +45 -8
- package/dist/indexer/ensure-index.js +0 -5
- package/dist/indexer/index-db-contention.js +56 -0
- package/dist/indexer/index-rebuild-lock.js +73 -0
- package/dist/indexer/index-written-assets.js +171 -133
- package/dist/indexer/indexer.js +1621 -458
- package/dist/indexer/lookup/adapter-concept-owner.js +5 -19
- package/dist/indexer/materialize-embeddings.js +785 -0
- package/dist/indexer/passes/dir-staleness.js +161 -0
- package/dist/indexer/passes/metadata.js +1 -18
- package/dist/indexer/scan/drain-dir.js +70 -27
- package/dist/indexer/search/db-search.js +89 -373
- package/dist/indexer/search/ranking-contributors.js +16 -21
- package/dist/indexer/search/ranking.js +57 -135
- package/dist/indexer/search/search-source.js +29 -11
- package/dist/integrations/agent/execution-lowering.js +3 -2
- package/dist/integrations/agent/execution-preparation.js +32 -1
- package/dist/integrations/agent/prompts.js +1 -1
- package/dist/integrations/agent/request-lowering.js +3 -2
- package/dist/llm/client.js +3 -11
- package/dist/llm/embedder.js +3 -10
- package/dist/llm/embedders/remote.js +104 -133
- package/dist/llm/feature-gate.js +2 -4
- package/dist/llm/rerank-client.js +3 -3
- package/dist/output/html-render.js +2 -1
- package/dist/output/shapes/passthrough.js +2 -1
- package/dist/output/stdout.js +24 -0
- package/dist/output/text/command-format.js +13 -19
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/index.js +2 -5
- package/dist/output/text.js +4 -3
- package/dist/registry/resolve.js +37 -10
- package/dist/scripts/akm-migrate-node.js +15197 -11351
- package/dist/scripts/akm-migrate.js +15514 -11668
- package/dist/setup/semantic-assets.js +2 -2
- package/dist/setup/setup.js +3 -3
- package/dist/setup/steps/connection.js +2 -3
- package/dist/setup/steps/tasks.js +29 -36
- package/dist/sources/providers/git-install.js +17 -11
- package/dist/sources/providers/git-provider.js +12 -5
- package/dist/sources/providers/git-stash.js +38 -16
- package/dist/sources/snapshot-fetchers/website-ingest.js +3 -3
- package/dist/storage/repositories/embedding-salvage-repository.js +184 -0
- package/dist/storage/repositories/index-connection.js +3 -1
- package/dist/storage/repositories/index-entries-repository.js +68 -77
- package/dist/storage/repositories/index-entry-schema.js +25 -16
- package/dist/storage/repositories/index-fts-repository.js +263 -29
- package/dist/storage/repositories/index-meta-repository.js +29 -0
- package/dist/storage/repositories/index-schema.js +122 -115
- package/dist/storage/repositories/index-utility-repository.js +1 -1
- package/dist/storage/repositories/index-vec-repository.js +435 -22
- package/dist/tasks/activation-config.js +90 -0
- package/dist/tasks/backends/cron.js +9 -0
- package/dist/tasks/backends/launchd.js +1 -0
- package/dist/tasks/backends/schtasks.js +2 -0
- package/dist/tasks/embedded.js +4 -5
- package/dist/tasks/scheduler-binding.js +2 -2
- package/dist/tasks/scheduler-sync-preview.js +8 -1
- package/dist/tasks/scheduler-sync.js +19 -10
- package/dist/tasks/source/parse-task-source.js +10 -113
- package/dist/tasks/source/project-v4.js +2 -2
- package/dist/tasks/source/task-source-v4.js +4 -12
- package/dist/tasks/source/task-to-v3.js +4 -12
- package/dist/tasks/source/task-to-v4.js +40 -7
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.15.md +36 -34
- package/docs/migration/release-notes/0.9.16.md +60 -98
- package/docs/migration/release-notes/README.md +0 -5
- package/docs/migration/v0.9.1-to-v0.9.2.md +6 -9
- package/docs/reference/cli.md +124 -122
- package/docs/reference/configuration.md +137 -133
- package/docs/reference/data-and-telemetry.md +1 -2
- package/docs/reference/tasks.md +34 -29
- package/package.json +1 -1
- package/schemas/akm-config.json +170 -6
- package/schemas/akm-task.json +1 -2
- package/dist/commands/sources/index-status.js +0 -99
- package/dist/core/hash.js +0 -18
- package/dist/indexer/drain.js +0 -306
- package/dist/indexer/embedding-identity.js +0 -20
- package/dist/indexer/enrich.js +0 -260
- package/dist/indexer/reconcile.js +0 -890
- package/dist/indexer/scan/parse-file.js +0 -66
- package/dist/indexer/units/unit.js +0 -159
- package/dist/llm/embedders/provider-limits.js +0 -288
- package/dist/storage/repositories/files-repository.js +0 -181
- package/dist/storage/repositories/units-repository.js +0 -510
package/CHANGELOG.md
CHANGED
|
@@ -6,147 +6,71 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
-
## [0.9.16
|
|
9
|
+
## [0.9.16] - 2026-09-22
|
|
10
10
|
|
|
11
|
-
###
|
|
11
|
+
### Fixed
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
the
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
- **Result documents larger than 64 KiB are no longer truncated on a piped
|
|
14
|
+
stdout.** `akm show`/`search`/`config get … | python3 -c …` (or `| head`, or
|
|
15
|
+
any other pipe) returned exactly 65,536 bytes — the Linux pipe-buffer size —
|
|
16
|
+
producing unparseable JSON, while the same command with `--output <file>`
|
|
17
|
+
wrote the complete document. The two stdout writers (`deliverRendered` for
|
|
18
|
+
json/yaml/text/md/html, `outputJsonl` for jsonl) used `console.log`, and on
|
|
19
|
+
Bun `console.log` issues a single `write(2)` against a non-blocking fd 1 and
|
|
20
|
+
silently discards whatever the kernel did not accept; a pipe accepts at most
|
|
21
|
+
one buffer's worth. Both now go through `writeStdout`
|
|
22
|
+
(`src/output/stdout.ts`), which uses `process.stdout.write` — that handles
|
|
23
|
+
the short write correctly, and the queued remainder keeps the process alive
|
|
24
|
+
until it drains. Byte-for-byte output is unchanged on every format; only the
|
|
25
|
+
transport moved.
|
|
26
26
|
|
|
27
|
-
###
|
|
27
|
+
### Added
|
|
28
28
|
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
pending set is exactly the unit hashes with no vector under the active
|
|
41
|
-
embedding identity, packed against the provider's own probed context
|
|
42
|
-
window and slot count, written into a single vector table
|
|
43
|
-
(`units`/`units_vec`) keyed by `(unit_hash, identity)`. An unchanged file
|
|
44
|
-
or unit is never re-derived or re-embedded again — not on a rename, not
|
|
45
|
-
on `akm index --full`, not on a future generation bump — because
|
|
46
|
-
everything is keyed by content hash and observed provider identity, never
|
|
47
|
-
by a row id or a config string.
|
|
48
|
-
- **Every write path indexes what it just wrote, inline.** `remember`,
|
|
49
|
-
`import`, extract's session-asset capture, `source clone`, and proposal
|
|
50
|
-
accept each reconcile and drain exactly the paths/units they touched, in
|
|
51
|
-
the same call as the write, with no lock probe and no background reindex
|
|
52
|
-
spawn.
|
|
53
|
-
- **Search is one query over `units`**, scoring lexical evidence by BM25
|
|
54
|
-
magnitude through the calibrated transform the repository already had and
|
|
55
|
-
combining it with semantic distance on the proven 0.7/0.3 split. Reciprocal
|
|
56
|
-
rank fusion was tried first and measured worse than the path it replaced
|
|
57
|
-
(0.918 against 0.933 on the `curate-golden` fixture, unchanged by splitting
|
|
58
|
-
or weighting the lists), because a rank cannot tell a strong match from a
|
|
59
|
-
weak one; the shipped scoring measures 0.936 with no banned-above-required
|
|
60
|
-
hits. The semantic-only `minScore` floor is gone, type filters now apply in
|
|
61
|
-
SQL before the candidate cap, and the exact/prefix/relaxed ladder tops up to
|
|
62
|
-
the candidate budget instead of stopping at the first non-empty tier. The
|
|
63
|
-
tier a hit came from is also ranking evidence: a unit matching every query
|
|
64
|
-
token outranks one matching a subset, because the calibrated BM25 transform
|
|
65
|
-
compresses even a sixfold magnitude difference into a few thousandths — far
|
|
66
|
-
less than any single ranking contributor — so tier decides across tiers and
|
|
67
|
-
magnitude decides within one.
|
|
68
|
-
- **`akm index --full`** no longer drops anything first: it forces every
|
|
69
|
-
walked file to be re-parsed (skipping the unchanged-file shortcut) but
|
|
70
|
-
updates each file's existing row in place, keeping its id, vectors, and
|
|
71
|
-
learned utility scores. **`akm index --reembed`** now means "drop the
|
|
72
|
-
active embedding identity's vectors, then re-embed every unit from
|
|
73
|
-
scratch."
|
|
74
|
-
- **`akm index --skip-if-locked` is deprecated and does nothing.** Index
|
|
75
|
-
runs no longer take a rebuild lock — every write is a short, idempotent,
|
|
76
|
-
content-addressed transaction, so two concurrent runs converge instead of
|
|
77
|
-
contending. Passing the flag prints one deprecation warning; kept only so
|
|
78
|
-
an existing script does not fail on an unknown flag.
|
|
79
|
-
- **The derived `index.db` generation changes from v23 to v24.** The first
|
|
80
|
-
read (or explicit `akm index`) after upgrade re-derives `entries` and
|
|
81
|
-
every unit from files, and embeds the full corpus once against the
|
|
82
|
-
configured provider, since the new unit vector store does not carry
|
|
83
|
-
forward the superseded per-entry vector tables. See the [0.9.16 migration
|
|
84
|
-
note](docs/migration/release-notes/0.9.16.md) for the cost, stated
|
|
85
|
-
plainly.
|
|
29
|
+
- **Scheduled execution is now granted by host-local config and bound to the
|
|
30
|
+
approved source installation.** `scheduler.enabled` records
|
|
31
|
+
`{kind, ref, sourceId}` entries written by `akm task enable`; authored task
|
|
32
|
+
frontmatter cannot enable itself. `akm migrate apply` performs the explicit,
|
|
33
|
+
backed-up conversion of older grants, and runtime config points unmigrated
|
|
34
|
+
installations to that command instead of silently inventing authority.
|
|
35
|
+
- **Executable assets now run beneath a host-owned tool ceiling.** Local
|
|
36
|
+
`execution.allowedTools` config caps asset tool requests. Workspace,
|
|
37
|
+
environment, and opaque runtime selection are no longer accepted from asset
|
|
38
|
+
frontmatter, and a transport that cannot enforce a non-empty resolved tool
|
|
39
|
+
policy fails before dispatch.
|
|
86
40
|
|
|
87
|
-
###
|
|
41
|
+
### Changed
|
|
88
42
|
|
|
89
|
-
- **
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
-
|
|
105
|
-
`
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
error and is simply ignored.
|
|
111
|
-
- **The `"ready-js"` semantic-search status.** Named a pure-JS
|
|
112
|
-
cosine-similarity fallback for when `sqlite-vec` was unavailable; the new
|
|
113
|
-
unit vector store has no BLOB-table fallback to fall back to, so nothing
|
|
114
|
-
produces that value any more.
|
|
43
|
+
- **Unscoped `akm task sync` reconciles every enabled bundle.** It refreshes
|
|
44
|
+
the native scheduler from the latest locally enabled task/workflow sources
|
|
45
|
+
and removes attributable bindings for bundles that have been disabled.
|
|
46
|
+
Removing a bundle also revokes its scheduler grants.
|
|
47
|
+
- **Bundle activation and identity are consistent across the CLI.** A disabled
|
|
48
|
+
bundle is inert for reads, writes, indexing, execution, and scheduling;
|
|
49
|
+
explicit lifecycle updates remain available. Physical-root identity rejects
|
|
50
|
+
duplicate or symlink-aliased bundle registrations and prevents an
|
|
51
|
+
`AKM_BUNDLE_DIR` alias from reactivating disabled content.
|
|
52
|
+
- **Shared config inheritance now separates portable policy from host
|
|
53
|
+
authority.** Source/default ownership, registries, scheduler and execution
|
|
54
|
+
grants, credentials, executable arguments/workspace, setup/experimental
|
|
55
|
+
state, reranker connections, and publication hooks stay local. Every
|
|
56
|
+
bundle-relative `extends` hop is checked by real path containment.
|
|
57
|
+
- **Unsafe overrides now name one risk each.** Use
|
|
58
|
+
`--allow-insecure-transport` for reviewed plain HTTP and
|
|
59
|
+
`--allow-dangerous-env-keys` for reviewed process-hijacking environment
|
|
60
|
+
keys; the former combined `--allow-insecure` switch is removed.
|
|
61
|
+
- **Bundle/source resolution carries explicit default and priority state.**
|
|
62
|
+
Search, show, write targeting, registry installation, and configuration
|
|
63
|
+
mutation no longer infer ownership or trust from array position.
|
|
115
64
|
|
|
116
65
|
### Fixed
|
|
117
66
|
|
|
118
|
-
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
migration ledger and its "does this file have any other tables" check as
|
|
126
|
-
two separate statements, so a sibling's bootstrap committing between them
|
|
127
|
-
looked exactly like a legacy unversioned database and was refused outright
|
|
128
|
-
— 2 of 1200 racing trials. Both initializations are now single atomic
|
|
129
|
-
units, measured at zero failures in 840 and 1200 trials respectively, idle
|
|
130
|
-
and under load. Only a genuinely contended run still fails, as
|
|
131
|
-
`INDEX_DB_CONTENDED`/`STATE_DB_CONTENDED` at exit 75, the documented
|
|
132
|
-
retry-shortly contract. An already-initialized database takes the same
|
|
133
|
-
unlocked path it always did.
|
|
134
|
-
- **`akm show <memory>` no longer fails when that memory has an inferred
|
|
135
|
-
`.derived` twin.** It exited 2 with `RESOURCE_ALREADY_EXISTS` ("multiple
|
|
136
|
-
physical owners"), so once `akm improve` derived a memory — its ordinary
|
|
137
|
-
output — the base ref stopped being usable, and the read path that did not
|
|
138
|
-
fail served the twin's content instead of the memory's. `.derived` is a
|
|
139
|
-
provenance marker on the same identity and the placement layer always
|
|
140
|
-
declared that the plain file wins; the physical-owner lookup now honours
|
|
141
|
-
that instead of discarding it. Genuinely ambiguous cases, including two
|
|
142
|
-
case-only spellings of one name and `env`'s co-equal `.env`/`default.env`
|
|
143
|
-
pair, still fail loudly and unchanged. Present since before 0.9.15.
|
|
144
|
-
- **A derived memory no longer outranks the memory it was derived from.** A
|
|
145
|
-
twin's own filename contributed a `derived` tag that minted a synthetic
|
|
146
|
-
alias, and the machine-written `source:` provenance backref was folded into
|
|
147
|
-
search hints, together handing the twin a flat 0.42 of ranking credit for
|
|
148
|
-
bookkeeping no author wrote — enough to beat a memory whose description
|
|
149
|
-
matched the query verbatim. Neither field earns ranking credit any more.
|
|
67
|
+
- Protected generated improve content from credential echoes, redacted bodies,
|
|
68
|
+
and run-only scaffolding, while exercising the real bounded engine probe.
|
|
69
|
+
- Repaired degraded sqlite-vec mirrors, preserved scheduler intent across
|
|
70
|
+
synchronization, kept explicit setup choices and rollback serialization
|
|
71
|
+
stable, and tolerated valid sharded startup contention.
|
|
72
|
+
- Made git, website, npm, and filesystem bundle add/update/remove workflows
|
|
73
|
+
converge on the same lifecycle and dangerous-environment audit behavior.
|
|
150
74
|
|
|
151
75
|
## [0.9.15] - 2026-09-10
|
|
152
76
|
|
|
@@ -264,6 +264,7 @@ akm bundle add @scope/pkg # From npm (managed)
|
|
|
264
264
|
akm bundle add owner/repo # From GitHub (managed)
|
|
265
265
|
akm bundle add ./path/to/local/bundle # Local directory
|
|
266
266
|
akm bundle add git@github.com:org/repo.git --provider git --name my-skills --writable
|
|
267
|
+
akm bundle add https://github.com/org/private.git --provider git --credential '$GIT_READ_TOKEN'
|
|
267
268
|
akm registry add https://skills.sh --name skills.sh --provider skills-sh # Add the skills.sh registry
|
|
268
269
|
akm registry remove skills.sh # Remove the skills.sh registry
|
|
269
270
|
akm bundle list # List all sources
|
|
@@ -271,9 +272,13 @@ akm bundle list --kind git # Filter by provider (files
|
|
|
271
272
|
akm bundle remove <target> # Remove by id, ref, path, or name
|
|
272
273
|
akm bundle update --all # Update all managed sources
|
|
273
274
|
akm bundle update <target> --force # Force re-download
|
|
274
|
-
akm bundle update <target> --allow-
|
|
275
|
+
akm bundle update <target> --allow-dangerous-env-keys # Approve reviewed dangerous env keys
|
|
276
|
+
akm bundle update --all --skip-if-locked # Scheduled refresh; exit 0 on DB contention
|
|
275
277
|
```
|
|
276
278
|
|
|
279
|
+
Git bundles refresh only when `akm bundle update` runs. Schedule that command
|
|
280
|
+
when automatic refresh is desired.
|
|
281
|
+
|
|
277
282
|
## Registries
|
|
278
283
|
|
|
279
284
|
```sh
|
|
@@ -357,7 +362,9 @@ file plus one `sync` is a complete workflow.
|
|
|
357
362
|
```sh
|
|
358
363
|
akm task add nightly-improve --schedule "@daily" --command "akm improve --strategy default"
|
|
359
364
|
akm task add briefing --schedule "0 9 * * *" --prompt "Draft the morning briefing" # Inline command task
|
|
360
|
-
akm task
|
|
365
|
+
akm task enable <bundle>//tasks/<id> # Enable locally and sync that bundle
|
|
366
|
+
akm task disable <bundle>//tasks/<id> # Disable locally and unschedule it
|
|
367
|
+
akm task sync # Reconcile activated refs from every enabled configured bundle
|
|
361
368
|
akm task sync --rebind # Also re-pin the scheduler's akm binary/spelling
|
|
362
369
|
akm task doctor # Scheduler binding + runtime eligibility diagnosis
|
|
363
370
|
akm task history # Recent run rows (status, timing)
|
|
@@ -370,10 +377,10 @@ Task files use task source v4 (`version: 4`). There is no `akm:` options bag
|
|
|
370
377
|
or `on:` block — every control (`schedule`, `timeout`, `engine`, `model`,
|
|
371
378
|
`redact`, `maxSteps`, `maxRetries`, …) is a top-level key now. Typed
|
|
372
379
|
`inputs:` declarations and a bounded `output:` schema work like a
|
|
373
|
-
workflow's (`output:` replaces v3's `akm.outputSchema`).
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
380
|
+
workflow's (`output:` replaces v3's `akm.outputSchema`). Task source never
|
|
381
|
+
controls activation: use `akm task enable` / `disable`, which update the
|
|
382
|
+
host-local config allow-list and sync. To remove one, delete the YAML and run
|
|
383
|
+
`akm task sync` — the scheduler entry is unbound. Top-level
|
|
377
384
|
`timeout:` may be `null` (disable the invocation timer) or a duration/number
|
|
378
385
|
overriding the selected engine invocation timeout. Preview old task-v2/v3
|
|
379
386
|
conversion with `akm migrate apply --dry-run`.
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
version: 4
|
|
2
2
|
run: akm improve --strategy catchup --skip-if-locked --require-engines
|
|
3
3
|
description: Manual recovery — consolidation + triage drain (run on demand via `akm task run akm-improve-catchup`)
|
|
4
|
-
# Manual-recovery task:
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
schedule:
|
|
8
|
-
- cron: "0 4 * * *"
|
|
9
|
-
enabled: false
|
|
4
|
+
# Manual-recovery task: setup offers it disabled by default. `akm task run`
|
|
5
|
+
# still works manually; `akm task enable` opts this host into the schedule.
|
|
6
|
+
schedule: "0 4 * * *"
|
|
@@ -62,8 +62,6 @@ const RETIRED_COMMAND_HINTS = {
|
|
|
62
62
|
"workflow report": "`akm workflow report` was removed — the external-driver protocol is gone; `akm workflow run <target>` dispatches and records units itself.",
|
|
63
63
|
"config show": "`akm config show` was removed in 0.9 — use `akm config list`.",
|
|
64
64
|
"config validate": "`akm config validate` was removed in 0.9 — the config file is validated on every load.",
|
|
65
|
-
"task enable": "`akm task enable` was removed in 0.9 — set `enabled: true` in the task YAML, then `akm task sync`.",
|
|
66
|
-
"task disable": "`akm task disable` was removed in 0.9 — set `enabled: false` in the task YAML, then `akm task sync`.",
|
|
67
65
|
"task init": "`akm task init` was removed in 0.9 — `akm setup` seeds the default schedules.",
|
|
68
66
|
"task show": "there is no `task show` — task files are indexed assets; use `akm show <ref>`.",
|
|
69
67
|
"task remove": "there is no `task remove` — delete the task YAML, then run `akm task sync` to unbind it.",
|
|
@@ -104,8 +102,6 @@ export function retiredCommandHint(parentPath, attempted) {
|
|
|
104
102
|
*/
|
|
105
103
|
const RETIRED_FLAG_HINTS = {
|
|
106
104
|
"index --background": "`--background` was removed in 0.9 — the flag never actually backgrounded; use `--quiet`.",
|
|
107
|
-
"index --clean": "`--clean` was removed in the index redesign — every `akm index` run now removes stale entries as part of reconcile, the same work `--clean` used to opt into.",
|
|
108
|
-
"index --dry-run": "`--dry-run` was removed in the index redesign along with `--clean`, the only flag it ever modified.",
|
|
109
105
|
"setup --detect-only": "`--detect-only` was removed in 0.9 — environment detection runs inside `akm setup`; `akm info` reports the configured capabilities.",
|
|
110
106
|
"setup --reset-recommended": "`--reset-recommended` was removed in 0.9 — `akm setup` now offers to apply recommended defaults interactively.",
|
|
111
107
|
"proposal extract --watch": "`--watch` was removed in 0.9 — schedule `akm proposal extract --auto` as a task instead.",
|
|
@@ -30,28 +30,6 @@ import { cittyComparableName, findCittyTopLevelCommandIndex, toAliasArray, } fro
|
|
|
30
30
|
import { retiredFlagHint } from "./retired-commands.js";
|
|
31
31
|
/** Flags citty implements itself, which no command declares. */
|
|
32
32
|
const IMPLICIT_FLAGS = ["help", "h", "version", "v"];
|
|
33
|
-
/**
|
|
34
|
-
* Keys `GLOBAL_OUTPUT_ARGS` (cli/shared.ts) contributes. Every leaf AND every
|
|
35
|
-
* group re-declares these so citty's parser consumes their values (see that
|
|
36
|
-
* constant's own doc) — so their mere presence in a group's own `args` never
|
|
37
|
-
* means the group has a real business use for its own flags; only a key
|
|
38
|
-
* beyond this set does. Duplicated here (not imported) to avoid coupling this
|
|
39
|
-
* shared scanner to `cli/shared.ts`'s own dependency surface; keep in sync if
|
|
40
|
-
* `GLOBAL_OUTPUT_ARGS` gains or loses a key.
|
|
41
|
-
*/
|
|
42
|
-
const GLOBAL_OUTPUT_ARG_KEYS = new Set(["format", "detail", "shape", "output", "quiet", "verbose"]);
|
|
43
|
-
/**
|
|
44
|
-
* Whether a group declares any flag of its own beyond the global output
|
|
45
|
-
* scaffold — i.e. whether its bare invocation (no subcommand token) runs a
|
|
46
|
-
* REAL default body that reads those flags (`akm index`'s `full`/`reembed`),
|
|
47
|
-
* as opposed to the canonical bare-group usage error every other group falls
|
|
48
|
-
* through to (`defineGroupCommand`, cli/shared.ts) — a `UsageError` either
|
|
49
|
-
* way, whose own declared args (if any) exist only so `--help` documents them,
|
|
50
|
-
* never so a body reads them.
|
|
51
|
-
*/
|
|
52
|
-
function hasOwnBusinessArgs(cmd) {
|
|
53
|
-
return Object.keys(cmd.args ?? {}).some((key) => !GLOBAL_OUTPUT_ARG_KEYS.has(key));
|
|
54
|
-
}
|
|
55
33
|
/**
|
|
56
34
|
* Retired flags whose commands still diagnose them THEMSELVES, with a message
|
|
57
35
|
* that names the replacement ("`--scope` was removed, use `--filter`",
|
|
@@ -124,20 +102,9 @@ function collectKnownArgs(root, rawArgs) {
|
|
|
124
102
|
break;
|
|
125
103
|
const idx = findCittyTopLevelCommandIndex(args, (cmd.args ?? {}));
|
|
126
104
|
const token = idx >= 0 ? args[idx] : undefined;
|
|
127
|
-
// A group with no subcommand token: citty
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// is the canonical bare-group usage error, and its own declared args (if
|
|
131
|
-
// any) exist only for `--help`, so a flag meant for the subcommand the
|
|
132
|
-
// caller forgot to type must not be misreported as "unknown" — stand
|
|
133
|
-
// down, exactly as before. `akm index` is the one group with a REAL
|
|
134
|
-
// default body that reads its own flags (`full`/`reembed`), so `akm index
|
|
135
|
-
// --background` must still be rejected even though `index` also carries a
|
|
136
|
-
// real subcommand (`status`) — `hasOwnBusinessArgs` is what tells the two
|
|
137
|
-
// apart.
|
|
138
|
-
if (token === undefined) {
|
|
139
|
-
return { names, valueFlags, booleanFlags, displayNames, path, resolved: hasOwnBusinessArgs(cmd) };
|
|
140
|
-
}
|
|
105
|
+
// A group with no subcommand token: citty reports "no command specified".
|
|
106
|
+
if (token === undefined)
|
|
107
|
+
return { names, valueFlags, booleanFlags, displayNames, path, resolved: false };
|
|
141
108
|
const sub = subCommands[token];
|
|
142
109
|
// An unrecognized token: citty reports the unknown command, which is the
|
|
143
110
|
// real problem — its flags are beside the point.
|
|
@@ -87,16 +87,16 @@ export function resolveEnvBinding(target, options = {}) {
|
|
|
87
87
|
const decision = decideDangerousEnvInjection({
|
|
88
88
|
dangerousKeys: dangerous,
|
|
89
89
|
thirdParty: Boolean(source.registryId),
|
|
90
|
-
|
|
90
|
+
allowDangerousEnvKeys: options.allowDangerousEnvKeys,
|
|
91
91
|
});
|
|
92
92
|
if (decision === "block") {
|
|
93
93
|
throw new UsageError(`Refusing to inject env from a third-party stash. ${detail}\n` +
|
|
94
94
|
` Review the file, then copy the values into a first-party env if you trust them, ` +
|
|
95
|
-
`or pass --allow-
|
|
95
|
+
`or pass --allow-dangerous-env-keys once you have.`, "INVALID_FLAG_VALUE");
|
|
96
96
|
}
|
|
97
97
|
if (decision === "warn") {
|
|
98
|
-
warn(options.
|
|
99
|
-
? `${detail} Injecting anyway (--allow-
|
|
98
|
+
warn(options.allowDangerousEnvKeys && source.registryId
|
|
99
|
+
? `${detail} Injecting anyway (--allow-dangerous-env-keys).`
|
|
100
100
|
: `${detail} Injecting anyway (first-party stash).`);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -214,7 +214,7 @@ async function runEnvInjected(target, opts) {
|
|
|
214
214
|
const { values: envValues } = resolveEnvBinding(target, {
|
|
215
215
|
only: opts.only,
|
|
216
216
|
except: opts.except,
|
|
217
|
-
|
|
217
|
+
allowDangerousEnvKeys: opts.allowDangerousEnvKeys,
|
|
218
218
|
});
|
|
219
219
|
const mergedEnv = buildChildEnv(process.env, {
|
|
220
220
|
clean: opts.clean === true,
|
|
@@ -283,7 +283,7 @@ const envRunCommand = defineJsonCommand({
|
|
|
283
283
|
type: "string",
|
|
284
284
|
description: "When used with --clean, also inherit these parent env vars (comma-separated). Ignored without --clean.",
|
|
285
285
|
},
|
|
286
|
-
"allow-
|
|
286
|
+
"allow-dangerous-env-keys": {
|
|
287
287
|
type: "boolean",
|
|
288
288
|
description: "Allow injecting a process-hijacking variable (e.g. LD_PRELOAD, GIT_SSH_COMMAND) from a third-party stash, which otherwise blocks. Use only after explicitly reviewing the env file.",
|
|
289
289
|
default: false,
|
|
@@ -295,7 +295,7 @@ const envRunCommand = defineJsonCommand({
|
|
|
295
295
|
except: parseKeyListFlag(args.except),
|
|
296
296
|
clean: args.clean === true,
|
|
297
297
|
inherit: parseKeyListFlag(args.inherit) ?? [],
|
|
298
|
-
|
|
298
|
+
allowDangerousEnvKeys: args["allow-dangerous-env-keys"] === true,
|
|
299
299
|
});
|
|
300
300
|
},
|
|
301
301
|
});
|
|
@@ -36,10 +36,10 @@ import { getImproveProcessConfig } from "../../core/config/config.js";
|
|
|
36
36
|
import { appendEvent } from "../../core/events.js";
|
|
37
37
|
import { withStateDb } from "../../core/state-db.js";
|
|
38
38
|
import { warn } from "../../core/warn.js";
|
|
39
|
-
import { searchEntriesLexical } from "../../indexer/search/db-search.js";
|
|
40
39
|
import { deactivateCanarySet, getActiveCanaries, getCanariesBySetId, insertCanaries, insertCycleMetrics, listActiveCanarySetIds, queryRecentCycleMetrics, } from "../../storage/repositories/canaries-repository.js";
|
|
41
40
|
import { closeDatabase, openExistingDatabase } from "../../storage/repositories/index-connection.js";
|
|
42
41
|
import { getAllEntries } from "../../storage/repositories/index-entries-repository.js";
|
|
42
|
+
import { searchFts } from "../../storage/repositories/index-fts-repository.js";
|
|
43
43
|
import { computeBigramDiversity, DEFAULT_MAX_GENERATION } from "./anti-collapse.js";
|
|
44
44
|
import { getAllRankScores } from "./salience.js";
|
|
45
45
|
// ── Defaults (mirrored in config-schema.ts ImproveCollapseDetectorSchema) ────
|
|
@@ -189,7 +189,7 @@ export function normHash(text) {
|
|
|
189
189
|
* Returns the 0-based rank of the first hit, or -1.
|
|
190
190
|
*/
|
|
191
191
|
function scoreCanary(indexDb, canary, k) {
|
|
192
|
-
const results =
|
|
192
|
+
const results = searchFts(indexDb, canary.query, k);
|
|
193
193
|
const anchorConceptId = canaryConceptId(canary.anchor_ref);
|
|
194
194
|
for (let i = 0; i < Math.min(results.length, k); i++) {
|
|
195
195
|
const r = results[i];
|
|
@@ -24,7 +24,7 @@ import { callStructured, preflightStructuredLlmRunner } from "../../llm/structur
|
|
|
24
24
|
import { getBodyEmbeddings, upsertBodyEmbeddings } from "../../storage/repositories/embeddings-repository.js";
|
|
25
25
|
import { closeDatabase, openExistingDatabase, openReadonlyExistingDatabase, } from "../../storage/repositories/index-connection.js";
|
|
26
26
|
import { findEntryIdByRef, getAllEntries, getEntryById } from "../../storage/repositories/index-entries-repository.js";
|
|
27
|
-
import { getNeighborsByEntryId } from "../../storage/repositories/
|
|
27
|
+
import { getNeighborsByEntryId } from "../../storage/repositories/index-vec-repository.js";
|
|
28
28
|
import { isProposalSkipped, listProposals, listProposalsReadOnly, proposalContent, } from "../proposal/repository.js";
|
|
29
29
|
import { hasSupersededStatus, validateProposalFrontmatter } from "../proposal/validators/proposal-quality-validators.js";
|
|
30
30
|
import { DEFAULT_RANDOM_CLUSTER_FRACTION } from "./anti-collapse.js";
|
|
@@ -1394,11 +1394,9 @@ export function narrowToIncrementalCandidates(memories, since, warnings, neighbo
|
|
|
1394
1394
|
const id = findEntryIdByRef(db, conceptIdFromTypeName("memory", m.name));
|
|
1395
1395
|
if (id === undefined)
|
|
1396
1396
|
continue;
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
// directly (no `+ 1` for self, no self-filter here).
|
|
1401
|
-
for (const hit of getNeighborsByEntryId(db, id, neighborsPerChanged)) {
|
|
1397
|
+
for (const hit of getNeighborsByEntryId(db, id, neighborsPerChanged + 1)) {
|
|
1398
|
+
if (hit.id === id)
|
|
1399
|
+
continue;
|
|
1402
1400
|
const entry = getEntryById(db, hit.id);
|
|
1403
1401
|
if (!entry)
|
|
1404
1402
|
continue;
|
|
@@ -15,7 +15,7 @@ import { redactSensitiveText } from "../../core/redaction.js";
|
|
|
15
15
|
import { clearLogFile, setLogFile, warn } from "../../core/warn.js";
|
|
16
16
|
import { resolveWriteTarget } from "../../core/write-source.js";
|
|
17
17
|
import { collectEngineCredentialValues } from "../../integrations/agent/engine-resolution.js";
|
|
18
|
-
import {
|
|
18
|
+
import { probeLlmReachable } from "../../llm/client.js";
|
|
19
19
|
import { getOutputMode } from "../../output/context.js";
|
|
20
20
|
import { deliverRendered } from "../../output/html-render.js";
|
|
21
21
|
import { akmImprove, resolveImproveReadSource } from "./improve.js";
|
|
@@ -122,27 +122,32 @@ function collectRequiredEngineTargets(plan) {
|
|
|
122
122
|
* `--require-engines` field re-test (#957): the static check above only
|
|
123
123
|
* proves an engine is configured and credentialed — it cannot see a dead
|
|
124
124
|
* endpoint. A field run against an unreachable engine sat silent for
|
|
125
|
-
* minutes instead of hitting the documented exit-78 path.
|
|
126
|
-
*
|
|
127
|
-
* `
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
125
|
+
* minutes instead of hitting the documented exit-78 path. Exercise the real
|
|
126
|
+
* model completion path with a tiny response and a three-second bound. The
|
|
127
|
+
* `/models` endpoint used by the lightweight health check is deliberately
|
|
128
|
+
* insufficient here: a gateway can list a model while its upstream completion
|
|
129
|
+
* route is dead (#980). Deduplicate by endpoint + model, not endpoint alone,
|
|
130
|
+
* because model backends behind one gateway can fail independently.
|
|
131
131
|
*/
|
|
132
|
-
async function assertRequiredEnginesReachable(plan, probeReachable =
|
|
132
|
+
async function assertRequiredEnginesReachable(plan, probeReachable = (connection) => probeLlmReachable(connection, 3_000)) {
|
|
133
133
|
const targets = collectRequiredEngineTargets(plan);
|
|
134
134
|
if (targets.length === 0)
|
|
135
135
|
return;
|
|
136
|
-
const
|
|
137
|
-
const probed = await Promise.all(targets.map(async (target) =>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
const probesByConnection = new Map();
|
|
137
|
+
const probed = await Promise.all(targets.map(async (target) => {
|
|
138
|
+
const key = `${target.connection.endpoint.replace(/\/+$/, "")}|${target.connection.model}`;
|
|
139
|
+
let pending = probesByConnection.get(key);
|
|
140
|
+
if (!pending) {
|
|
141
|
+
pending = probeReachable(target.connection);
|
|
142
|
+
probesByConnection.set(key, pending);
|
|
143
|
+
}
|
|
144
|
+
return { ...target, reach: await pending };
|
|
145
|
+
}));
|
|
141
146
|
const unreachable = probed.filter((item) => !item.reach.reachable);
|
|
142
147
|
if (unreachable.length === 0)
|
|
143
148
|
return;
|
|
144
149
|
const lines = unreachable.map((item) => ` - ${item.process} (engine "${item.engine}", ${item.connection.endpoint}): ${item.reach.error ?? "did not respond"}`);
|
|
145
|
-
throw new ConfigError(`--require-engines: ${unreachable.length} improve process${unreachable.length === 1 ? "" : "es"} cannot run because ${unreachable.length === 1 ? "its" : "their"} engine
|
|
150
|
+
throw new ConfigError(`--require-engines: ${unreachable.length} improve process${unreachable.length === 1 ? "" : "es"} cannot run because ${unreachable.length === 1 ? "its" : "their"} engine completion path is not reachable:\n${lines.join("\n")}`, "LLM_NOT_CONFIGURED");
|
|
146
151
|
}
|
|
147
152
|
/**
|
|
148
153
|
* `--show-prompt` (#952): render the composed reflect prompt for one asset ref
|
|
@@ -249,7 +254,7 @@ export const improveCommand = defineCommand({
|
|
|
249
254
|
},
|
|
250
255
|
"skip-if-locked": {
|
|
251
256
|
type: "boolean",
|
|
252
|
-
description: "If another improve run already holds the lock, skip gracefully (exit 0) instead of failing with 'already running' (exit
|
|
257
|
+
description: "If another improve run already holds the lock, skip gracefully (exit 0) instead of failing with 'already running' (exit 78). Use for high-frequency scheduled runs so they don't pile up failures while a longer run is in progress.",
|
|
253
258
|
default: false,
|
|
254
259
|
},
|
|
255
260
|
"require-engines": {
|
|
@@ -29,6 +29,7 @@ import { parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
|
29
29
|
import { conceptIdFromTypeName, parseRefInput } from "../../core/asset/resolve-ref.js";
|
|
30
30
|
import { DESCRIPTION_MAX_CHARS, requiresDescription } from "../../core/authoring-rules.js";
|
|
31
31
|
import { loadConfig } from "../../core/config/config.js";
|
|
32
|
+
import { generatedContentRejection, stripReflectPromptScaffolding } from "../../core/content-safety.js";
|
|
32
33
|
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
33
34
|
import { appendEvent, readEvents } from "../../core/events.js";
|
|
34
35
|
import { lintLessonContent } from "../../core/lesson-lint.js";
|
|
@@ -511,7 +512,12 @@ export function sanitizeReflectPayload(payload, sourceContent, targetRef) {
|
|
|
511
512
|
mergedFm[field] = sourceFm[field];
|
|
512
513
|
}
|
|
513
514
|
}
|
|
514
|
-
const
|
|
515
|
+
const withoutAppendedFrontmatter = stripAppendedFrontmatter(rawLlmBody.replace(/^\s+/, ""));
|
|
516
|
+
const promptScaffolding = stripReflectPromptScaffolding(withoutAppendedFrontmatter);
|
|
517
|
+
const cleanedBody = promptScaffolding.content;
|
|
518
|
+
if (promptScaffolding.stripped) {
|
|
519
|
+
warnings.push('Removed echoed run-only "Avoid These Patterns" guidance from the proposed asset body (#963).');
|
|
520
|
+
}
|
|
515
521
|
// #636 — deterministic description fallback (reflect-side belt-and-suspenders).
|
|
516
522
|
// If the type requires a `description` and the merged frontmatter is still
|
|
517
523
|
// MISSING one (source had none AND the model didn't author one), derive a
|
|
@@ -1881,7 +1887,22 @@ export async function akmReflect(options = {}) {
|
|
|
1881
1887
|
// draft paths.
|
|
1882
1888
|
cleanupReflectDrafts(draftPathsToCleanup);
|
|
1883
1889
|
}
|
|
1884
|
-
|
|
1890
|
+
const unsafeContent = generatedContentRejection(payload.content, redactSensitiveText(payload.content, sensitiveValues));
|
|
1891
|
+
if (unsafeContent) {
|
|
1892
|
+
emitReflectFailed("parse_error", "parse_error", options.ref, {
|
|
1893
|
+
...(result.exitCode !== null ? { exitCode: result.exitCode } : {}),
|
|
1894
|
+
});
|
|
1895
|
+
return {
|
|
1896
|
+
schemaVersion: 2,
|
|
1897
|
+
ok: false,
|
|
1898
|
+
reason: "parse_error",
|
|
1899
|
+
error: unsafeContent,
|
|
1900
|
+
...(options.ref ? { ref: options.ref } : {}),
|
|
1901
|
+
engine: engineName,
|
|
1902
|
+
exitCode: result.exitCode,
|
|
1903
|
+
...reflectNoticeFields(executionNotices),
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1885
1906
|
const refFailure = validateReflectPayloadRef({
|
|
1886
1907
|
payload,
|
|
1887
1908
|
result,
|
|
@@ -41,6 +41,7 @@ import { checkUnquotedDescriptionColon } from "../../core/asset/frontmatter-lint
|
|
|
41
41
|
import { isArchivedRelPath } from "../../core/asset/memory-archive.js";
|
|
42
42
|
import { conceptIdFromTypeName, typeNameFromConceptId } from "../../core/asset/resolve-ref.js";
|
|
43
43
|
import { localDateStamp } from "../../core/common.js";
|
|
44
|
+
import { containsRedactedContent, REDACTED_CONTENT_MARKER } from "../../core/content-safety.js";
|
|
44
45
|
import { findFenceRegions } from "./markdown-insertion.js";
|
|
45
46
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
46
47
|
/** Fold physically wrapped prose the same way a YAML plain scalar does. */
|
|
@@ -603,6 +604,14 @@ export function runBaseChecks(ctx) {
|
|
|
603
604
|
.map((v) => String(v).trim())
|
|
604
605
|
.filter(Boolean);
|
|
605
606
|
const shouldRun = (issueType) => !lintSkip.includes(issueType);
|
|
607
|
+
if (shouldRun("redacted-content") && containsRedactedContent(currentRaw)) {
|
|
608
|
+
issues.push({
|
|
609
|
+
file: ctx.relPath,
|
|
610
|
+
issue: "redacted-content",
|
|
611
|
+
detail: `asset contains ${REDACTED_CONTENT_MARKER}; restore the original non-secret prose from version history`,
|
|
612
|
+
fixed: false,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
606
615
|
// ── 1. unquoted-colon ──────────────────────────────────────────────────
|
|
607
616
|
if (shouldRun("unquoted-colon")) {
|
|
608
617
|
const unquotedColonDetail = checkUnquotedDescriptionColon(ctx.frontmatter);
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Enforcement scope:
|
|
13
13
|
* - `akm lint` reports findings as `dangerous-env-key` (non-blocking warn).
|
|
14
|
-
* - `akm bundle add` BLOCKS install unless `--allow-
|
|
14
|
+
* - `akm bundle add` BLOCKS install unless `--allow-dangerous-env-keys` is set (or, on TTY,
|
|
15
15
|
* the user explicitly confirms at the prompt).
|
|
16
16
|
* - Local env writes do NOT consult this list — by design, the operator may
|
|
17
17
|
* legitimately store any key locally. The gate exists only for third-party
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* invoked by many interactive tools and are a documented RCE vector when
|
|
23
23
|
* sourced from untrusted environments. They will also flag on benign files
|
|
24
24
|
* where the operator legitimately wants to set their editor — accept the
|
|
25
|
-
* FP and bypass with `--allow-
|
|
25
|
+
* FP and bypass with `--allow-dangerous-env-keys` after review.
|
|
26
26
|
*/
|
|
27
27
|
import fs from "node:fs";
|
|
28
28
|
import { listKeys } from "../env/env.js";
|