akm-cli 0.9.10 → 0.9.12
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 +226 -0
- package/STABILITY.md +28 -15
- package/dist/assets/hints/cli-hints-full.md +1 -1
- package/dist/assets/improve-strategies/consolidate.json +1 -1
- package/dist/assets/improve-strategies/default.json +1 -1
- package/dist/assets/improve-strategies/thorough.json +1 -2
- package/dist/cli/shared.js +16 -4
- package/dist/cli.js +15 -13
- package/dist/commands/agent/agent-dispatch.js +8 -0
- package/dist/commands/command/execution-source-loader.js +25 -22
- package/dist/commands/command/portable-template.js +4 -26
- package/dist/commands/config-cli.js +10 -4
- package/dist/commands/env/env-binding.js +10 -3
- package/dist/commands/env/env-cli.js +7 -0
- package/dist/commands/env/secret-cli.js +15 -4
- package/dist/commands/health/checks.js +209 -78
- package/dist/commands/health/improve-metrics.js +12 -0
- package/dist/commands/health.js +16 -4
- package/dist/commands/improve/distill/quality-gate.js +15 -7
- package/dist/commands/improve/distill.js +28 -12
- package/dist/commands/improve/eval-cases.js +9 -2
- package/dist/commands/improve/execution.js +1 -2
- package/dist/commands/improve/extract.js +82 -56
- package/dist/commands/improve/improve-strategies.js +26 -8
- package/dist/commands/improve/improve.js +32 -4
- package/dist/commands/improve/loop-stages.js +13 -3
- package/dist/commands/improve/preparation.js +9 -6
- package/dist/commands/improve/reflect.js +61 -77
- package/dist/commands/lint/index.js +3 -1
- package/dist/commands/migrate-cli.js +6 -4
- package/dist/commands/proposal/drain-policies.js +22 -2
- package/dist/commands/proposal/repository.js +4 -4
- package/dist/commands/proposal/validators/proposal-quality-validators.js +23 -2
- package/dist/commands/proposal/validators/proposals.js +10 -19
- package/dist/commands/read/show.js +42 -31
- package/dist/commands/registry-cli.js +4 -2
- package/dist/commands/sources/init.js +4 -8
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-clone.js +5 -7
- package/dist/commands/sources/sources-cli.js +3 -5
- package/dist/commands/tasks/tasks-cli.js +36 -12
- package/dist/commands/tasks/tasks.js +38 -35
- package/dist/commands/tasks/validate.js +186 -0
- package/dist/commands/url-checker.js +75 -16
- package/dist/commands/workflow-cli.js +17 -15
- package/dist/core/activation-policy.js +31 -3
- package/dist/core/adapter/execution-source.js +39 -11
- package/dist/core/asset/stash-meta.js +7 -41
- package/dist/core/bundle-id.js +7 -1
- package/dist/core/common.js +8 -17
- package/dist/core/config/config-schema.js +3 -23
- package/dist/core/config/config-walker.js +50 -4
- package/dist/core/config/config.js +21 -5
- package/dist/core/config/legacy-source-shape-shim.js +79 -0
- package/dist/core/config/schema/embedding.js +2 -2
- package/dist/core/config/schema/engines.js +19 -2
- package/dist/core/config/schema/index-config.js +19 -21
- package/dist/core/config/schema/primitives.js +21 -10
- package/dist/core/config/schema/sources-bundles.js +1 -6
- package/dist/core/errors.js +2 -3
- package/dist/core/improve-result.js +8 -0
- package/dist/core/improve-types.js +17 -0
- package/dist/core/json-schema.js +1 -11
- package/dist/core/maintenance-barrier.js +17 -2
- package/dist/core/paths.js +124 -15
- package/dist/core/state/migrations.js +28 -0
- package/dist/core/state-db.js +28 -1
- package/dist/core/write-source.js +6 -6
- package/dist/indexer/bundle-identity-guard.js +3 -0
- package/dist/indexer/ensure-index.js +5 -0
- package/dist/indexer/indexer.js +11 -3
- package/dist/indexer/lookup/adapter-concept-owner.js +14 -3
- package/dist/indexer/passes/metadata.js +16 -5
- package/dist/indexer/search/search-fields.js +1 -30
- package/dist/indexer/search/search-source.js +3 -2
- package/dist/integrations/agent/engine-resolution.js +107 -4
- package/dist/integrations/agent/execution-lowering.js +15 -2
- package/dist/integrations/agent/model-map.js +16 -10
- package/dist/integrations/agent/prompts.js +13 -6
- package/dist/integrations/agent/runner-dispatch.js +16 -3
- package/dist/integrations/agent/runner.js +2 -0
- package/dist/integrations/lockfile.js +22 -7
- package/dist/llm/client.js +16 -0
- package/dist/llm/index-passes.js +3 -2
- package/dist/output/shapes/passthrough.js +10 -3
- package/dist/output/shapes.js +5 -3
- package/dist/output/text/workflow-format.js +8 -1
- package/dist/scripts/akm-migrate-node.js +2028 -1613
- package/dist/scripts/akm-migrate.js +2027 -1612
- package/dist/setup/setup.js +14 -21
- package/dist/sources/include.js +150 -20
- package/dist/sources/providers/git-install.js +14 -12
- package/dist/sources/providers/git-provider.js +3 -3
- package/dist/sources/snapshot-fetchers/website-ingest.js +54 -16
- package/dist/sources/website-url.js +12 -4
- package/dist/storage/engines/sqlite-migrations.js +40 -10
- package/dist/storage/like-pattern.js +7 -0
- package/dist/storage/repositories/extract-sessions-repository.js +23 -0
- package/dist/storage/repositories/index-connection.js +27 -10
- package/dist/storage/repositories/index-entry-schema.js +19 -2
- package/dist/storage/repositories/index-schema.js +30 -9
- package/dist/storage/repositories/proposals-repository.js +2 -1
- package/dist/storage/repositories/task-history-repository.js +14 -7
- package/dist/storage/repositories/workflow-runs-repository.js +15 -1
- package/dist/storage/sqlite-read-snapshot.js +11 -9
- package/dist/tasks/backends/cron.js +34 -5
- package/dist/tasks/backends/launchd.js +23 -26
- package/dist/tasks/backends/schtasks.js +50 -3
- package/dist/tasks/frozen-script.js +2 -0
- package/dist/tasks/prepare/prepare.js +2 -7
- package/dist/tasks/prepare/script-capture.js +38 -6
- package/dist/tasks/schedule.js +154 -13
- package/dist/tasks/scheduler-sync.js +51 -25
- package/dist/tasks/source/task-source-v3-frozen.js +0 -1
- package/dist/tasks/source/task-source-v4.js +0 -1
- package/dist/workflows/exec/child-workflow.js +2 -3
- package/dist/workflows/exec/dispatch-redaction.js +21 -7
- package/dist/workflows/exec/exec-unit.js +3 -4
- package/dist/workflows/exec/run-workflow.js +19 -10
- package/dist/workflows/exec/step-work.js +35 -56
- package/dist/workflows/freeze/resolve-steps.js +19 -11
- package/dist/workflows/freeze/source-freeze.js +7 -0
- package/dist/workflows/freeze/targets/child-workflow.js +12 -18
- package/dist/workflows/freeze/targets/command.js +14 -2
- package/dist/workflows/ir/environment-v4.js +4 -2
- package/dist/workflows/ir/freeze-v4.js +2 -5
- package/dist/workflows/ir/plan-hash.js +0 -3
- package/dist/workflows/ir/schema-v4.js +14 -9
- package/dist/workflows/ir/schema.js +1 -3
- package/dist/workflows/resource-limits.js +35 -48
- package/dist/workflows/runtime/plan-classifier.js +89 -41
- package/dist/workflows/runtime/run-outputs.js +1 -21
- package/dist/workflows/runtime/runs.js +76 -150
- package/dist/workflows/source-files.js +28 -54
- package/dist/workflows/source-ir/program.js +2 -2
- package/dist/workflows/source-ir/semantics.js +5 -23
- package/docs/integration/bundling-akm.md +1 -1
- package/docs/migration/v0.8-to-v0.9.md +32 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +20 -0
- package/docs/reference/cli.md +105 -22
- package/docs/reference/configuration.md +12 -2
- package/docs/reference/data-and-telemetry.md +1 -1
- package/docs/reference/tasks.md +8 -0
- package/package.json +1 -1
- package/schemas/akm-config.json +11 -8
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,232 @@ 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.12] - 2026-09-03
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`akm health` probes LLM engine reachability (#914).** `default-llm-engine`
|
|
12
|
+
and `configured-engines` now send one bounded `GET` to the endpoint's
|
|
13
|
+
`/models` route (3 s timeout, one probe per distinct endpoint per
|
|
14
|
+
invocation, no cross-run cache) instead of only checking that a credential is
|
|
15
|
+
present. Any HTTP response counts as reachable, so a cold local server is
|
|
16
|
+
never asked to load a model just to be checked. An unreachable default LLM engine is a hard `fail` naming the
|
|
17
|
+
connection error; an unreachable non-default engine is a `warn`. Pass
|
|
18
|
+
`--no-probe` on an offline or air-gapped host to keep the credential-only
|
|
19
|
+
verdict; the message then says reachability was not probed.
|
|
20
|
+
- **`akm proposal extract` reports the engine it resolved (#913).** The
|
|
21
|
+
envelope carries `engine` and `engineKind` (`llm`, `sdk`, or `agent`), every
|
|
22
|
+
`sessions[]` entry carries `engine`, and the `extract_sessions_seen` ledger
|
|
23
|
+
metadata records it, so "which engine did that run actually use" is one
|
|
24
|
+
field instead of config archaeology. `akm health`'s `active-improve-strategy`
|
|
25
|
+
check names the engine each improve process resolved to, which makes a
|
|
26
|
+
strategy-level `engine` pin that shadows `defaults.llmEngine` visible.
|
|
27
|
+
- **`akm workflow run <ref>` says when it resumes, and `--new` starts fresh
|
|
28
|
+
(#919).** Resolving a ref to an already-active run in the current scope is
|
|
29
|
+
unchanged, but the `workflow-run` envelope now carries `resumed: true` and
|
|
30
|
+
the text output leads with `resuming existing run <id> for <ref>; pass --new
|
|
31
|
+
to start a fresh run`. `--new` starts a second run and leaves the active one
|
|
32
|
+
untouched. `status`, `abandon`, `resume`, and `run <id>` all accept a unique
|
|
33
|
+
run-id prefix of eight or more characters.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- **A run that skipped every session for an infrastructure reason is visible
|
|
38
|
+
in the extract envelope (#912).** `warnings[]` gains one aggregate line per
|
|
39
|
+
infrastructure skip reason (`llm_unavailable`, `read_failed`, `exception`,
|
|
40
|
+
`locked_concurrent`), for example `25 of 25 sessions skipped: llm_unavailable
|
|
41
|
+
(engine "default")`, and a `skipReasons` count map is present whenever
|
|
42
|
+
`sessionsSkipped > 0`. `ok` keeps meaning "the command ran" and the exit code
|
|
43
|
+
does not change; that meaning is now written down on the type.
|
|
44
|
+
- **`akm health`'s `session-extraction` check reads the extraction ledger
|
|
45
|
+
(#914).** It used to read only `improve_runs`, which the hook-driven
|
|
46
|
+
`akm proposal extract --session-id` never writes, so a plugin-driven machine
|
|
47
|
+
reported "not active" as `pass` forever. It now derives its verdict from the
|
|
48
|
+
last seven days of `extract_sessions_seen`: `unknown` when nothing was
|
|
49
|
+
recorded, `warn` naming the reason and engine when every session was skipped
|
|
50
|
+
or failed for an infrastructure reason (`llm_unavailable`, `read_failed`,
|
|
51
|
+
`exception`, `locked_concurrent`), otherwise `pass` with per-outcome counts.
|
|
52
|
+
- **Every passthrough success envelope carries `ok: true` (#918).**
|
|
53
|
+
`akm config set` and `akm config unset` printed the resulting config with no
|
|
54
|
+
`ok` field while their failure envelope had `ok: false`, so a caller
|
|
55
|
+
branching on `.ok` read success as failure. The shared passthrough stamp now
|
|
56
|
+
adds `ok: true` when a result has no `ok` of its own; commands whose exit
|
|
57
|
+
code grades the outcome (`task sync`, `task sync --dry-run`, `task prune`,
|
|
58
|
+
`workflow run`, `upgrade`, and the `migrate` subcommands) set `ok` and the
|
|
59
|
+
exit code from the same value, so the two cannot disagree. `--silent` still
|
|
60
|
+
prints nothing.
|
|
61
|
+
- **The unsupported-plan error names the real situation (#919).** A frozen
|
|
62
|
+
workflow plan with an `irVersion` above the current one no longer reports
|
|
63
|
+
"pre-irVersion-5 ... after the 0.9.2 upgrade"; it says the plan was probably
|
|
64
|
+
written by a newer akm. The reported `irVersion 111` did not reproduce
|
|
65
|
+
against this tree (the column is only ever written as `5` and no path
|
|
66
|
+
rewrites it on a source edit); a regression test pins that editing a
|
|
67
|
+
workflow source leaves its in-flight run executable.
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
|
|
71
|
+
- **`akm proposal accept` no longer rewrites your content.** Two "repairs" ran
|
|
72
|
+
before validation: one deleted every body line matching `description:` or
|
|
73
|
+
`when_to_use:`, the other deleted every `---` in a body that had
|
|
74
|
+
frontmatter. Both fired inside fenced code blocks, so any asset documenting
|
|
75
|
+
frontmatter — a note about akm, Claude Code skills, Jekyll, Hugo — was
|
|
76
|
+
silently gutted on accept, and the rewritten bytes were saved back over the
|
|
77
|
+
original in the proposals database. Nothing was printed. Both repairs are
|
|
78
|
+
gone; the truncated-description repair, which only ever rewrote a
|
|
79
|
+
frontmatter value, stays.
|
|
80
|
+
- **Prose-quality findings no longer block `proposal accept`.** A description
|
|
81
|
+
that read like a heading, an odd number of backticks, or a reflect revision
|
|
82
|
+
outside the size ratio refused the promotion and told the user to "fix the
|
|
83
|
+
proposal payload and try again" — but there is no `akm proposal edit` and
|
|
84
|
+
`accept` has no `--force`, so the only way out was hand-editing the
|
|
85
|
+
proposals database. These findings are now reported as warnings on a
|
|
86
|
+
command a human typed. Structural defects that genuinely cannot be written
|
|
87
|
+
(empty content, an unparseable ref, malformed frontmatter, a broken
|
|
88
|
+
workflow shape) still block.
|
|
89
|
+
- **akm no longer refuses to open a state database migrated by a newer akm.**
|
|
90
|
+
Two akm versions sharing one data directory is a supported deployment — a
|
|
91
|
+
bundled CLI beside a newer global install — and the old binary was bricked
|
|
92
|
+
for every command that touches `state.db`, not degraded. It protected
|
|
93
|
+
nothing: an older binary's entire migration registry is already applied, so
|
|
94
|
+
it has no pending migration to run. It now opens, warns once naming the
|
|
95
|
+
migrations it does not know, and reads and writes the tables it knows. A
|
|
96
|
+
ledger that genuinely diverges (a migration this akm has was never applied
|
|
97
|
+
and something else was applied in its place) is still refused.
|
|
98
|
+
- **`akm agent --prompt` no longer rejects prose that looks like a template.**
|
|
99
|
+
A prompt was validated as if it were a portable command template, so any
|
|
100
|
+
prompt containing `}}` from compact JSON, a `$VAR`, a `${...}`, a `$(...)`
|
|
101
|
+
shell snippet, an `@path`, or a `` !` `` was rejected with "unsupported
|
|
102
|
+
portable template construct" before the agent started. akm substitutes
|
|
103
|
+
nothing into a prompt, so it is now sent verbatim. The template language is
|
|
104
|
+
unchanged for stored command files, which are templates.
|
|
105
|
+
|
|
106
|
+
- **The 0.9.2 `claude-code` -> `claude` harness rename left the ledgers split
|
|
107
|
+
(#915).** State migration `027-extract-sessions-seen-harness-rename` moves
|
|
108
|
+
`extract_sessions_seen` and `workflow_runs.agent_harness` rows off the old
|
|
109
|
+
key, keeping a session already recorded under `claude` as the authoritative
|
|
110
|
+
row, and empties the old key space. `PERSISTED_HARNESS_IDS` in the harness
|
|
111
|
+
registry is now pinned by a test so the next rename cannot ship without a
|
|
112
|
+
migration. Scripts querying the ledger by `claude-code` will find it empty
|
|
113
|
+
after upgrade; see the 0.9.1 -> 0.9.2 migration guide.
|
|
114
|
+
|
|
115
|
+
### Changed — refusals that now degrade
|
|
116
|
+
|
|
117
|
+
A repo-wide audit reviewed every defensive refusal in the codebase against
|
|
118
|
+
three tests: has it demonstrably helped a real user, does its failure mode cost
|
|
119
|
+
less than the hazard it guards, and is the hazard already gated behind a
|
|
120
|
+
deliberate human command. Refusals that failed those tests were removed or
|
|
121
|
+
downgraded to a warning, in that order of preference. Machinery that prevents
|
|
122
|
+
data loss or corruption — atomic writes, backups, write-path validation, path
|
|
123
|
+
containment — was explicitly out of scope and is unchanged.
|
|
124
|
+
|
|
125
|
+
The user-visible effect is that akm stops aborting on conditions it can
|
|
126
|
+
survive. Highlights:
|
|
127
|
+
|
|
128
|
+
- **Config load no longer bricks every command over one bad key.** Retired
|
|
129
|
+
vocabulary (`profiles`, `llm`, `agent`, `features`, `stashes`,
|
|
130
|
+
`modelAliases`, `bindings`, top-level `writable`) warns and passes through,
|
|
131
|
+
and the pre-`bundles` `stashDir`/`sources[]`/`installed[]` shape folds into
|
|
132
|
+
the current shape in memory. An unknown `config set` key warns and stores.
|
|
133
|
+
- **A newer or unfamiliar state.db migration ledger no longer refuses to
|
|
134
|
+
open.** A ledger carrying migrations this binary does not know warns once and
|
|
135
|
+
degrades; only a genuinely inconsistent ledger still aborts. This is what let
|
|
136
|
+
a bundled older akm keep working against a newer host's data directory.
|
|
137
|
+
A `plan_ir_version` of NULL (a row predating the column) decodes normally.
|
|
138
|
+
- **Free text is no longer validated as code.** `akm agent` accepts prompts
|
|
139
|
+
containing `{{`/`}}`; inline workflow `akm/command` content and portable
|
|
140
|
+
command templates accept `$HOME`, `@file`, `${...}` and the rest as the prose
|
|
141
|
+
they are. Only `$ARGUMENTS[N]`, which merely looks like the one placeholder
|
|
142
|
+
akm expands, still warns.
|
|
143
|
+
- **Version skew stops being treated as corruption.** A newer index is left
|
|
144
|
+
alone rather than wiped, a stale indexed workflow identity falls back, and
|
|
145
|
+
frozen-plan spine drift from a formatting change between releases warns
|
|
146
|
+
instead of marking every in-flight run corrupt.
|
|
147
|
+
- **Deliberate commands stop being second-guessed.** `--since` bypasses
|
|
148
|
+
`extract`'s per-run cap the way `--force` already did; `workflow create
|
|
149
|
+
--force` no longer also demands `--reset`; scheduler writes, `setup --dir`
|
|
150
|
+
and `bundle create --dir` under transient paths warn instead of refusing.
|
|
151
|
+
- **Symlinks are followed on read paths** (task sources, `models.json`, stash
|
|
152
|
+
meta, `akm.include` entries) with realpath containment doing the actual
|
|
153
|
+
safety work, rather than being refused outright.
|
|
154
|
+
- **Blocks that had no escape hatch got one, or got removed.** `env run` gained
|
|
155
|
+
`--allow-insecure` for the third-party dangerous-key block that previously
|
|
156
|
+
had no bypass; `registry add` accepts a credentialed URL with a warning that
|
|
157
|
+
redacts the credential; promotion lint findings on `proposal accept` report
|
|
158
|
+
instead of blocking, since there is no `proposal edit` and no `accept
|
|
159
|
+
--force` to work around them.
|
|
160
|
+
- **Limits that bounded nothing real were deleted.** Workflow plan and embedded
|
|
161
|
+
child-plan byte caps, step-evidence tombstoning, search-text truncation, and
|
|
162
|
+
the JSON-schema node budget are gone; the akm.lock acquisition budget went
|
|
163
|
+
from 300 ms to ~30 s with backoff and a "waiting for another akm process"
|
|
164
|
+
notice.
|
|
165
|
+
- **A cron line too long for vixie-cron now spills into a wrapper script**
|
|
166
|
+
instead of refusing the install. The length limit stays, because a truncated
|
|
167
|
+
cron line would execute a partial command.
|
|
168
|
+
|
|
169
|
+
Guards that passed the three tests were kept and documented, including the
|
|
170
|
+
registry-URL credential inspection limit, the third-party dangerous-key block
|
|
171
|
+
itself, `MAX_SCHTASKS_TRIGGERS`, and every path-containment check.
|
|
172
|
+
|
|
173
|
+
## [0.9.11] - 2026-09-03
|
|
174
|
+
|
|
175
|
+
### Added
|
|
176
|
+
|
|
177
|
+
- **`akm task validate <path>` reports what `akm task sync` would say about one
|
|
178
|
+
task file (#907).** It parses a single file by filesystem path, without a
|
|
179
|
+
bundle, a concept ref, or a configured engine, and reports `valid`,
|
|
180
|
+
`converts` (a v2/v3 source the migrator converts deterministically),
|
|
181
|
+
`blocked` (needs a human decision), `invalid`, or `not-a-task`, with the
|
|
182
|
+
reason sync would give. It runs the same two gates sync runs before
|
|
183
|
+
installing a schedule, the defaults-applied schedule input contract and
|
|
184
|
+
the cron dialect check, which are now shared with `compileTaskSources` so
|
|
185
|
+
the two commands cannot drift. Exit 0 for valid/converts, 1 for the rest,
|
|
186
|
+
2 for a missing or unreadable path. `resolved` on success is the compiled
|
|
187
|
+
task shape (id, version, target, inputs, schedule), never an
|
|
188
|
+
execution-lowered plan.
|
|
189
|
+
- **`engines.<name>.apiKeyFile` supplies an LLM engine credential from a file
|
|
190
|
+
(#905).** A host that refuses secrets in the process environment can point
|
|
191
|
+
an engine at a path instead of a `$VAR` reference: `~` expands, one
|
|
192
|
+
trailing newline is trimmed, the file is read only at dispatch, and a
|
|
193
|
+
missing or empty file is a config error naming the engine and path but
|
|
194
|
+
never the value. Setting both `apiKey` and `apiKeyFile` is rejected;
|
|
195
|
+
setting neither still falls through to the implicit
|
|
196
|
+
`AKM_ENGINE_<NAME>_API_KEY` convention. The value is redacted from
|
|
197
|
+
dispatch output the same way an env-backed credential is, and `akm health`
|
|
198
|
+
checks the file is present and non-empty.
|
|
199
|
+
|
|
200
|
+
### Changed
|
|
201
|
+
|
|
202
|
+
- **`akm improve`'s machine-local writers moved out of `$STASH/.akm` into
|
|
203
|
+
`$STATE` and `$CACHE` (#890).** `distill-rejected/`, `eval-cases/`,
|
|
204
|
+
`measurement/verdicts/`, the synthetic `unresolved-sources/` placeholder,
|
|
205
|
+
and the improve-pipeline lock files never met the "must travel with the
|
|
206
|
+
content" rule; they now live under `$STATE/improve/…/<stash>/`,
|
|
207
|
+
`$CACHE/index/unresolved-sources/<stash>/`, and `$STATE/locks/<stash>/`,
|
|
208
|
+
namespaced by a short hash of the resolved stash path. `akm migrate
|
|
209
|
+
status` and `apply` gain a relocation step that covers the default stash
|
|
210
|
+
and every other filesystem-backed bundle (remote sources are skipped
|
|
211
|
+
without a network call), moves files by rename or copy-then-delete, and
|
|
212
|
+
deletes an old lock only when the same staleness check `akm improve` uses
|
|
213
|
+
says its holder is dead; a live lock is left in place and reported. The
|
|
214
|
+
step is idempotent and `--dry-run` moves nothing. Scripts that read the
|
|
215
|
+
old paths should switch to the new locations named in
|
|
216
|
+
`docs/architecture/internals/storage-locations.md`.
|
|
217
|
+
|
|
218
|
+
### Fixed
|
|
219
|
+
|
|
220
|
+
- **The dead-link check states its coverage and no longer lies about it
|
|
221
|
+
(#892).** `akm improve`'s post-loop check scanned at most ten knowledge
|
|
222
|
+
refs and fired every HEAD request at once with no timeout. It now scans
|
|
223
|
+
every actionable ref, checks at a bounded concurrency, bounds each request
|
|
224
|
+
at five seconds and reports a timeout as a dead entry, and counts a DNS or
|
|
225
|
+
connection failure as skipped rather than as dead or as fine. The result
|
|
226
|
+
carries `checked`, `total`, and `skipped`, threaded into the improve
|
|
227
|
+
result, the `improve_completed` event, and `akm health`'s improve summary.
|
|
228
|
+
The other constants the issue names (`MAX_URLS`, the per-entry slice,
|
|
229
|
+
`MAX_BODY_CHARS`, the duplicated `MAX_CONTENT_CHARS`) were already removed
|
|
230
|
+
in 0.9.8, and the curate score floors only decide whether a second search
|
|
231
|
+
pass runs; they never filter returned results.
|
|
232
|
+
|
|
7
233
|
## [0.9.10] - 2026-09-02
|
|
8
234
|
|
|
9
235
|
### Fixed
|
package/STABILITY.md
CHANGED
|
@@ -112,6 +112,7 @@ enumeration of the whole `proposal` noun group.
|
|
|
112
112
|
| `akm task sync` | Evolving | |
|
|
113
113
|
| `akm task doctor` | Evolving | |
|
|
114
114
|
| `akm task explain` | Evolving | New in 0.9.2; secret-shaped values in provenance output are redacted on a best-effort heuristic basis (not a guarantee). |
|
|
115
|
+
| `akm task validate` | Evolving | New in 0.9.11; read-only, and the only `task` subcommand that takes a bare filesystem path instead of a ref — the file need not belong to any configured bundle. |
|
|
115
116
|
|
|
116
117
|
## Stable
|
|
117
118
|
|
|
@@ -218,6 +219,10 @@ enumeration of the whole `proposal` noun group.
|
|
|
218
219
|
| `4` | Health warning (`akm health` only) |
|
|
219
220
|
| `70` | Internal / unclassified |
|
|
220
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.
|
|
221
226
|
- **Install scripts** — `install.sh` and `install.ps1` URLs; the `--prefix`
|
|
222
227
|
/ `AKM_INSTALL_DIR` environment override.
|
|
223
228
|
- **Runtime** — the npm package requires Node.js >= 22 as its bootstrap and
|
|
@@ -256,21 +261,28 @@ CHANGELOG with a migration note.
|
|
|
256
261
|
`akm improve && akm proposal drain --promote --yes`, or a `triage` block
|
|
257
262
|
with `applyMode: "promote"` in your strategy.
|
|
258
263
|
- **Tasks** — `akm task` subcommand surface (`add | run | sync | doctor |
|
|
259
|
-
history | explain`; no alias, no
|
|
260
|
-
task source v4 YAML (typed
|
|
261
|
-
accepted version — task v3 and
|
|
262
|
-
`akm migrate apply`. Command tasks use
|
|
263
|
-
metadata is versioned. Schema additions in
|
|
264
|
-
minor. Bare `akm task` is a usage error
|
|
265
|
-
(`akm task doctor` reports scheduler diagnostics).
|
|
266
|
-
(new in 0.9.2) and `akm workflow plan <ref>` are
|
|
267
|
-
provenance surfaces: they show what a task or workflow
|
|
268
|
-
resolved target, input bindings, child expansion — without
|
|
269
|
-
publishing a run. `akm workflow plan` is secret-free **by
|
|
270
|
-
(the excluded data never reaches the command). `akm task
|
|
271
|
-
instead **redacts** secret-shaped input values on a best-effort
|
|
264
|
+
history | explain | validate`; no alias, no
|
|
265
|
+
`list`/`remove`/`init`/`enable`/`disable`); task source v4 YAML (typed
|
|
266
|
+
`inputs:`, optional `schedule:`) is the only accepted version — task v3 and
|
|
267
|
+
task v2 sources are converted by `akm migrate apply`. Command tasks use
|
|
268
|
+
named engines and task history metadata is versioned. Schema additions in
|
|
269
|
+
patch releases; removals only at minor. Bare `akm task` is a usage error
|
|
270
|
+
naming the subcommands (`akm task doctor` reports scheduler diagnostics).
|
|
271
|
+
`akm task explain <ref>` (new in 0.9.2) and `akm workflow plan <ref>` are
|
|
272
|
+
both zero-write provenance surfaces: they show what a task or workflow
|
|
273
|
+
would do — resolved target, input bindings, child expansion — without
|
|
274
|
+
starting or publishing a run. `akm workflow plan` is secret-free **by
|
|
275
|
+
construction** (the excluded data never reaches the command). `akm task
|
|
276
|
+
explain` instead **redacts** secret-shaped input values on a best-effort
|
|
272
277
|
heuristic basis — a value that doesn't match the heuristic can still
|
|
273
|
-
print unredacted.
|
|
278
|
+
print unredacted. `akm task validate <path>` (new in 0.9.11) is the same
|
|
279
|
+
kind of zero-write introspection as `explain`, but takes a bare filesystem
|
|
280
|
+
path rather than a bundle-qualified ref — it reports whether that ONE file
|
|
281
|
+
would parse cleanly (`valid`), auto-convert from task v2/v3 (`converts`),
|
|
282
|
+
need a human decision the deterministic migrator can't make (`blocked`),
|
|
283
|
+
fail schema validation (`invalid`), or isn't a task source at all
|
|
284
|
+
(`not-a-task`) — exactly the diagnostic `akm task sync` would produce for
|
|
285
|
+
it, before the file is ever wired into a bundle or the scheduler.
|
|
274
286
|
- **Workflow plan** — `akm workflow plan <ref>`, new in 0.9.2: zero-write
|
|
275
287
|
compile+freeze introspection (the canonical step graph, task/child
|
|
276
288
|
expansion, input bindings, and lowering notices for a workflow, without
|
|
@@ -431,7 +443,6 @@ on them.
|
|
|
431
443
|
| `AKM_SQLITE_JOURNAL_MODE` | SQLite journal mode (network filesystems) |
|
|
432
444
|
| `AKM_BIN` | Absolute `akm` path for scheduler registration |
|
|
433
445
|
| `AKM_INSTALL_DIR` | Install-script prefix |
|
|
434
|
-
| `AKM_FORCE_SETUP_TMP_STASH` | Documented escape hatch for intentional temp-directory bundles |
|
|
435
446
|
| `AKM_UPGRADE_SKIP_CHECKSUM` | Recovery hatch for a broken upgrade checksum |
|
|
436
447
|
|
|
437
448
|
**Internal** — no compatibility guarantee, may vanish without notice:
|
|
@@ -473,6 +484,8 @@ lives in this repo). **D3** shipped too, in the end: `akm mv` was removed in
|
|
|
473
484
|
0.9.0 (see the Renames bullet above), with `scripts/rekey-asset-ref.ts` as the
|
|
474
485
|
Internal replacement for the one capability nothing else covered.
|
|
475
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.
|
|
476
489
|
- **0.10 — migration extraction.** The migration machinery leaves the CLI for
|
|
477
490
|
a separately published `akm-migrate` package (see Internal above).
|
|
478
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
|
|
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
|
|
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"]
|
|
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 } },
|
package/dist/cli/shared.js
CHANGED
|
@@ -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`
|
|
154
|
-
//
|
|
155
|
-
//
|
|
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'
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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 {
|
|
4
|
+
import { warn } from "../../core/warn.js";
|
|
5
5
|
export const PORTABLE_ARGUMENTS_PLACEHOLDER = "$ARGUMENTS";
|
|
6
|
-
const
|
|
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
|
-
|
|
32
|
-
|
|
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
|
/**
|