akm-cli 0.9.0-rc.13 → 0.9.0-rc.15
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 +162 -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 +5 -0
- package/dist/cli/unknown-flags.js +226 -0
- package/dist/cli.js +19 -35
- package/dist/commands/agent/agent-dispatch.js +7 -3
- package/dist/commands/agent/contribute-cli.js +15 -3
- package/dist/commands/feedback-cli.js +6 -3
- package/dist/commands/health/checks.js +6 -3
- package/dist/commands/improve/collapse-detector.js +2 -3
- package/dist/commands/improve/reflect.js +5 -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/propose.js +5 -3
- 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/init.js +1 -1
- package/dist/commands/sources/self-update.js +30 -7
- package/dist/commands/sources/source-add.js +17 -2
- package/dist/commands/tasks/tasks.js +10 -10
- package/dist/commands/workflow-cli.js +133 -259
- 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 +0 -17
- package/dist/core/config/schema/sources-bundles.js +11 -0
- package/dist/core/config/schema/workflow.js +3 -3
- package/dist/core/errors.js +5 -1
- 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/engine-fallback.js +76 -0
- package/dist/integrations/agent/profiles.js +14 -0
- package/dist/integrations/agent/spawn.js +1 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
- package/dist/integrations/lockfile.js +11 -56
- package/dist/output/shapes/passthrough.js +0 -6
- 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 +15 -181
- package/dist/output/text/workflow.js +1 -6
- package/dist/scripts/akm-migrate-node.js +58666 -0
- package/dist/scripts/akm-migrate.js +33976 -11498
- package/dist/setup/detect.js +42 -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/storage/repositories/workflow-runs-repository.js +9 -23
- package/dist/tasks/runner.js +43 -19
- package/dist/tasks/schema.js +24 -1
- package/dist/tasks/validator.js +6 -2
- package/dist/workflows/exec/frozen-judge.js +28 -2
- package/dist/workflows/exec/native-executor.js +44 -28
- package/dist/workflows/exec/param-secrets.js +9 -9
- package/dist/workflows/exec/run-workflow.js +122 -60
- package/dist/workflows/exec/step-work.js +64 -175
- package/dist/workflows/exec/unit-dispatch.js +4 -0
- package/dist/workflows/ir/compile.js +2 -2
- package/dist/workflows/ir/freeze.js +29 -9
- package/dist/workflows/ir/params.js +135 -11
- 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 +72 -92
- package/dist/workflows/runtime/unit-checkin.js +1 -1
- package/dist/workflows/runtime/unit-phases.js +2 -2
- 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 +70 -100
- package/docs/reference/data-and-telemetry.md +5 -5
- package/package.json +7 -3
- package/schemas/akm-config.json +24 -6
- package/schemas/akm-workflow.json +1 -1
- package/dist/workflows/cli.js +0 -33
- package/dist/workflows/exec/brief.js +0 -450
- package/dist/workflows/exec/report.js +0 -1460
- package/dist/workflows/exec/workflow-engine-gate.js +0 -67
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,124 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
### Removed
|
|
10
|
+
|
|
11
|
+
- **The experimental `akm workflow brief` / `akm workflow report`
|
|
12
|
+
external-driver protocol is removed**, along with the
|
|
13
|
+
`experimental.workflowEngine` config key that gated it, its
|
|
14
|
+
`WORKFLOW_ENGINE_NOT_ENABLED` error code, and the `workflowEngine` block in
|
|
15
|
+
`akm task doctor`. `akm workflow run` is now the single execution surface.
|
|
16
|
+
|
|
17
|
+
The protocol let a calling agent session execute a run's units itself
|
|
18
|
+
instead of akm dispatching them. Its stated justification was harness
|
|
19
|
+
neutrality, which measurement did not support: native dispatch already
|
|
20
|
+
covers **ten** harnesses (opencode, claude, opencode-sdk, codex, copilot,
|
|
21
|
+
pi, gemini, aider, amazonq, openhands) in 2,214 LOC total, while the
|
|
22
|
+
protocol cost 2,690 LOC on its own — more than supporting every harness
|
|
23
|
+
natively — and an eleventh harness is ~220 lines, not a protocol. Removing
|
|
24
|
+
it also drops the second consumer of `workflow_run_units` and the
|
|
25
|
+
cross-surface parity obligation on `step-work.ts`, both of which
|
|
26
|
+
constrained every future engine change. The analysis is recorded in
|
|
27
|
+
`docs/architecture/specs/driver-protocol-keep-or-cut.md`.
|
|
28
|
+
|
|
29
|
+
Legacy configs setting `experimental.workflowEngine` remain valid — the
|
|
30
|
+
config schema is `.passthrough()`, so the key is accepted and ignored.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **`akm workflow run` and prompt tasks fall back to `opencode-sdk` instead of
|
|
35
|
+
refusing when no engine is configured.** A clean install that never ran
|
|
36
|
+
`akm setup` — a bare container, a CI image, an agent-operated session — used
|
|
37
|
+
to fail closed with `INVALID_CONFIG_FILE` (exit 78). When the `opencode`
|
|
38
|
+
binary is on PATH, akm now synthesizes a **config-free** `opencode-sdk`
|
|
39
|
+
engine: it carries no model, endpoint, or credential, so provider, model,
|
|
40
|
+
and auth all resolve from opencode's own configuration and akm never mirrors
|
|
41
|
+
or validates it. With `opencode` absent the failure is unchanged, and its
|
|
42
|
+
remedy now names both routes. An operator-configured `opencode-sdk` engine
|
|
43
|
+
always wins over the synthesized one.
|
|
44
|
+
|
|
45
|
+
The requirement is the **binary**, not the npm package: `@opencode-ai/sdk`
|
|
46
|
+
is an HTTP client that declares no dependencies and whose own
|
|
47
|
+
`createOpencodeServer` spawns `opencode serve`, so a host with the package
|
|
48
|
+
and no binary has no server to reach. Install it with `npm i -g opencode-ai`
|
|
49
|
+
or opencode's own installer.
|
|
50
|
+
|
|
51
|
+
The fallback is **announced, never silent**: a workflow run surfaces it once
|
|
52
|
+
at run creation in the result's `warnings`, and a prompt task writes it to
|
|
53
|
+
the task run log. The frozen plan records the engine actually used, so a
|
|
54
|
+
resume never re-announces a decision it did not make.
|
|
55
|
+
|
|
56
|
+
- **RSS, Bluesky, and X sources.** `akm bundle add` now recognizes three new
|
|
57
|
+
kinds of URL and snapshots them as knowledge assets instead of crawling
|
|
58
|
+
them as ordinary web pages:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
akm bundle add https://blog.example/feed # RSS 2.0 / Atom / RDF
|
|
62
|
+
akm bundle add https://bsky.app/profile/<handle> # public, no auth
|
|
63
|
+
akm bundle add https://x.com/<user> # see token note below
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Any of these falling through — a `/feed` URL that actually serves HTML, an
|
|
67
|
+
unresolvable Bluesky handle — degrades to the normal website crawl rather
|
|
68
|
+
than failing the command.
|
|
69
|
+
|
|
70
|
+
X needs credentials: set `X_BEARER_TOKEN` for the X API v2, or
|
|
71
|
+
`X_RSS_TEMPLATE` to an RSS bridge URL containing `{username}`. To keep the
|
|
72
|
+
token out of your shell history, store it as an akm secret and inject it
|
|
73
|
+
per-invocation:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
akm secret set x-bearer-token
|
|
77
|
+
akm secret run secrets/x-bearer-token X_BEARER_TOKEN -- akm bundle add https://x.com/<user>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
With neither set, the X fetcher emits one warning and falls through.
|
|
81
|
+
|
|
82
|
+
### Changed
|
|
83
|
+
|
|
84
|
+
- **X source tokens now resolve from the secret store during bundle update.**
|
|
85
|
+
The `secrets/x-bearer-token` akm secret is honored on the provider
|
|
86
|
+
`sync()` / bundle-update path, not just when adding or importing a URL —
|
|
87
|
+
closing a gap where a refresh saw only the `X_BEARER_TOKEN` environment
|
|
88
|
+
variable. Implemented as a `SecretResolver` capability injected from above
|
|
89
|
+
the source-provider import cycle; internals are documented in
|
|
90
|
+
`docs/architecture/reviews/env-secret-access.md`.
|
|
91
|
+
|
|
92
|
+
- **`website` crawls now have a hard time limit.** `crawlTimeoutMs` (default
|
|
93
|
+
600000 — 10 minutes) bounds the entire crawl, and unlike the previous
|
|
94
|
+
between-page check it aborts work already in flight: a `Retry-After` sleep
|
|
95
|
+
could previously park `akm bundle add` for as long as a rate-limiting server
|
|
96
|
+
asked, well past the advertised cap. Raise it for a large site, or set
|
|
97
|
+
`"crawlTimeoutMs": 0` to disable the cap. Relatedly, `fetchWithRetry` now
|
|
98
|
+
honors its caller's `AbortSignal` during retry backoff, so any operation that
|
|
99
|
+
passes a signal can actually interrupt a long wait.
|
|
100
|
+
|
|
101
|
+
- **Website snapshots now extract the page's main content.** Conversion moved
|
|
102
|
+
from a hand-rolled regex converter to a DOM parse plus Turndown, scoped to
|
|
103
|
+
the page's content region (`<main>`, `<article>`, `[role=main]`, then common
|
|
104
|
+
content ids/classes, falling back to `<body>` minus nav/header/footer/aside).
|
|
105
|
+
Navigation, ads, and boilerplate no longer land in snapshots, and tables,
|
|
106
|
+
nested lists, and fenced code blocks with language hints now survive
|
|
107
|
+
conversion. **Existing website snapshots will change on their next refresh**
|
|
108
|
+
— expect them to get shorter and cleaner. Link discovery still scans the
|
|
109
|
+
whole page, so crawl coverage is unchanged.
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
- **`website` sources now respect `robots.txt` by default.** Before crawling
|
|
113
|
+
an origin, akm fetches and parses that origin's `/robots.txt` and skips
|
|
114
|
+
paths disallowed for the `akm`/`akm-cli` product tokens (or `*`), honoring
|
|
115
|
+
`Crawl-delay` (clamped to 10s) between page fetches. This is a deliberate
|
|
116
|
+
behavior change: **existing website sources may return fewer pages, or
|
|
117
|
+
fail with an error if the start URL itself is disallowed, after
|
|
118
|
+
upgrading.** Re-running `akm bundle update` on a website source is what
|
|
119
|
+
surfaces it. Opt out with `"respectRobots": false` on the website
|
|
120
|
+
descriptor to restore the exact pre-upgrade behavior (no `/robots.txt`
|
|
121
|
+
request at all):
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{ "bundles": { "docs": { "website": { "url": "https://docs.example.com", "respectRobots": false } } } }
|
|
125
|
+
```
|
|
126
|
+
|
|
9
127
|
## [0.9.0-rc.13] - 2026-07-31
|
|
10
128
|
|
|
11
129
|
### Security
|
|
@@ -139,6 +257,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
139
257
|
|
|
140
258
|
### Changed
|
|
141
259
|
|
|
260
|
+
- **Workflow execution is consolidated on stable `akm workflow run`.** The
|
|
261
|
+
public `workflow start`, `next`, and `complete` commands are removed with
|
|
262
|
+
explicit `UNKNOWN_COMMAND` migration hints; `run <ref|run-id>` now owns
|
|
263
|
+
creation, active-run continuation, native dispatch, completion, and durable
|
|
264
|
+
replay. It is no longer gated by `experimental.workflowEngine`; only the
|
|
265
|
+
experimental `brief`/`report` external-driver protocol retains that opt-in.
|
|
266
|
+
Workflow parameters move from the opaque `--params '<json>'` bag to exact
|
|
267
|
+
declared flags (`--version 1.2.3`, repeated array flags, JSON object/array
|
|
268
|
+
values) coerced through the frozen parameter schemas. New invocation controls
|
|
269
|
+
add bounded failed-step retries (`--max-retries`) and a whole-run timeout
|
|
270
|
+
(`--timeout N|Nms|Ns|Nm`); failures, gate rejection, timeout, and signals now
|
|
271
|
+
produce non-zero process statuses while leaving interrupted work resumable.
|
|
272
|
+
|
|
273
|
+
Criteria-bearing gates now require `workflow.judgeEngine`, which may name a
|
|
274
|
+
configured LLM or agent engine and is frozen into the run. Verification is
|
|
275
|
+
fail-closed: a missing/failing verifier or malformed verdict rejects instead
|
|
276
|
+
of silently advancing. Scheduled workflow tasks now execute through the same
|
|
277
|
+
native orchestrator rather than stopping after run creation. Migration:
|
|
278
|
+
replace `workflow start/next/complete` loops with `workflow run`, replace
|
|
279
|
+
`--params` with exact declared flags, and configure `workflow.judgeEngine`
|
|
280
|
+
before running a workflow with a non-empty `### gate` rubric.
|
|
281
|
+
|
|
142
282
|
- **The two workflow authoring formats — markdown documents and YAML
|
|
143
283
|
orchestration programs — are unified into one format**, per
|
|
144
284
|
`docs/architecture/specs/workflow-format-unification.md`. A workflow is
|
|
@@ -169,15 +309,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
169
309
|
bullet section — they live under a step's `### gate` sub-heading, the
|
|
170
310
|
format's one reserved marker, as full prose a judge receives byte-exact.
|
|
171
311
|
Frontmatter `gate:` now carries only optional `max_loops` configuration.
|
|
172
|
-
Omitted or empty rubric text skips validation; a non-empty rubric
|
|
173
|
-
|
|
312
|
+
Omitted or empty rubric text skips validation; a non-empty rubric requires
|
|
313
|
+
the frozen `workflow.judgeEngine`, and unavailable or malformed judges reject
|
|
314
|
+
the gate.
|
|
174
315
|
|
|
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.
|
|
316
|
+
This is a **pre-1.0 format change**. The ten example workflows under
|
|
317
|
+
`scripts/akm-eval/example-stash/workflows/` are rewritten to the unified
|
|
318
|
+
format in this change; existing user-authored workflow assets must be updated
|
|
319
|
+
manually before execution.
|
|
181
320
|
|
|
182
321
|
- **akm is described as a knowledge toolkit, not a package manager** (R-048).
|
|
183
322
|
The npm one-liner, the README lede, and the `concepts.md` opener all led with
|
|
@@ -766,9 +905,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
766
905
|
- **The CHURN alert class is removed from the collapse detector.** Its input was
|
|
767
906
|
a hard-coded `0` from the 0.9.0 confidence-gate deletion onward, so the alert
|
|
768
907
|
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.
|
|
908
|
+
`improve_cycle_metrics.accepted_actions` column stays and is written as `0`
|
|
909
|
+
because deployed 0.8 `state.db` files already contain it.
|
|
772
910
|
|
|
773
911
|
- **`IndexResponse.graphQuality` is removed** from the `akm index` envelope — it
|
|
774
912
|
was declared but never assigned in any code path, so it was always absent.
|
|
@@ -839,15 +977,16 @@ earlier `0.9.0-rc.1` / `0.9.0-beta.*` development entries below.
|
|
|
839
977
|
order). The pre-0.9.0 `[origin//]type:name` grammar is removed — there is no
|
|
840
978
|
compatibility parser; the frozen migrator in `scripts/akm-migrate/migrate/`
|
|
841
979
|
is the only place it survives.
|
|
842
|
-
- **Explicit,
|
|
980
|
+
- **Explicit, crash-resumable cutover (`akm migrate apply`).** The
|
|
843
981
|
migrator re-keys all durable state to the new spelling, folds the former
|
|
844
982
|
`workflow.db` into `state.db` (four databases down to three: `state.db` /
|
|
845
983
|
`index.db` / a separate `logs.db`), and migrates config from the flat
|
|
846
984
|
`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
|
-
|
|
985
|
+
`defaultBundle`. A semantically verified, installation-scoped **backup manifest v4**
|
|
986
|
+
(covering the pre-rescue `index.db`) is taken before mutation. One phase-free
|
|
987
|
+
incomplete sentinel retains that backup and target; expected orphans are
|
|
988
|
+
quarantined, integrity failures fail closed, and the whole cutover reruns
|
|
989
|
+
idempotently after a crash. Normal commands refuse an
|
|
851
990
|
un-migrated or divergent durable schema rather than migrating as a side effect.
|
|
852
991
|
The retired `stashDir` / `sources` / `installed` keys are **hard-rejected** by
|
|
853
992
|
the 0.9.0 config schema whenever present (the error names `akm migrate apply`);
|
|
@@ -888,13 +1027,13 @@ See `docs/migration/v0.8-to-v0.9.md` and
|
|
|
888
1027
|
migration, dashboard, or health schema was added.
|
|
889
1028
|
- **Explicit, crash-resumable 0.9 migration coordination.** `akm migrate
|
|
890
1029
|
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`.
|
|
1030
|
+
`akm migrate apply [--config <prepared>]` creates a semantically verified,
|
|
1031
|
+
installation-scoped config/database backup before applying pending migrations.
|
|
1032
|
+
Apply and restore use one phase-free incomplete sentinel, bounded control-file
|
|
1033
|
+
reads, SQLite integrity and ordered-ledger checks, active-writer barriers,
|
|
1034
|
+
WAL/SHM-safe publication, and idempotent replay. Legacy checksum columns are
|
|
1035
|
+
inert. Routine reads and current database opens no longer depend on a
|
|
1036
|
+
historical cutover bundle. See `docs/migration/release-notes/0.9.0.md`.
|
|
898
1037
|
- **Workflow orchestration engine (experimental).** akm can now execute
|
|
899
1038
|
multi-step workflows through a native engine or any agent session. Workflow
|
|
900
1039
|
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,11 @@ 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 — `akm workflow run <target>` completes steps automatically; use `akm workflow status <target>` to inspect.",
|
|
60
|
+
"workflow brief": "`akm workflow brief` was removed — the external-driver protocol is gone; `akm workflow run <target>` executes the run and `akm workflow status <target>` inspects it.",
|
|
61
|
+
"workflow report": "`akm workflow report` was removed — the external-driver protocol is gone; `akm workflow run <target>` dispatches and records units itself.",
|
|
57
62
|
"config show": "`akm config show` was removed in 0.9 — use `akm config list`.",
|
|
58
63
|
"config validate": "`akm config validate` was removed in 0.9 — the config file is validated on every load.",
|
|
59
64
|
"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
|
+
}
|