akm-cli 0.9.12 → 0.9.14-beta.1
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 +100 -0
- package/dist/assets/workflows/workflow-template.md +4 -0
- package/dist/commands/improve/eligibility.js +27 -15
- package/dist/commands/improve/improve.js +1 -0
- package/dist/commands/lint/base-linter.js +10 -0
- package/dist/commands/proposal/drain.js +48 -6
- package/dist/commands/proposal/proposal-cli.js +1 -0
- package/dist/commands/read/curate.js +3 -2
- package/dist/commands/read/show.js +26 -9
- package/dist/core/adapter/adapters/akm-adapter.js +5 -1
- package/dist/core/asset/markdown-fragments.js +146 -0
- package/dist/core/config/config-walker.js +7 -3
- package/dist/core/config/config.js +21 -12
- package/dist/core/config/schema/primitives.js +8 -2
- package/dist/core/errors.js +2 -0
- package/dist/core/lexical-score.js +25 -0
- package/dist/core/type-presentation.js +36 -4
- package/dist/indexer/index-written-assets.js +4 -0
- package/dist/indexer/indexer.js +5 -2
- package/dist/indexer/passes/metadata.js +64 -1
- package/dist/indexer/scan/doc-to-entry.js +3 -0
- package/dist/indexer/scan/drain-dir.js +33 -22
- package/dist/indexer/search/db-search.js +72 -14
- package/dist/indexer/search/name-match.js +35 -0
- package/dist/indexer/search/ranking-contributors.js +15 -12
- package/dist/indexer/search/ranking.js +42 -18
- package/dist/indexer/usage/show-usage.js +14 -2
- package/dist/llm/client.js +12 -8
- package/dist/llm/embedders/remote.js +3 -2
- package/dist/llm/graph-extract.js +18 -67
- package/dist/output/shapes.js +46 -1
- package/dist/output/text/proposal-format.js +5 -0
- package/dist/scripts/akm-migrate-node.js +648 -253
- package/dist/scripts/akm-migrate.js +648 -253
- package/dist/storage/repositories/index-connection.js +23 -8
- package/dist/storage/repositories/index-entries-repository.js +3 -2
- package/dist/storage/repositories/index-entry-schema.js +43 -3
- package/dist/storage/repositories/index-fts-repository.js +160 -14
- package/dist/storage/repositories/index-schema.js +8 -18
- package/dist/storage/repositories/workflow-runs-repository.js +118 -10
- package/dist/workflows/exec/run-workflow.js +1 -1
- package/dist/workflows/exec/step-work.js +41 -0
- package/dist/workflows/parser.js +1 -1
- package/dist/workflows/runtime/runs.js +29 -5
- package/docs/migration/release-notes/0.9.14.md +26 -0
- package/docs/migration/release-notes/README.md +2 -0
- package/docs/reference/cli.md +18 -0
- package/package.json +1 -1
|
@@ -194,7 +194,7 @@ async function acquireRunLease(runId, holder) {
|
|
|
194
194
|
const row = repo.getRunById(runId);
|
|
195
195
|
throw new UsageError(`Workflow run ${runId} is already being driven by engine ${row?.engine_lease_holder ?? "(unknown)"} ` +
|
|
196
196
|
`(run lease expires ${row?.engine_lease_until ?? "(unknown)"}). A second \`akm workflow run\` would race it — ` +
|
|
197
|
-
`wait for that invocation to finish or for the lease to expire
|
|
197
|
+
`wait for that invocation to finish or for the lease to expire.`, "RUN_LEASE_HELD");
|
|
198
198
|
}));
|
|
199
199
|
}
|
|
200
200
|
/**
|
|
@@ -18,6 +18,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
18
18
|
import unitPreambleTemplate from "../../assets/prompts/workflow-unit-preamble.md" with { type: "text" };
|
|
19
19
|
import { UsageError } from "../../core/errors.js";
|
|
20
20
|
import { validateJsonSchemaSubset } from "../../core/json-schema.js";
|
|
21
|
+
import { parseEmbeddedJsonResponse } from "../../core/parse.js";
|
|
21
22
|
import { canonicalInputJson, validateInputs } from "../../execution/input-contract.js";
|
|
22
23
|
import { withWorkflowRunsRepo, } from "../../storage/repositories/workflow-runs-repository.js";
|
|
23
24
|
import { canonicalJson } from "../ir/plan-hash.js";
|
|
@@ -599,6 +600,41 @@ export function validateStepArtifact(plan, evidence) {
|
|
|
599
600
|
return (`Step "${plan.stepId}" artifact failed validation against the step's declared output schema: ` +
|
|
600
601
|
`${errors.join("; ")}.`);
|
|
601
602
|
}
|
|
603
|
+
/**
|
|
604
|
+
* Warn-only check of each successful unit's own promoted value against its
|
|
605
|
+
* template's declared `schema` (`unit.output`) — the one field a harness that
|
|
606
|
+
* cannot request structured output drops during lowering (`untranslated-field`,
|
|
607
|
+
* field `outputSchema`), after which nothing else ever compares the returned
|
|
608
|
+
* text to it. Unlike {@link validateStepArtifact} this never fails the step:
|
|
609
|
+
* a harness that DID honor the schema already returned a compliant `result`
|
|
610
|
+
* (this re-check then finds nothing), and one that could not is exactly the
|
|
611
|
+
* case this exists to surface — the run continues either way.
|
|
612
|
+
*/
|
|
613
|
+
export function unitSchemaWarning(plan, units) {
|
|
614
|
+
const schema = stepTemplate(plan)?.schema;
|
|
615
|
+
if (!schema)
|
|
616
|
+
return undefined;
|
|
617
|
+
const mismatches = [];
|
|
618
|
+
for (const unit of units) {
|
|
619
|
+
if (!unit.ok)
|
|
620
|
+
continue;
|
|
621
|
+
const candidate = unit.result !== undefined
|
|
622
|
+
? unit.result
|
|
623
|
+
: unit.text !== undefined
|
|
624
|
+
? parseEmbeddedJsonResponse(unit.text)
|
|
625
|
+
: undefined;
|
|
626
|
+
if (candidate === undefined) {
|
|
627
|
+
mismatches.push(`unit "${unit.unitId}" produced no structured output to check`);
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
const errors = validateJsonSchemaSubset(candidate, schema);
|
|
631
|
+
if (errors.length > 0)
|
|
632
|
+
mismatches.push(`unit "${unit.unitId}": ${errors.join("; ")}`);
|
|
633
|
+
}
|
|
634
|
+
if (mismatches.length === 0)
|
|
635
|
+
return undefined;
|
|
636
|
+
return `Output does not match the unit's declared schema (advisory; the run continued): ${mismatches.join("; ")}.`;
|
|
637
|
+
}
|
|
602
638
|
/**
|
|
603
639
|
* Build the summary the completion-criteria gate judges for a step (addendum
|
|
604
640
|
* R2, "typed artifacts, honest gates"): a one-line unit count followed by the
|
|
@@ -743,6 +779,11 @@ export function reduceStepOutcomes(plan, reducer, isFanOut, onError, units) {
|
|
|
743
779
|
artifactSchemaFailure = true;
|
|
744
780
|
}
|
|
745
781
|
}
|
|
782
|
+
if (!artifactSchemaFailure) {
|
|
783
|
+
const schemaWarning = unitSchemaWarning(plan, units);
|
|
784
|
+
if (schemaWarning !== undefined)
|
|
785
|
+
summary += ` ${schemaWarning}`;
|
|
786
|
+
}
|
|
746
787
|
// P3b §3.4: a composed child workflow that blocked is carried on the
|
|
747
788
|
// failed unit's LIVE-ONLY `childRun` field (child-workflow.ts's
|
|
748
789
|
// driveChildWorkflowUnit). Surfaced here, unconditionally on the unit
|
package/dist/workflows/parser.js
CHANGED
|
@@ -249,7 +249,7 @@ function bindStepSections(headings, lines, bodyStartLine, totalLines, path, decl
|
|
|
249
249
|
if (!declaredIds.has(h.text)) {
|
|
250
250
|
errors.push({
|
|
251
251
|
line: h.line,
|
|
252
|
-
message: `Unexpected level-2 heading "## ${h.text}" on line ${h.line} — no step "${h.text}" is declared in frontmatter "steps:". Level-2 headings must exactly match a declared step id.`,
|
|
252
|
+
message: `Unexpected level-2 heading "## ${h.text}" on line ${h.line} — no step "${h.text}" is declared in frontmatter "steps:". Level-2 headings must exactly match a declared step id. To document something that is not a step, use a level-3 heading.`,
|
|
253
253
|
});
|
|
254
254
|
continue;
|
|
255
255
|
}
|
|
@@ -637,14 +637,33 @@ function readWorkflowRunOrPrefix(repo, specifier) {
|
|
|
637
637
|
const run = findRunByIdOrPrefix(repo, specifier);
|
|
638
638
|
if (!run)
|
|
639
639
|
throw new NotFoundError(`Workflow run "${specifier}" not found.`, "WORKFLOW_NOT_FOUND");
|
|
640
|
-
return run;
|
|
640
|
+
return reclaimOrphanedEngineLease(repo, run);
|
|
641
641
|
}
|
|
642
642
|
function readWorkflowRun(repo, runId) {
|
|
643
643
|
const run = repo.getRunById(runId);
|
|
644
644
|
if (!run) {
|
|
645
645
|
throw new NotFoundError(`Workflow run "${runId}" not found.`, "WORKFLOW_NOT_FOUND");
|
|
646
646
|
}
|
|
647
|
-
return run;
|
|
647
|
+
return reclaimOrphanedEngineLease(repo, run);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Self-heal a run's engine lease once it has expired — the orphaned-lease
|
|
651
|
+
* case where an engine crashed without releasing it — mirroring the
|
|
652
|
+
* maintenance barrier's self-reclaim of a wedged sentinel, applied at the
|
|
653
|
+
* points a caller actually asks "what is this run's state". Never touches a
|
|
654
|
+
* live lease: {@link WorkflowRunsRepository.reclaimExpiredEngineLease} is a
|
|
655
|
+
* compare-and-swap on the exact (holder, until) this call observed, so a
|
|
656
|
+
* lease renewed or re-acquired between the read and this write is left alone.
|
|
657
|
+
*/
|
|
658
|
+
function reclaimOrphanedEngineLease(repo, run) {
|
|
659
|
+
const { engine_lease_holder: holder, engine_lease_until: until } = run;
|
|
660
|
+
if (!holder || !until)
|
|
661
|
+
return run;
|
|
662
|
+
const now = new Date().toISOString();
|
|
663
|
+
if (until >= now)
|
|
664
|
+
return run;
|
|
665
|
+
repo.reclaimExpiredEngineLease(run.id, holder, until, now);
|
|
666
|
+
return { ...run, engine_lease_holder: null, engine_lease_until: null };
|
|
648
667
|
}
|
|
649
668
|
function readWorkflowRunSteps(repo, runId) {
|
|
650
669
|
return repo.getStepsForRun(runId);
|
|
@@ -758,8 +777,13 @@ function toWorkflowRunSummary(run) {
|
|
|
758
777
|
executionSupport: plan.support,
|
|
759
778
|
// Surface the engine lease (holder id + expiry — never workflow-authored
|
|
760
779
|
// content) so `workflow run`/`status` show which native execution
|
|
761
|
-
// invocation currently holds the run lease.
|
|
762
|
-
|
|
780
|
+
// invocation currently holds the run lease. Gated on `until` still being
|
|
781
|
+
// in the future: a crashed engine's lease self-expires, and a run
|
|
782
|
+
// whose holder is provably gone must stop reading as engine-driven the
|
|
783
|
+
// instant that happens, not just once something next attempts to acquire
|
|
784
|
+
// it (`readWorkflowRun`/`readWorkflowRunOrPrefix` also reclaim the DB
|
|
785
|
+
// columns outright on the same condition).
|
|
786
|
+
...(run.engine_lease_holder && run.engine_lease_until && run.engine_lease_until >= new Date().toISOString()
|
|
763
787
|
? { engineLease: { holder: run.engine_lease_holder, until: run.engine_lease_until } }
|
|
764
788
|
: {}),
|
|
765
789
|
// P3b (spec §4.5): all three optional and conditionally spread, so every
|
|
@@ -786,7 +810,7 @@ function assertLeaseAllowsSpineAdvance(run, leaseHolder) {
|
|
|
786
810
|
return; // expired ⇒ claimable, not live
|
|
787
811
|
throw new UsageError(`Workflow run ${run.id} is being driven by engine ${run.engine_lease_holder} ` +
|
|
788
812
|
`(run lease expires ${run.engine_lease_until}). The engine owns the step spine while it runs — ` +
|
|
789
|
-
`wait for it to finish or for the lease to expire before advancing steps manually
|
|
813
|
+
`wait for it to finish or for the lease to expire before advancing steps manually.`, "RUN_LEASE_HELD");
|
|
790
814
|
}
|
|
791
815
|
function toWorkflowRunStepState(step) {
|
|
792
816
|
return {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Migration notes for akm v0.9.14
|
|
2
|
+
|
|
3
|
+
The derived `index.db` generation changes from v22 to v23 to support lexical
|
|
4
|
+
Markdown fragments. On the first normal read after upgrade, akm detects that
|
|
5
|
+
the v22 cache cannot serve queries and performs one inline v23 rebuild from
|
|
6
|
+
currently materialized sources before serving the request. An explicit
|
|
7
|
+
`akm index` performs the same writable rebuild. No hand-written database
|
|
8
|
+
migration or manual deletion is needed, but the first rebuild may take longer
|
|
9
|
+
than a normal read. Do not run an older akm against an index this release has
|
|
10
|
+
already rebuilt: upgrade that binary instead. If v23 schema creation is
|
|
11
|
+
interrupted, the partial cache is not admitted as current; the next writable
|
|
12
|
+
open rebuilds it, while an existing/read-only opener reports that `akm index`
|
|
13
|
+
is required.
|
|
14
|
+
|
|
15
|
+
Search may now return an addressable `#akm-fragment-…` suffix for the matching
|
|
16
|
+
part of a long Markdown asset. Pass that returned ref to `akm show` to display
|
|
17
|
+
the exact indexed fragment.
|
|
18
|
+
|
|
19
|
+
If you use collapse-detector canaries, their baseline predates the new index
|
|
20
|
+
generation. After the `akm index` rebuild/reindex completes, explicitly mint a
|
|
21
|
+
new set from the repository checkout:
|
|
22
|
+
|
|
23
|
+
bun scripts/refresh-canary-set.ts --refresh
|
|
24
|
+
|
|
25
|
+
The refresh is deliberate and is never automatic, so historical canary cycles
|
|
26
|
+
remain interpretable against their original baseline.
|
|
@@ -7,6 +7,8 @@ live one level up in `docs/migration/`.
|
|
|
7
7
|
|
|
8
8
|
## Available notes
|
|
9
9
|
|
|
10
|
+
- [0.9.14](0.9.14.md) — index v22-to-v23 derived-cache rebuild, lexical
|
|
11
|
+
fragments, and collapse-detector canary re-minting
|
|
10
12
|
- [0.9.2](0.9.2.md) — task source v4 migration, workflow source IR v1 and
|
|
11
13
|
durable-v4-family `irVersion: 5`, command diagnostics, and strategy judgment
|
|
12
14
|
migration
|
package/docs/reference/cli.md
CHANGED
|
@@ -79,6 +79,24 @@ For local materialized assets, `editHint` is added only when `editable` is
|
|
|
79
79
|
or use `action` (or curate `followUp`). Registry-only results have no local
|
|
80
80
|
`path`, `editable`, or `editHint`.
|
|
81
81
|
|
|
82
|
+
### The `results` collection alias
|
|
83
|
+
|
|
84
|
+
Every list-returning command names its collection field differently —
|
|
85
|
+
`search` returns `hits`, `curate` returns `items`, `proposal list` returns
|
|
86
|
+
`proposals`, `bundle list` returns `sources`, `env list` returns `envs`,
|
|
87
|
+
`secret list` returns `secrets`, `registry list` returns `registries`,
|
|
88
|
+
`registry search` returns `hits`, `workflow list` returns `runs`,
|
|
89
|
+
`task history` returns `rows`, `log list` returns `events`. A caller that does
|
|
90
|
+
not already know each command's key cannot write one accessor across all of
|
|
91
|
+
them.
|
|
92
|
+
|
|
93
|
+
Every one of these commands also carries a `results` field — the identical
|
|
94
|
+
array, not a copy — alongside its semantic key, in every `--format`/`--detail`
|
|
95
|
+
combination and both `--shape human` (the default) and `--shape agent`. Code
|
|
96
|
+
written against a single command should keep using its semantic key for
|
|
97
|
+
clarity; code that needs to handle several list commands uniformly can read
|
|
98
|
+
`results` and never maintain a per-command lookup table.
|
|
99
|
+
|
|
82
100
|
### `--shape summary`
|
|
83
101
|
|
|
84
102
|
Valid **only on `akm show`**. Every other command rejects `--shape summary`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.14-beta.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cursor.",
|
|
6
6
|
"keywords": [
|