akm-cli 0.9.0-rc.13 → 0.9.0-rc.14
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 +117 -23
- package/dist/akm-migrate +3 -1
- package/dist/assets/hints/cli-hints-full.md +3 -4
- package/dist/assets/hints/cli-hints-short.md +5 -5
- package/dist/assets/workflows/workflow-template.md +4 -3
- package/dist/cli/invocation.js +3 -2
- package/dist/cli/retired-commands.js +3 -0
- package/dist/cli/unknown-flags.js +226 -0
- package/dist/cli.js +19 -35
- package/dist/commands/agent/contribute-cli.js +15 -3
- package/dist/commands/feedback-cli.js +6 -3
- package/dist/commands/improve/collapse-detector.js +2 -3
- package/dist/commands/lint/base-linter.js +4 -16
- package/dist/commands/lint/index.js +13 -13
- package/dist/commands/log.js +6 -1
- package/dist/commands/migration-tool.js +4 -5
- package/dist/commands/observability-cli.js +1 -1
- package/dist/commands/proposal/repository.js +5 -5
- package/dist/commands/read/knowledge.js +2 -0
- package/dist/commands/registry-cli.js +5 -3
- package/dist/commands/sources/add-cli.js +6 -6
- package/dist/commands/sources/self-update.js +30 -7
- package/dist/commands/sources/source-add.js +17 -2
- package/dist/commands/tasks/tasks.js +8 -3
- package/dist/commands/workflow-cli.js +142 -119
- package/dist/core/adapter/adapters/akm-lint.js +17 -13
- package/dist/core/adapter/adapters/akm-task-adapter.js +14 -11
- package/dist/core/asset/akm-markdown.js +41 -8
- package/dist/core/asset/frontmatter.js +22 -0
- package/dist/core/asset/resolve-ref.js +23 -3
- package/dist/core/common.js +45 -2
- package/dist/core/config/config-schema.js +8 -0
- package/dist/core/config/schema/experimental.js +5 -13
- package/dist/core/config/schema/sources-bundles.js +11 -0
- package/dist/core/config/schema/workflow.js +3 -1
- package/dist/core/errors.js +5 -0
- package/dist/core/logs-db.js +2 -1
- package/dist/core/parse.js +4 -1
- package/dist/core/state/migrations.js +11 -14
- package/dist/core/state-db.js +4 -6
- package/dist/core/subprocess.js +6 -4
- package/dist/core/type-presentation.js +1 -1
- package/dist/indexer/indexer.js +16 -1
- package/dist/indexer/search/search-source.js +1 -1
- package/dist/indexer/walk/matchers.js +3 -1
- package/dist/integrations/agent/config.js +2 -2
- package/dist/integrations/agent/detect.js +49 -19
- package/dist/integrations/agent/profiles.js +10 -0
- package/dist/integrations/agent/spawn.js +1 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +7 -3
- package/dist/integrations/lockfile.js +11 -56
- package/dist/output/shapes/passthrough.js +0 -4
- package/dist/output/text/command-format.js +3 -3
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/show-directives.js +4 -6
- package/dist/output/text/workflow-format.js +7 -62
- package/dist/output/text/workflow.js +1 -5
- package/dist/scripts/akm-migrate-node.js +58773 -0
- package/dist/scripts/akm-migrate.js +33976 -11391
- package/dist/setup/detect.js +40 -15
- package/dist/setup/setup.js +1 -1
- package/dist/sources/providers/git-stash.js +4 -2
- package/dist/sources/providers/website.js +5 -0
- package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
- package/dist/sources/snapshot-fetchers/content-extract.js +370 -0
- package/dist/sources/snapshot-fetchers/fetcher-util.js +40 -0
- package/dist/sources/snapshot-fetchers/host-guard.js +199 -0
- package/dist/sources/snapshot-fetchers/registry.js +10 -1
- package/dist/sources/snapshot-fetchers/robots.js +348 -0
- package/dist/sources/snapshot-fetchers/rss.js +279 -0
- package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
- package/dist/sources/snapshot-fetchers/website-ingest.js +488 -257
- package/dist/sources/snapshot-fetchers/x.js +193 -0
- package/dist/storage/engines/sqlite-migrations.js +22 -107
- package/dist/tasks/runner.js +19 -16
- package/dist/tasks/schema.js +24 -1
- package/dist/workflows/exec/brief.js +1 -1
- package/dist/workflows/exec/frozen-judge.js +28 -2
- package/dist/workflows/exec/native-executor.js +18 -1
- package/dist/workflows/exec/report.js +11 -4
- package/dist/workflows/exec/run-workflow.js +103 -44
- package/dist/workflows/exec/step-work.js +19 -18
- package/dist/workflows/exec/unit-dispatch.js +4 -0
- package/dist/workflows/exec/workflow-engine-gate.js +11 -13
- package/dist/workflows/ir/compile.js +2 -2
- package/dist/workflows/ir/freeze.js +16 -8
- package/dist/workflows/ir/params.js +134 -10
- package/dist/workflows/ir/plan-hash.js +1 -1
- package/dist/workflows/ir/schema.js +6 -2
- package/dist/workflows/renderer.js +2 -2
- package/dist/workflows/runtime/checkin.js +3 -3
- package/dist/workflows/runtime/runs.js +50 -29
- package/dist/workflows/validate-summary.js +30 -14
- package/docs/migration/release-notes/0.9.0.md +31 -4
- package/docs/migration/v0.8-to-v0.9.md +69 -100
- package/docs/reference/data-and-telemetry.md +5 -5
- package/package.json +6 -2
- package/schemas/akm-config.json +24 -0
- package/schemas/akm-workflow.json +1 -1
- package/dist/workflows/cli.js +0 -33
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,79 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **RSS, Bluesky, and X sources.** `akm bundle add` now recognizes three new
|
|
12
|
+
kinds of URL and snapshots them as knowledge assets instead of crawling
|
|
13
|
+
them as ordinary web pages:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
akm bundle add https://blog.example/feed # RSS 2.0 / Atom / RDF
|
|
17
|
+
akm bundle add https://bsky.app/profile/<handle> # public, no auth
|
|
18
|
+
akm bundle add https://x.com/<user> # see token note below
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Any of these falling through — a `/feed` URL that actually serves HTML, an
|
|
22
|
+
unresolvable Bluesky handle — degrades to the normal website crawl rather
|
|
23
|
+
than failing the command.
|
|
24
|
+
|
|
25
|
+
X needs credentials: set `X_BEARER_TOKEN` for the X API v2, or
|
|
26
|
+
`X_RSS_TEMPLATE` to an RSS bridge URL containing `{username}`. To keep the
|
|
27
|
+
token out of your shell history, store it as an akm secret and inject it
|
|
28
|
+
per-invocation:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
akm secret set x-bearer-token
|
|
32
|
+
akm secret run secrets/x-bearer-token X_BEARER_TOKEN -- akm bundle add https://x.com/<user>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
With neither set, the X fetcher emits one warning and falls through.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- **X source tokens now resolve from the secret store during bundle update.**
|
|
40
|
+
The `secrets/x-bearer-token` akm secret is honored on the provider
|
|
41
|
+
`sync()` / bundle-update path, not just when adding or importing a URL —
|
|
42
|
+
closing a gap where a refresh saw only the `X_BEARER_TOKEN` environment
|
|
43
|
+
variable. Implemented as a `SecretResolver` capability injected from above
|
|
44
|
+
the source-provider import cycle; internals are documented in
|
|
45
|
+
`docs/architecture/reviews/env-secret-access.md`.
|
|
46
|
+
|
|
47
|
+
- **`website` crawls now have a hard time limit.** `crawlTimeoutMs` (default
|
|
48
|
+
600000 — 10 minutes) bounds the entire crawl, and unlike the previous
|
|
49
|
+
between-page check it aborts work already in flight: a `Retry-After` sleep
|
|
50
|
+
could previously park `akm bundle add` for as long as a rate-limiting server
|
|
51
|
+
asked, well past the advertised cap. Raise it for a large site, or set
|
|
52
|
+
`"crawlTimeoutMs": 0` to disable the cap. Relatedly, `fetchWithRetry` now
|
|
53
|
+
honors its caller's `AbortSignal` during retry backoff, so any operation that
|
|
54
|
+
passes a signal can actually interrupt a long wait.
|
|
55
|
+
|
|
56
|
+
- **Website snapshots now extract the page's main content.** Conversion moved
|
|
57
|
+
from a hand-rolled regex converter to a DOM parse plus Turndown, scoped to
|
|
58
|
+
the page's content region (`<main>`, `<article>`, `[role=main]`, then common
|
|
59
|
+
content ids/classes, falling back to `<body>` minus nav/header/footer/aside).
|
|
60
|
+
Navigation, ads, and boilerplate no longer land in snapshots, and tables,
|
|
61
|
+
nested lists, and fenced code blocks with language hints now survive
|
|
62
|
+
conversion. **Existing website snapshots will change on their next refresh**
|
|
63
|
+
— expect them to get shorter and cleaner. Link discovery still scans the
|
|
64
|
+
whole page, so crawl coverage is unchanged.
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
- **`website` sources now respect `robots.txt` by default.** Before crawling
|
|
68
|
+
an origin, akm fetches and parses that origin's `/robots.txt` and skips
|
|
69
|
+
paths disallowed for the `akm`/`akm-cli` product tokens (or `*`), honoring
|
|
70
|
+
`Crawl-delay` (clamped to 10s) between page fetches. This is a deliberate
|
|
71
|
+
behavior change: **existing website sources may return fewer pages, or
|
|
72
|
+
fail with an error if the start URL itself is disallowed, after
|
|
73
|
+
upgrading.** Re-running `akm bundle update` on a website source is what
|
|
74
|
+
surfaces it. Opt out with `"respectRobots": false` on the website
|
|
75
|
+
descriptor to restore the exact pre-upgrade behavior (no `/robots.txt`
|
|
76
|
+
request at all):
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{ "bundles": { "docs": { "website": { "url": "https://docs.example.com", "respectRobots": false } } } }
|
|
80
|
+
```
|
|
81
|
+
|
|
9
82
|
## [0.9.0-rc.13] - 2026-07-31
|
|
10
83
|
|
|
11
84
|
### Security
|
|
@@ -139,6 +212,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
139
212
|
|
|
140
213
|
### Changed
|
|
141
214
|
|
|
215
|
+
- **Workflow execution is consolidated on stable `akm workflow run`.** The
|
|
216
|
+
public `workflow start`, `next`, and `complete` commands are removed with
|
|
217
|
+
explicit `UNKNOWN_COMMAND` migration hints; `run <ref|run-id>` now owns
|
|
218
|
+
creation, active-run continuation, native dispatch, completion, and durable
|
|
219
|
+
replay. It is no longer gated by `experimental.workflowEngine`; only the
|
|
220
|
+
experimental `brief`/`report` external-driver protocol retains that opt-in.
|
|
221
|
+
Workflow parameters move from the opaque `--params '<json>'` bag to exact
|
|
222
|
+
declared flags (`--version 1.2.3`, repeated array flags, JSON object/array
|
|
223
|
+
values) coerced through the frozen parameter schemas. New invocation controls
|
|
224
|
+
add bounded failed-step retries (`--max-retries`) and a whole-run timeout
|
|
225
|
+
(`--timeout N|Nms|Ns|Nm`); failures, gate rejection, timeout, and signals now
|
|
226
|
+
produce non-zero process statuses while leaving interrupted work resumable.
|
|
227
|
+
|
|
228
|
+
Criteria-bearing gates now require `workflow.judgeEngine`, which may name a
|
|
229
|
+
configured LLM or agent engine and is frozen into the run. Verification is
|
|
230
|
+
fail-closed: a missing/failing verifier or malformed verdict rejects instead
|
|
231
|
+
of silently advancing. Scheduled workflow tasks now execute through the same
|
|
232
|
+
native orchestrator rather than stopping after run creation. Migration:
|
|
233
|
+
replace `workflow start/next/complete` loops with `workflow run`, replace
|
|
234
|
+
`--params` with exact declared flags, and configure `workflow.judgeEngine`
|
|
235
|
+
before running a workflow with a non-empty `### gate` rubric.
|
|
236
|
+
|
|
142
237
|
- **The two workflow authoring formats — markdown documents and YAML
|
|
143
238
|
orchestration programs — are unified into one format**, per
|
|
144
239
|
`docs/architecture/specs/workflow-format-unification.md`. A workflow is
|
|
@@ -169,15 +264,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
169
264
|
bullet section — they live under a step's `### gate` sub-heading, the
|
|
170
265
|
format's one reserved marker, as full prose a judge receives byte-exact.
|
|
171
266
|
Frontmatter `gate:` now carries only optional `max_loops` configuration.
|
|
172
|
-
Omitted or empty rubric text skips validation; a non-empty rubric
|
|
173
|
-
|
|
267
|
+
Omitted or empty rubric text skips validation; a non-empty rubric requires
|
|
268
|
+
the frozen `workflow.judgeEngine`, and unavailable or malformed judges reject
|
|
269
|
+
the gate.
|
|
174
270
|
|
|
175
|
-
This is a **pre-1.0 change
|
|
176
|
-
`
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
unified format in this change. There is nothing on disk to migrate and
|
|
180
|
-
no users to break.
|
|
271
|
+
This is a **pre-1.0 format change**. The ten example workflows under
|
|
272
|
+
`scripts/akm-eval/example-stash/workflows/` are rewritten to the unified
|
|
273
|
+
format in this change; existing user-authored workflow assets must be updated
|
|
274
|
+
manually before execution.
|
|
181
275
|
|
|
182
276
|
- **akm is described as a knowledge toolkit, not a package manager** (R-048).
|
|
183
277
|
The npm one-liner, the README lede, and the `concepts.md` opener all led with
|
|
@@ -766,9 +860,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
766
860
|
- **The CHURN alert class is removed from the collapse detector.** Its input was
|
|
767
861
|
a hard-coded `0` from the 0.9.0 confidence-gate deletion onward, so the alert
|
|
768
862
|
could never fire. The other three alert classes are unaffected. The
|
|
769
|
-
`improve_cycle_metrics.accepted_actions` column stays and is written as `0
|
|
770
|
-
|
|
771
|
-
ledger can already have sealed that body's checksum.
|
|
863
|
+
`improve_cycle_metrics.accepted_actions` column stays and is written as `0`
|
|
864
|
+
because deployed 0.8 `state.db` files already contain it.
|
|
772
865
|
|
|
773
866
|
- **`IndexResponse.graphQuality` is removed** from the `akm index` envelope — it
|
|
774
867
|
was declared but never assigned in any code path, so it was always absent.
|
|
@@ -839,15 +932,16 @@ earlier `0.9.0-rc.1` / `0.9.0-beta.*` development entries below.
|
|
|
839
932
|
order). The pre-0.9.0 `[origin//]type:name` grammar is removed — there is no
|
|
840
933
|
compatibility parser; the frozen migrator in `scripts/akm-migrate/migrate/`
|
|
841
934
|
is the only place it survives.
|
|
842
|
-
- **Explicit,
|
|
935
|
+
- **Explicit, crash-resumable cutover (`akm migrate apply`).** The
|
|
843
936
|
migrator re-keys all durable state to the new spelling, folds the former
|
|
844
937
|
`workflow.db` into `state.db` (four databases down to three: `state.db` /
|
|
845
938
|
`index.db` / a separate `logs.db`), and migrates config from the flat
|
|
846
939
|
`stashDir` / `sources` / `installed` / `wikiName` keys to `bundles` /
|
|
847
|
-
`defaultBundle`. A verified, installation-scoped **backup manifest
|
|
848
|
-
(covering the pre-rescue `index.db`) is taken before
|
|
849
|
-
|
|
850
|
-
|
|
940
|
+
`defaultBundle`. A semantically verified, installation-scoped **backup manifest v4**
|
|
941
|
+
(covering the pre-rescue `index.db`) is taken before mutation. One phase-free
|
|
942
|
+
incomplete sentinel retains that backup and target; expected orphans are
|
|
943
|
+
quarantined, integrity failures fail closed, and the whole cutover reruns
|
|
944
|
+
idempotently after a crash. Normal commands refuse an
|
|
851
945
|
un-migrated or divergent durable schema rather than migrating as a side effect.
|
|
852
946
|
The retired `stashDir` / `sources` / `installed` keys are **hard-rejected** by
|
|
853
947
|
the 0.9.0 config schema whenever present (the error names `akm migrate apply`);
|
|
@@ -888,13 +982,13 @@ See `docs/migration/v0.8-to-v0.9.md` and
|
|
|
888
982
|
migration, dashboard, or health schema was added.
|
|
889
983
|
- **Explicit, crash-resumable 0.9 migration coordination.** `akm migrate
|
|
890
984
|
status` classifies config, `state.db`, and `workflow.db` independently;
|
|
891
|
-
`akm migrate apply [--config <prepared>]` creates a verified,
|
|
892
|
-
installation-scoped backup before
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
Routine reads and current database opens no longer depend on a
|
|
897
|
-
cutover bundle. See `docs/migration/release-notes/0.9.0.md`.
|
|
985
|
+
`akm migrate apply [--config <prepared>]` creates a semantically verified,
|
|
986
|
+
installation-scoped config/database backup before applying pending migrations.
|
|
987
|
+
Apply and restore use one phase-free incomplete sentinel, bounded control-file
|
|
988
|
+
reads, SQLite integrity and ordered-ledger checks, active-writer barriers,
|
|
989
|
+
WAL/SHM-safe publication, and idempotent replay. Legacy checksum columns are
|
|
990
|
+
inert. Routine reads and current database opens no longer depend on a
|
|
991
|
+
historical cutover bundle. See `docs/migration/release-notes/0.9.0.md`.
|
|
898
992
|
- **Workflow orchestration engine (experimental).** akm can now execute
|
|
899
993
|
multi-step workflows through a native engine or any agent session. Workflow
|
|
900
994
|
assets use the unified markdown format described above; the stable manual
|
package/dist/akm-migrate
CHANGED
|
@@ -24,11 +24,13 @@ const bunProbe = process.versions.bun
|
|
|
24
24
|
const [bunMajor = 0] = (bunProbe.status === 0 ? bunProbe.stdout.trim() : "").split(".").map(Number);
|
|
25
25
|
const useBun = bunMajor >= 1;
|
|
26
26
|
const bunEntry = fileURLToPath(new URL("./scripts/akm-migrate.js", import.meta.url));
|
|
27
|
+
const nodeEntry = fileURLToPath(new URL("./scripts/akm-migrate-node.js", import.meta.url));
|
|
27
28
|
|
|
28
29
|
{
|
|
29
30
|
const command = process.versions.bun ? process.execPath : useBun ? "bun" : process.execPath;
|
|
31
|
+
const entry = process.versions.bun || useBun ? bunEntry : nodeEntry;
|
|
30
32
|
const result = await new Promise((resolve) => {
|
|
31
|
-
const child = spawn(command, [
|
|
33
|
+
const child = spawn(command, [entry, ...process.argv.slice(2)], { stdio: "inherit" });
|
|
32
34
|
child.once("error", (error) => resolve({ error }));
|
|
33
35
|
child.once("exit", (code, signal) => resolve({ code, signal }));
|
|
34
36
|
});
|
|
@@ -93,7 +93,7 @@ akm import https://example.com/docs/auth # Fetch one URL and import it as
|
|
|
93
93
|
akm import ./doc.md --target my-other-bundle # Route import to a named writable bundle source
|
|
94
94
|
akm workflow create ship-release # Create a workflow asset in the bundle
|
|
95
95
|
akm lint --type workflows # Parse and compile every unified markdown workflow; list every error
|
|
96
|
-
akm workflow
|
|
96
|
+
akm workflow run workflows/ship-release # Start or resume and execute the workflow
|
|
97
97
|
akm feedback skills/code-review --positive # Record that an asset helped
|
|
98
98
|
akm feedback agents/reviewer --negative --reason "wrong framework" # Record why an asset missed the mark
|
|
99
99
|
akm feedback memories/deployment-notes --positive # Works for memories too
|
|
@@ -169,9 +169,8 @@ workflow. Direct run-id commands still target the exact run.
|
|
|
169
169
|
```sh
|
|
170
170
|
akm workflow create ship-release --print # Print a starter workflow template, without writing
|
|
171
171
|
akm workflow create ship-release # Scaffold a new workflow asset
|
|
172
|
-
akm workflow
|
|
173
|
-
akm workflow
|
|
174
|
-
akm workflow complete <run-id> --step <id> --summary "..." # Mark a step complete and advance
|
|
172
|
+
akm workflow run workflows/ship-release --version=1.2.3 # Start and execute with exact-name parameter flags
|
|
173
|
+
akm workflow run <run-id> --max-retries 2 --timeout 10m # Resume with invocation-wide controls
|
|
175
174
|
akm workflow status <run-id> # Show the exact run by id
|
|
176
175
|
akm workflow resume <run-id> # Resume a blocked or failed run
|
|
177
176
|
akm workflow list # List workflow runs in the current scope
|
|
@@ -11,12 +11,12 @@ For any task, follow this loop:
|
|
|
11
11
|
4. `akm feedback <ref> --positive` — record success; use `--negative --reason "..."` when it fails
|
|
12
12
|
|
|
13
13
|
For workflow tasks:
|
|
14
|
-
1. `akm
|
|
15
|
-
2.
|
|
16
|
-
3. `akm workflow
|
|
14
|
+
1. `akm show workflows/<name>` — inspect the procedure before executing it
|
|
15
|
+
2. `akm workflow run workflows/<name> --param_name=value` — execute every step and verification gate
|
|
16
|
+
3. `akm workflow status <run-id> --units` — inspect a stopped or failed run
|
|
17
17
|
|
|
18
18
|
Workflow runs are scoped to your current project/worktree/directory. Ref-based
|
|
19
|
-
commands like `workflow
|
|
19
|
+
commands like `workflow run workflows/<name>`, `workflow status workflows/<name>`,
|
|
20
20
|
and `workflow list` operate within the current scope only.
|
|
21
21
|
|
|
22
22
|
## Quick Reference
|
|
@@ -29,7 +29,7 @@ akm search "memories/projectA/" # List a subtree (conceptId prefix
|
|
|
29
29
|
akm search "<query>" --from all # Also search registries
|
|
30
30
|
akm show <ref> # View asset details
|
|
31
31
|
akm help <command> # Show a command's options and subcommands
|
|
32
|
-
akm workflow
|
|
32
|
+
akm workflow run <ref> # Start or resume and execute a workflow
|
|
33
33
|
akm remember "Deployment needs VPN access" # Record a memory in your bundle
|
|
34
34
|
akm remember "note" --bundle my-bundle # Route write to a named writable bundle source
|
|
35
35
|
akm remember "note" --xref knowledge/auth-flow # Cite provenance in frontmatter xrefs (repeatable)
|
|
@@ -32,9 +32,10 @@ first step's attached artifact."
|
|
|
32
32
|
### gate
|
|
33
33
|
|
|
34
34
|
A `### gate` sub-heading is the step's completion rubric: the judge
|
|
35
|
-
receives this whole section byte-exact
|
|
36
|
-
|
|
37
|
-
malformed
|
|
35
|
+
receives this whole section byte-exact. Omit the heading or leave its text
|
|
36
|
+
empty to skip validation. A non-empty rubric requires
|
|
37
|
+
`workflow.judgeEngine`; verifier failures and malformed verdicts reject the
|
|
38
|
+
gate rather than silently skipping it.
|
|
38
39
|
|
|
39
40
|
- Confirm the step accomplished what it set out to do.
|
|
40
41
|
- Confirm nothing required was silently skipped.
|
package/dist/cli/invocation.js
CHANGED
|
@@ -181,10 +181,11 @@ export function _resetParsedInvocationForTests() {
|
|
|
181
181
|
export function parseAllFlagValues(flag) {
|
|
182
182
|
return getParsedInvocation().getAllFlagValues(flag);
|
|
183
183
|
}
|
|
184
|
-
|
|
184
|
+
/** citty compares `foo-bar` and `fooBar` as the same arg name. */
|
|
185
|
+
export function cittyComparableName(name) {
|
|
185
186
|
return name.replace(/[-_]+([a-zA-Z0-9])/g, (_match, char) => char.toUpperCase());
|
|
186
187
|
}
|
|
187
|
-
function toAliasArray(alias) {
|
|
188
|
+
export function toAliasArray(alias) {
|
|
188
189
|
if (Array.isArray(alias))
|
|
189
190
|
return alias;
|
|
190
191
|
return typeof alias === "string" ? [alias] : [];
|
|
@@ -54,6 +54,9 @@ const RETIRED_COMMAND_HINTS = {
|
|
|
54
54
|
"workflow template": "`akm workflow template` was folded into `akm workflow create --print` in 0.9.",
|
|
55
55
|
"workflow validate": "`akm workflow validate` was folded into `akm lint --type workflows` in 0.9 (add `--fail-on-flagged` for CI gates).",
|
|
56
56
|
"workflow watch": "`akm workflow watch` was removed in 0.9 — use `akm log --run <run-id>`.",
|
|
57
|
+
"workflow start": "`akm workflow start` was removed in 0.9 — use `akm workflow run <ref>`.",
|
|
58
|
+
"workflow next": "`akm workflow next` was removed in 0.9 — use `akm workflow status <target>` to inspect or `akm workflow run <target>` to execute.",
|
|
59
|
+
"workflow complete": "`akm workflow complete` was removed in 0.9 — native runs complete steps automatically; external drivers use `akm workflow brief` and `akm workflow report`.",
|
|
57
60
|
"config show": "`akm config show` was removed in 0.9 — use `akm config list`.",
|
|
58
61
|
"config validate": "`akm config validate` was removed in 0.9 — the config file is validated on every load.",
|
|
59
62
|
"task enable": "`akm task enable` was removed in 0.9 — set `enabled: true` in the task YAML, then `akm task sync`.",
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Reject flags the resolved command does not declare.
|
|
6
|
+
*
|
|
7
|
+
* citty (0.2.x) parses argv with `node:util`'s parseArgs in non-strict mode
|
|
8
|
+
* (`strict: false`), so an undeclared flag is collected into the parsed object
|
|
9
|
+
* and silently ignored by the handler. Nothing
|
|
10
|
+
* downstream noticed, so `akm lint --fail-on-flaged` (one transposed letter in
|
|
11
|
+
* the flag STABILITY.md documents as a CI contract) parsed fine, exited 0, and
|
|
12
|
+
* the gate it was meant to enforce never fired. Same for `--limt 3`, `--jsn`,
|
|
13
|
+
* and every other typo — the command ran with the default instead, and the user
|
|
14
|
+
* had no signal.
|
|
15
|
+
*
|
|
16
|
+
* This walks the same subcommand path citty resolves (via the shared scan
|
|
17
|
+
* helpers in `./invocation`), unions the arg definitions declared along it,
|
|
18
|
+
* and fails a flag that matches nothing. The union (rather than the leaf's
|
|
19
|
+
* args alone) is deliberate: parent-level flags may legally appear before the
|
|
20
|
+
* subcommand token, and a false positive here would reject a VALID invocation
|
|
21
|
+
* — much worse than the silence it replaces. In the same spirit, everything
|
|
22
|
+
* after a literal `--` is passthrough, a declared value flag's value is never
|
|
23
|
+
* scanned as a flag, `--no-<name>` resolves against `<name>`, and a bare `-`
|
|
24
|
+
* (stdin convention) and negative numbers are left alone. A subcommand invoked
|
|
25
|
+
* by a `meta.name` alias rather than its key takes the stand-down path (no
|
|
26
|
+
* validation) — conservative by design.
|
|
27
|
+
*/
|
|
28
|
+
import { UsageError } from "../core/errors.js";
|
|
29
|
+
import { cittyComparableName, findCittyTopLevelCommandIndex, toAliasArray, } from "./invocation.js";
|
|
30
|
+
/** Flags citty implements itself, which no command declares. */
|
|
31
|
+
const IMPLICIT_FLAGS = ["help", "h", "version", "v"];
|
|
32
|
+
/**
|
|
33
|
+
* Retired flags whose commands still diagnose them THEMSELVES, with a message
|
|
34
|
+
* that names the replacement ("`--scope` was removed, use `--filter`",
|
|
35
|
+
* "`--source` was renamed to `--generator`"). A generic "unknown flag" would
|
|
36
|
+
* preempt the better diagnosis, so these are passed through — but ONLY on the
|
|
37
|
+
* command path that owns the diagnostic, keyed by the resolved path. On every
|
|
38
|
+
* other command the same spelling is a genuine typo and still fails fast
|
|
39
|
+
* A retired flag is rejected everywhere else, where silently dropping it could
|
|
40
|
+
* run a real mutation.
|
|
41
|
+
*
|
|
42
|
+
* Shrink-only: when a command drops its bespoke diagnostic, drop the entry and
|
|
43
|
+
* the generic error takes over.
|
|
44
|
+
*/
|
|
45
|
+
const SELF_DIAGNOSED_FLAGS = new Map(Object.entries({
|
|
46
|
+
show: ["akmView", "scope"], // removed view grammar; --scope points at --filter
|
|
47
|
+
index: ["enrich", "re-enrich"], // removed index-time enrichment flags
|
|
48
|
+
"proposal accept": ["source"], // renamed to --generator
|
|
49
|
+
"proposal reject": ["source"], // renamed to --generator
|
|
50
|
+
"proposal drain": ["profile"], // retired, points at --strategy
|
|
51
|
+
search: ["source"], // renamed to --from
|
|
52
|
+
curate: ["source"], // renamed to --from
|
|
53
|
+
remember: ["target"], // renamed to --bundle
|
|
54
|
+
clone: ["target"], // renamed to --bundle
|
|
55
|
+
improve: ["auto-accept", "target"], // retired in 0.9.0 / renamed to --bundle
|
|
56
|
+
"task add": ["target"], // renamed to --bundle
|
|
57
|
+
"task run": ["target"], // renamed to --bundle
|
|
58
|
+
"task history": ["target"], // renamed to --bundle
|
|
59
|
+
"task sync": ["target"], // renamed to --bundle
|
|
60
|
+
}).map(([path, flags]) => [path, new Set(flags.map(cittyComparableName))]));
|
|
61
|
+
/**
|
|
62
|
+
* Union the arg definitions declared along the resolved command path. The
|
|
63
|
+
* subcommand walk delegates to {@link findCittyTopLevelCommandIndex} — the
|
|
64
|
+
* same scan `src/cli.ts` resolves commands with — so both agree on which
|
|
65
|
+
* token is the subcommand name.
|
|
66
|
+
*/
|
|
67
|
+
function collectKnownArgs(root, rawArgs) {
|
|
68
|
+
const names = new Set(IMPLICIT_FLAGS.map(cittyComparableName));
|
|
69
|
+
const valueFlags = new Set();
|
|
70
|
+
const booleanFlags = new Set();
|
|
71
|
+
const displayNames = [];
|
|
72
|
+
const path = [];
|
|
73
|
+
let cmd = root;
|
|
74
|
+
let args = rawArgs;
|
|
75
|
+
for (;;) {
|
|
76
|
+
for (const [key, def] of Object.entries(cmd.args ?? {})) {
|
|
77
|
+
// Positionals are not flags; including them would accept `--<positional>`.
|
|
78
|
+
if (def.type === "positional")
|
|
79
|
+
continue;
|
|
80
|
+
const comparable = cittyComparableName(key);
|
|
81
|
+
// Shared args (GLOBAL_OUTPUT_ARGS) recur at every level; suggest each once.
|
|
82
|
+
if (!names.has(comparable)) {
|
|
83
|
+
names.add(comparable);
|
|
84
|
+
displayNames.push(`--${key}`);
|
|
85
|
+
}
|
|
86
|
+
if (def.type === "string" || def.type === "enum")
|
|
87
|
+
valueFlags.add(comparable);
|
|
88
|
+
if (def.type === "boolean")
|
|
89
|
+
booleanFlags.add(comparable);
|
|
90
|
+
for (const alias of toAliasArray(def.alias)) {
|
|
91
|
+
names.add(cittyComparableName(alias));
|
|
92
|
+
if (def.type === "string" || def.type === "enum")
|
|
93
|
+
valueFlags.add(cittyComparableName(alias));
|
|
94
|
+
if (def.type === "boolean")
|
|
95
|
+
booleanFlags.add(cittyComparableName(alias));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const subCommands = cmd.subCommands;
|
|
99
|
+
if (!subCommands || Object.keys(subCommands).length === 0)
|
|
100
|
+
break;
|
|
101
|
+
const idx = findCittyTopLevelCommandIndex(args, (cmd.args ?? {}));
|
|
102
|
+
const token = idx >= 0 ? args[idx] : undefined;
|
|
103
|
+
// A group with no subcommand token: citty reports "no command specified".
|
|
104
|
+
if (token === undefined)
|
|
105
|
+
return { names, valueFlags, booleanFlags, displayNames, path, resolved: false };
|
|
106
|
+
const sub = subCommands[token];
|
|
107
|
+
// An unrecognized token: citty reports the unknown command, which is the
|
|
108
|
+
// real problem — its flags are beside the point.
|
|
109
|
+
if (!sub)
|
|
110
|
+
return { names, valueFlags, booleanFlags, displayNames, path, resolved: false };
|
|
111
|
+
path.push(token);
|
|
112
|
+
cmd = sub;
|
|
113
|
+
args = args.slice(idx + 1);
|
|
114
|
+
}
|
|
115
|
+
return { names, valueFlags, booleanFlags, displayNames, path, resolved: true };
|
|
116
|
+
}
|
|
117
|
+
/** Single-row-at-a-time edit-distance DP (inputs are short flag/command names). */
|
|
118
|
+
function editDistance(a, b) {
|
|
119
|
+
let previous = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
120
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
121
|
+
const current = [i];
|
|
122
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
123
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
124
|
+
current.push(Math.min((previous[j] ?? 0) + 1, (current[j - 1] ?? 0) + 1, (previous[j - 1] ?? 0) + cost));
|
|
125
|
+
}
|
|
126
|
+
previous = current;
|
|
127
|
+
}
|
|
128
|
+
return previous[b.length] ?? 0;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Closest candidate within `threshold` edit distance, or undefined when
|
|
132
|
+
* nothing is close enough to be worth suggesting. Shared by the unknown-flag
|
|
133
|
+
* and unknown-command (src/cli.ts) did-you-mean paths, which pick different
|
|
134
|
+
* thresholds.
|
|
135
|
+
*/
|
|
136
|
+
export function closestMatch(attempted, candidates, threshold) {
|
|
137
|
+
let best;
|
|
138
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
139
|
+
for (const candidate of candidates) {
|
|
140
|
+
const distance = editDistance(attempted, candidate);
|
|
141
|
+
if (distance < bestDistance) {
|
|
142
|
+
bestDistance = distance;
|
|
143
|
+
best = candidate;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return best !== undefined && bestDistance <= threshold ? best : undefined;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The one unknown-flag rejection, shared by the short- and long-flag scans so
|
|
150
|
+
* the user-facing error contract cannot drift between them. No explicit hint
|
|
151
|
+
* when there is no suggestion — UNKNOWN_FLAG's canned hint (core/errors.ts)
|
|
152
|
+
* already says to run the command with --help.
|
|
153
|
+
*
|
|
154
|
+
* @param shown The flag as the user typed it (dashes, no `=value`).
|
|
155
|
+
* @param attempted The spelling to edit-distance against `--long` candidates.
|
|
156
|
+
*/
|
|
157
|
+
function throwUnknownFlag(shown, attempted, known) {
|
|
158
|
+
const threshold = Math.max(2, Math.ceil(attempted.length / 3));
|
|
159
|
+
const suggestion = closestMatch(attempted, known.displayNames, threshold);
|
|
160
|
+
throw new UsageError(`Unknown flag "${shown}".`, "UNKNOWN_FLAG", suggestion
|
|
161
|
+
? `Did you mean \`${suggestion}\`? Run the command with \`--help\` to see its accepted flags.`
|
|
162
|
+
: undefined);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Throw a {@link UsageError} naming the first flag the resolved command does
|
|
166
|
+
* not declare. Returns silently when every flag is known.
|
|
167
|
+
*/
|
|
168
|
+
export function assertKnownFlags(root, rawArgs) {
|
|
169
|
+
const passthroughAt = rawArgs.indexOf("--");
|
|
170
|
+
const ownArgs = passthroughAt === -1 ? rawArgs : rawArgs.slice(0, passthroughAt);
|
|
171
|
+
const known = collectKnownArgs(root, rawArgs);
|
|
172
|
+
if (!known.resolved)
|
|
173
|
+
return;
|
|
174
|
+
const dynamicWorkflowParams = known.path.join(" ") === "workflow run";
|
|
175
|
+
const selfDiagnosed = SELF_DIAGNOSED_FLAGS.get(known.path.join(" "));
|
|
176
|
+
for (let i = 0; i < ownArgs.length; i += 1) {
|
|
177
|
+
const token = ownArgs[i];
|
|
178
|
+
// Not a flag: positional, a bare `-` (stdin), or a negative number.
|
|
179
|
+
if (!token.startsWith("-") || token === "-" || /^-\d/.test(token))
|
|
180
|
+
continue;
|
|
181
|
+
// Node's util.parseArgs, which citty delegates to, treats one-dash tokens
|
|
182
|
+
// as bundled short flags. Boolean aliases may be combined (`-qy`), while a
|
|
183
|
+
// string alias consumes the remainder (`-mhello`) or the following token.
|
|
184
|
+
// It never treats `-auto-fix` as the long `auto-fix` option.
|
|
185
|
+
if (!token.startsWith("--")) {
|
|
186
|
+
const shortFlags = token.slice(1);
|
|
187
|
+
for (let offset = 0; offset < shortFlags.length; offset += 1) {
|
|
188
|
+
const rawName = shortFlags[offset];
|
|
189
|
+
const candidate = cittyComparableName(rawName);
|
|
190
|
+
if (!known.names.has(candidate))
|
|
191
|
+
throwUnknownFlag(token, `-${rawName}`, known);
|
|
192
|
+
if (known.valueFlags.has(candidate)) {
|
|
193
|
+
if (offset === shortFlags.length - 1)
|
|
194
|
+
i += 1;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const withoutDashes = token.replace(/^-{1,2}/, "");
|
|
201
|
+
const [rawName = ""] = withoutDashes.split("=", 1);
|
|
202
|
+
const hasInlineValue = withoutDashes.includes("=");
|
|
203
|
+
// `--no-foo` is citty's negation of the BOOLEAN `--foo`. Resolving it
|
|
204
|
+
// against a value flag would accept `--no-limit`, which citty's own `--no-`
|
|
205
|
+
// preprocessing force-sets to `limit: false` (no type check, before
|
|
206
|
+
// parseArgs runs) — a boolean reaching a string parser, i.e. an internal
|
|
207
|
+
// error (exit 70) instead of the usage error (exit 2) this is here to give.
|
|
208
|
+
const negated = rawName.startsWith("no-") && known.booleanFlags.has(cittyComparableName(rawName.slice(3)))
|
|
209
|
+
? rawName.slice(3)
|
|
210
|
+
: undefined;
|
|
211
|
+
const candidates = [cittyComparableName(rawName), ...(negated ? [cittyComparableName(negated)] : [])];
|
|
212
|
+
if (selfDiagnosed !== undefined && candidates.some((name) => selfDiagnosed.has(name)))
|
|
213
|
+
continue;
|
|
214
|
+
if (!candidates.some((name) => known.names.has(name))) {
|
|
215
|
+
// `workflow run` owns one deliberately dynamic namespace: long options
|
|
216
|
+
// become exact-name workflow parameters and are checked against the
|
|
217
|
+
// frozen plan before a run is inserted. Short flags remain strict.
|
|
218
|
+
if (dynamicWorkflowParams)
|
|
219
|
+
continue;
|
|
220
|
+
throwUnknownFlag(token.split("=")[0], `--${rawName}`, known);
|
|
221
|
+
}
|
|
222
|
+
// Skip a declared value flag's value so `--reason "--x"` is not scanned.
|
|
223
|
+
if (!hasInlineValue && candidates.some((name) => known.valueFlags.has(name)))
|
|
224
|
+
i += 1;
|
|
225
|
+
}
|
|
226
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -60,6 +60,7 @@ import { defineCommand, parseArgs, renderUsage, runCommand, showUsage } from "ci
|
|
|
60
60
|
import { findCittyTopLevelCommand, 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
|
+
import { assertKnownFlags, closestMatch } from "./cli/unknown-flags.js";
|
|
63
64
|
import { agentCommand, lintCommand } from "./commands/agent/contribute-cli.js";
|
|
64
65
|
import { generateBashCompletions, installBashCompletions } from "./commands/completions.js";
|
|
65
66
|
import { configCommand } from "./commands/config-cli.js";
|
|
@@ -236,7 +237,13 @@ const setupCommand = defineCommand({
|
|
|
236
237
|
printSetupTtyHint(result);
|
|
237
238
|
}
|
|
238
239
|
else {
|
|
239
|
-
// Interactive wizard
|
|
240
|
+
// Interactive wizard. Guard the TTY first: the wizard's prompts read
|
|
241
|
+
// from stdin, so a piped/redirected/CI invocation would render the
|
|
242
|
+
// first prompt and then block forever instead of failing — and `akm
|
|
243
|
+
// setup` is the first command users automate.
|
|
244
|
+
if (process.stdin.isTTY !== true) {
|
|
245
|
+
throw new UsageError("Interactive setup requires a TTY. Pass --yes to accept defaults, or --config <json> / --from <file> to configure non-interactively.", "NON_INTERACTIVE_REQUIRES_YES");
|
|
246
|
+
}
|
|
240
247
|
const { runSetupWizard } = await import("./setup/setup.js");
|
|
241
248
|
await runSetupWizard({ dir: args.dir, noInit });
|
|
242
249
|
}
|
|
@@ -818,40 +825,9 @@ function findUnknownCommandAttempt(rawArgs) {
|
|
|
818
825
|
args = args.slice(idx + 1);
|
|
819
826
|
}
|
|
820
827
|
}
|
|
821
|
-
/**
|
|
822
|
-
* Standard edit-distance DP, single-row-at-a-time (sizes here are always
|
|
823
|
-
* short command-name strings). Builds each row left-to-right, appending as
|
|
824
|
-
* it goes, so every index read below is already-populated — the `?? 0`
|
|
825
|
-
* fallbacks only satisfy `noUncheckedIndexedAccess`, they never fire.
|
|
826
|
-
*/
|
|
827
|
-
function levenshteinDistance(a, b) {
|
|
828
|
-
let previousRow = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
829
|
-
for (let i = 1; i <= a.length; i++) {
|
|
830
|
-
const currentRow = [i];
|
|
831
|
-
for (let j = 1; j <= b.length; j++) {
|
|
832
|
-
const substitutionCost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
833
|
-
const deletion = (previousRow[j] ?? 0) + 1;
|
|
834
|
-
const insertion = (currentRow[j - 1] ?? 0) + 1;
|
|
835
|
-
const substitution = (previousRow[j - 1] ?? 0) + substitutionCost;
|
|
836
|
-
currentRow.push(Math.min(deletion, insertion, substitution));
|
|
837
|
-
}
|
|
838
|
-
previousRow = currentRow;
|
|
839
|
-
}
|
|
840
|
-
return previousRow[b.length] ?? 0;
|
|
841
|
-
}
|
|
842
|
-
/** Closest candidate within a length-scaled distance threshold, or undefined when nothing is close enough to be worth suggesting. */
|
|
828
|
+
/** Closest command within a length-scaled distance threshold (shared DP in cli/unknown-flags.ts). */
|
|
843
829
|
function closestCommandMatch(attempted, candidates) {
|
|
844
|
-
|
|
845
|
-
let bestDistance = Number.POSITIVE_INFINITY;
|
|
846
|
-
for (const candidate of candidates) {
|
|
847
|
-
const distance = levenshteinDistance(attempted, candidate);
|
|
848
|
-
if (distance < bestDistance) {
|
|
849
|
-
bestDistance = distance;
|
|
850
|
-
best = candidate;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
const threshold = Math.max(2, Math.ceil(attempted.length / 2));
|
|
854
|
-
return best !== undefined && bestDistance <= threshold ? best : undefined;
|
|
830
|
+
return closestMatch(attempted, candidates, Math.max(2, Math.ceil(attempted.length / 2)));
|
|
855
831
|
}
|
|
856
832
|
const CLI_HELP_POINTER = "Run `akm --help` for usage.";
|
|
857
833
|
/**
|
|
@@ -898,6 +874,10 @@ function toUsageErrorFromCliError(error, rawArgs) {
|
|
|
898
874
|
const message = error.message.replace(/\x1b\[[0-9;]*m/g, "");
|
|
899
875
|
return new UsageError(message, code, hint);
|
|
900
876
|
}
|
|
877
|
+
/** Normalize citty's private CLIError for the real entrypoint and in-process test harness. */
|
|
878
|
+
export function normalizeCittyCliError(error, rawArgs) {
|
|
879
|
+
return isCittyCliError(error) ? toUsageErrorFromCliError(error, rawArgs) : error;
|
|
880
|
+
}
|
|
901
881
|
/**
|
|
902
882
|
* The CLI's real startup sequence, extracted into a function so error paths
|
|
903
883
|
* can `return` early — top-level `return` is a syntax error in an ES module,
|
|
@@ -1016,6 +996,10 @@ async function runCli() {
|
|
|
1016
996
|
console.log(pkgVersion);
|
|
1017
997
|
return;
|
|
1018
998
|
}
|
|
999
|
+
// citty's parseArgs (strict: false) silently ignores undeclared flags, so
|
|
1000
|
+
// a typo used to run the command with defaults and exit 0. Checked here, after --help and
|
|
1001
|
+
// --version, so those keep working on any command.
|
|
1002
|
+
assertKnownFlags(main, rawArgs);
|
|
1019
1003
|
await runCommand(main, { rawArgs });
|
|
1020
1004
|
}
|
|
1021
1005
|
catch (error) {
|
|
@@ -1027,7 +1011,7 @@ async function runCli() {
|
|
|
1027
1011
|
// + raw `console.error(message)` — a one-line diagnosis plus a short
|
|
1028
1012
|
// hint (with a did-you-mean suggestion when applicable), not a ~46KB
|
|
1029
1013
|
// usage dump.
|
|
1030
|
-
emitJsonError(
|
|
1014
|
+
emitJsonError(normalizeCittyCliError(error, rawArgs));
|
|
1031
1015
|
return;
|
|
1032
1016
|
}
|
|
1033
1017
|
// Anything else escaping here is a genuinely unexpected failure outside
|