akm-cli 0.9.11 → 0.9.13

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.
Files changed (134) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/STABILITY.md +6 -1
  3. package/dist/assets/hints/cli-hints-full.md +1 -1
  4. package/dist/assets/improve-strategies/consolidate.json +1 -1
  5. package/dist/assets/improve-strategies/default.json +1 -1
  6. package/dist/assets/improve-strategies/thorough.json +1 -2
  7. package/dist/assets/workflows/workflow-template.md +4 -0
  8. package/dist/cli/shared.js +16 -4
  9. package/dist/cli.js +15 -13
  10. package/dist/commands/agent/agent-dispatch.js +8 -0
  11. package/dist/commands/command/execution-source-loader.js +25 -22
  12. package/dist/commands/command/portable-template.js +4 -26
  13. package/dist/commands/config-cli.js +10 -4
  14. package/dist/commands/env/env-binding.js +10 -3
  15. package/dist/commands/env/env-cli.js +7 -0
  16. package/dist/commands/env/secret-cli.js +15 -4
  17. package/dist/commands/health/checks.js +186 -71
  18. package/dist/commands/health.js +16 -4
  19. package/dist/commands/improve/distill/quality-gate.js +2 -2
  20. package/dist/commands/improve/distill.js +28 -12
  21. package/dist/commands/improve/execution.js +1 -2
  22. package/dist/commands/improve/extract.js +82 -56
  23. package/dist/commands/improve/improve-strategies.js +26 -8
  24. package/dist/commands/improve/improve.js +14 -0
  25. package/dist/commands/improve/preparation.js +9 -6
  26. package/dist/commands/improve/reflect.js +61 -77
  27. package/dist/commands/lint/base-linter.js +10 -0
  28. package/dist/commands/lint/index.js +3 -1
  29. package/dist/commands/migrate-cli.js +6 -4
  30. package/dist/commands/proposal/drain-policies.js +22 -2
  31. package/dist/commands/proposal/drain.js +48 -6
  32. package/dist/commands/proposal/proposal-cli.js +1 -0
  33. package/dist/commands/proposal/repository.js +4 -4
  34. package/dist/commands/proposal/validators/proposal-quality-validators.js +23 -2
  35. package/dist/commands/proposal/validators/proposals.js +10 -19
  36. package/dist/commands/read/show.js +42 -31
  37. package/dist/commands/registry-cli.js +4 -2
  38. package/dist/commands/sources/init.js +4 -8
  39. package/dist/commands/sources/self-update.js +2 -2
  40. package/dist/commands/sources/source-clone.js +5 -7
  41. package/dist/commands/sources/sources-cli.js +3 -5
  42. package/dist/commands/tasks/tasks-cli.js +4 -12
  43. package/dist/commands/tasks/tasks.js +38 -35
  44. package/dist/commands/workflow-cli.js +17 -15
  45. package/dist/core/activation-policy.js +31 -3
  46. package/dist/core/adapter/execution-source.js +39 -11
  47. package/dist/core/asset/stash-meta.js +7 -41
  48. package/dist/core/common.js +8 -17
  49. package/dist/core/config/config-schema.js +3 -23
  50. package/dist/core/config/config-walker.js +56 -6
  51. package/dist/core/config/config.js +42 -17
  52. package/dist/core/config/legacy-source-shape-shim.js +79 -0
  53. package/dist/core/config/schema/embedding.js +2 -2
  54. package/dist/core/config/schema/engines.js +2 -2
  55. package/dist/core/config/schema/index-config.js +19 -21
  56. package/dist/core/config/schema/primitives.js +27 -10
  57. package/dist/core/config/schema/sources-bundles.js +1 -6
  58. package/dist/core/errors.js +4 -3
  59. package/dist/core/improve-types.js +17 -0
  60. package/dist/core/json-schema.js +1 -11
  61. package/dist/core/maintenance-barrier.js +17 -2
  62. package/dist/core/paths.js +12 -15
  63. package/dist/core/state/migrations.js +28 -0
  64. package/dist/core/state-db.js +28 -1
  65. package/dist/core/write-source.js +6 -6
  66. package/dist/indexer/bundle-identity-guard.js +3 -0
  67. package/dist/indexer/ensure-index.js +5 -0
  68. package/dist/indexer/indexer.js +11 -3
  69. package/dist/indexer/lookup/adapter-concept-owner.js +14 -3
  70. package/dist/indexer/passes/metadata.js +16 -5
  71. package/dist/indexer/search/search-fields.js +1 -30
  72. package/dist/integrations/agent/engine-resolution.js +15 -1
  73. package/dist/integrations/agent/model-map.js +16 -10
  74. package/dist/integrations/agent/prompts.js +13 -6
  75. package/dist/integrations/lockfile.js +22 -7
  76. package/dist/llm/client.js +28 -8
  77. package/dist/llm/embedders/remote.js +3 -2
  78. package/dist/llm/index-passes.js +3 -2
  79. package/dist/output/shapes/passthrough.js +9 -3
  80. package/dist/output/shapes.js +50 -3
  81. package/dist/output/text/proposal-format.js +5 -0
  82. package/dist/output/text/workflow-format.js +8 -1
  83. package/dist/scripts/akm-migrate-node.js +1737 -1392
  84. package/dist/scripts/akm-migrate.js +1736 -1391
  85. package/dist/setup/setup.js +14 -21
  86. package/dist/sources/include.js +150 -20
  87. package/dist/sources/providers/git-install.js +14 -12
  88. package/dist/sources/providers/git-provider.js +3 -3
  89. package/dist/sources/snapshot-fetchers/website-ingest.js +54 -16
  90. package/dist/sources/website-url.js +12 -4
  91. package/dist/storage/engines/sqlite-migrations.js +40 -10
  92. package/dist/storage/like-pattern.js +7 -0
  93. package/dist/storage/repositories/extract-sessions-repository.js +23 -0
  94. package/dist/storage/repositories/index-connection.js +27 -10
  95. package/dist/storage/repositories/index-entry-schema.js +19 -2
  96. package/dist/storage/repositories/index-schema.js +30 -9
  97. package/dist/storage/repositories/proposals-repository.js +2 -1
  98. package/dist/storage/repositories/task-history-repository.js +14 -7
  99. package/dist/storage/repositories/workflow-runs-repository.js +133 -11
  100. package/dist/storage/sqlite-read-snapshot.js +11 -9
  101. package/dist/tasks/backends/cron.js +34 -5
  102. package/dist/tasks/backends/launchd.js +23 -26
  103. package/dist/tasks/backends/schtasks.js +50 -3
  104. package/dist/tasks/frozen-script.js +2 -0
  105. package/dist/tasks/prepare/prepare.js +2 -7
  106. package/dist/tasks/prepare/script-capture.js +38 -6
  107. package/dist/tasks/schedule.js +154 -13
  108. package/dist/tasks/source/task-source-v3-frozen.js +0 -1
  109. package/dist/tasks/source/task-source-v4.js +0 -1
  110. package/dist/workflows/exec/child-workflow.js +2 -3
  111. package/dist/workflows/exec/exec-unit.js +3 -4
  112. package/dist/workflows/exec/run-workflow.js +20 -11
  113. package/dist/workflows/exec/step-work.js +76 -56
  114. package/dist/workflows/freeze/resolve-steps.js +19 -11
  115. package/dist/workflows/freeze/source-freeze.js +7 -0
  116. package/dist/workflows/freeze/targets/child-workflow.js +12 -18
  117. package/dist/workflows/freeze/targets/command.js +14 -2
  118. package/dist/workflows/ir/environment-v4.js +4 -2
  119. package/dist/workflows/ir/freeze-v4.js +2 -5
  120. package/dist/workflows/ir/plan-hash.js +0 -3
  121. package/dist/workflows/ir/schema-v4.js +14 -9
  122. package/dist/workflows/ir/schema.js +1 -3
  123. package/dist/workflows/parser.js +1 -1
  124. package/dist/workflows/resource-limits.js +35 -48
  125. package/dist/workflows/runtime/plan-classifier.js +89 -41
  126. package/dist/workflows/runtime/run-outputs.js +1 -21
  127. package/dist/workflows/runtime/runs.js +104 -154
  128. package/dist/workflows/source-files.js +28 -54
  129. package/dist/workflows/source-ir/program.js +2 -2
  130. package/dist/workflows/source-ir/semantics.js +5 -23
  131. package/docs/migration/v0.9.1-to-v0.9.2.md +20 -0
  132. package/docs/reference/cli.md +92 -17
  133. package/package.json +1 -1
  134. package/schemas/akm-config.json +5 -10
@@ -15,6 +15,7 @@ import path from "node:path";
15
15
  import { compareCodePoints, toPosix } from "../core/common.js";
16
16
  import { UsageError } from "../core/errors.js";
17
17
  import { canonicalizeWorkflowName, WORKFLOW_EXTENSIONS } from "../core/recognition-util.js";
18
+ import { warnOnce } from "../core/warn.js";
18
19
  export class WorkflowSourceRejectionError extends UsageError {
19
20
  sourcePaths;
20
21
  constructor(message, code, sourcePaths) {
@@ -34,23 +35,6 @@ export class WorkflowSourceCollisionError extends WorkflowSourceRejectionError {
34
35
  Object.setPrototypeOf(this, new.target.prototype);
35
36
  }
36
37
  }
37
- export class WorkflowSourceDomainError extends WorkflowSourceRejectionError {
38
- canonicalName;
39
- constructor(canonicalName, sourcePaths, issues, collidingSourcePaths) {
40
- const sortedPaths = [...sourcePaths].sort(compareCodePoints);
41
- const sortedCollisions = [...collidingSourcePaths].sort(compareCodePoints);
42
- const code = issues.some((issue) => issue.code === "PATH_ESCAPE_VIOLATION")
43
- ? "PATH_ESCAPE_VIOLATION"
44
- : "WORKFLOW_SOURCE_INVALID";
45
- const collisionDetail = sortedCollisions.length > 1 ? ` Valid owners also collide: ${sortedCollisions.join(", ")}.` : "";
46
- super(`Workflow "${canonicalName}" has an invalid source ownership domain across candidates: ${sortedPaths.join(", ")}. ` +
47
- `Problems: ${issues.map((issue) => issue.message).join(" ")}${collisionDetail} ` +
48
- "Every candidate in the canonical domain is rejected until all invalid or duplicate sources are removed.", code, sortedPaths);
49
- this.name = "WorkflowSourceDomainError";
50
- this.canonicalName = canonicalName;
51
- Object.setPrototypeOf(this, new.target.prototype);
52
- }
53
- }
54
38
  export class WorkflowSourceIdentityError extends UsageError {
55
39
  constructor(ref, indexedPath, authoritativePath) {
56
40
  super(`Indexed workflow source identity for "${ref}" points to ${indexedPath}, but the authoritative source is ${authoritativePath}. ` +
@@ -171,11 +155,7 @@ export function listWorkflowSourceFiles(sourceRoot, adapterId, name) {
171
155
  });
172
156
  }
173
157
  candidates.sort((left, right) => compareCodePoints(left.relativePath, right.relativePath));
174
- const { sources, issues } = inspectWorkflowSourceDomain(candidates, canonicalName, realRoot);
175
- if (issues.length > 0) {
176
- throw workflowSourceDomainError(adapterId, canonicalName, candidates, sources, issues);
177
- }
178
- return sources;
158
+ return inspectWorkflowSourceDomain(candidates, canonicalName, realRoot);
179
159
  }
180
160
  /**
181
161
  * Resolve a pre-enumerated set of authored workflow candidates in one batch.
@@ -233,47 +213,44 @@ export function resolveWorkflowSourceDomains(sourceRoot, adapterId, sourcePaths)
233
213
  for (const canonicalName of [...candidatesByName.keys()].sort(compareCodePoints)) {
234
214
  const candidates = candidatesByName.get(canonicalName) ?? [];
235
215
  candidates.sort((left, right) => compareCodePoints(left.relativePath, right.relativePath));
236
- const { sources, issues } = inspectWorkflowSourceDomain(candidates, canonicalName, realRoot);
216
+ const sources = inspectWorkflowSourceDomain(candidates, canonicalName, realRoot);
237
217
  const sourcePaths = candidates.map((candidate) => candidate.relativePath);
238
- if (issues.length > 0) {
239
- resolutions.push({
240
- canonicalName,
241
- sourcePaths,
242
- rejection: workflowSourceDomainError(adapterId, canonicalName, candidates, sources, issues),
243
- });
244
- continue;
245
- }
246
- if (sources.length > 1) {
247
- resolutions.push({
248
- canonicalName,
249
- sourcePaths,
250
- rejection: new WorkflowSourceCollisionError(adapterId === "akm" ? `workflows/${canonicalName}` : canonicalName, sources.map((source) => source.relativePath)),
251
- });
252
- continue;
253
- }
254
- resolutions.push({ canonicalName, sourcePaths, source: sources[0] });
218
+ resolutions.push({
219
+ canonicalName,
220
+ sourcePaths,
221
+ source: pickWorkflowSource(adapterId, canonicalName, sources),
222
+ });
255
223
  }
256
224
  return resolutions;
257
225
  }
258
226
  function inspectWorkflowSourceDomain(candidates, canonicalName, realRoot) {
259
227
  const sources = [];
260
- const issues = [];
261
228
  for (const candidate of candidates) {
262
229
  const inspection = inspectWorkflowSourceCandidate(candidate, canonicalName, realRoot);
263
- if (inspection.source)
230
+ if (inspection.source) {
264
231
  sources.push(inspection.source);
265
- issues.push(...inspection.issues);
232
+ continue;
233
+ }
234
+ for (const issue of inspection.issues) {
235
+ warnOnce(`workflow-source-invalid:${issue.sourcePaths.join(",")}`, issue.message);
236
+ }
266
237
  }
267
- return { sources, issues };
238
+ return sources;
268
239
  }
269
- function workflowSourceDomainError(adapterId, canonicalName, candidates, sources, issues) {
270
- return new WorkflowSourceDomainError(adapterId === "akm" ? `workflows/${canonicalName}` : canonicalName, candidates.map((candidate) => candidate.relativePath), issues, sources.map((source) => source.relativePath));
240
+ function pickWorkflowSource(adapterId, canonicalName, sources) {
241
+ if (sources.length <= 1)
242
+ return sources[0];
243
+ const winner = [...sources].sort((left, right) => left.format === right.format ? 0 : left.format === "markdown" ? -1 : 1)[0];
244
+ const shadowed = sources.filter((source) => source !== winner);
245
+ const displayName = adapterId === "akm" ? `workflows/${canonicalName}` : canonicalName;
246
+ warnOnce(`workflow-source-collision:${displayName}`, `Workflow "${displayName}" has both a .md and .yml source (${shadowed
247
+ .map((source) => source.relativePath)
248
+ .join(", ")} shadowed by ${winner?.relativePath}); using the .md source. Remove the shadowed sibling to ` +
249
+ "silence this warning.");
250
+ return winner;
271
251
  }
272
252
  function inspectWorkflowSourceCandidate(candidate, canonicalName, realRoot) {
273
253
  const issues = [];
274
- const nestedSuffix = WORKFLOW_EXTENSIONS.find((suffix) => candidate.extensionlessStem.toLowerCase().endsWith(suffix));
275
- if (nestedSuffix)
276
- issues.push(new WorkflowSourceNameError(candidate.relativePath, nestedSuffix));
277
254
  let authoredStat;
278
255
  try {
279
256
  authoredStat = fs.lstatSync(candidate.path);
@@ -327,11 +304,8 @@ function inspectWorkflowSourceCandidate(candidate, canonicalName, realRoot) {
327
304
  /** Return the sole owner, throw on a collision, or return undefined when absent. */
328
305
  export function resolveUniqueWorkflowSource(sourceRoot, adapterId, name) {
329
306
  const sources = listWorkflowSourceFiles(sourceRoot, adapterId, name);
330
- if (sources.length > 1) {
331
- const canonicalName = sources[0]?.canonicalName ?? canonicalizeWorkflowName(normalizeName(name));
332
- throw new WorkflowSourceCollisionError(adapterId === "akm" ? `workflows/${canonicalName}` : canonicalName, sources.map((source) => source.relativePath));
333
- }
334
- return sources[0];
307
+ const canonicalName = sources[0]?.canonicalName ?? canonicalizeWorkflowName(normalizeName(name));
308
+ return pickWorkflowSource(adapterId, canonicalName, sources);
335
309
  }
336
310
  /** Compare an indexed path with the single authoritative on-disk source. */
337
311
  export function assertIndexedWorkflowSourceIdentity(ref, indexedPath, authoritative) {
@@ -2,7 +2,7 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import { parseBuiltinCommandAction } from "../../commands/command/builtin-action.js";
5
- import { applyPortableCommandArguments } from "../../commands/command/portable-template.js";
5
+ import { PORTABLE_ARGUMENTS_PLACEHOLDER } from "../../commands/command/portable-template.js";
6
6
  /** Lower one adapter-neutral source-IR step into the shared execution unit. */
7
7
  export function sourceStepProgramUnit(source) {
8
8
  const unit = {
@@ -32,7 +32,7 @@ export function sourceStepInstructions(source) {
32
32
  }
33
33
  if (source.commandMode === "literal")
34
34
  return action.content;
35
- return applyPortableCommandArguments(action.content, action.arguments, "inline workflow command").content;
35
+ return action.content.split(PORTABLE_ARGUMENTS_PLACEHOLDER).join(action.arguments ?? "");
36
36
  }
37
37
  if (source.uses !== undefined)
38
38
  return `Invoke local target ${source.uses}.`;
@@ -5,10 +5,8 @@
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
7
7
  import { parseBuiltinCommandAction } from "../../commands/command/builtin-action.js";
8
- import { validatePortableCommandTemplate } from "../../commands/command/portable-template.js";
9
8
  import { parseSchedule } from "../../tasks/schedule.js";
10
9
  import { classifyWorkflowSourceUses } from "./uses.js";
11
- const TOKEN_SAFE_RUN = /^[A-Za-z0-9_./:@+=,-]+(?: [A-Za-z0-9_./:@+=,-]+)*$/;
12
10
  export class WorkflowSourceSemanticError extends Error {
13
11
  code;
14
12
  constructor(code, message) {
@@ -34,17 +32,10 @@ export function canonicalizeWorkflowRun(value) {
34
32
  if (value.includes("${{")) {
35
33
  throw new WorkflowSourceSemanticError("unsupported-github-expression", "GitHub expressions and contexts are not supported.");
36
34
  }
37
- if (value.includes("\n") || value.includes("\r")) {
38
- throw new WorkflowSourceSemanticError("unsafe-run-syntax", "Local run accepts only whitespace-separated safe tokens; shell expansion and operators are unsupported.");
35
+ if (value.includes("\0")) {
36
+ throw new WorkflowSourceSemanticError("invalid-exec-argv", "Local run may not contain NUL bytes.");
39
37
  }
40
- const canonical = value
41
- .trim()
42
- .split(/[ \t]+/)
43
- .join(" ");
44
- if (!TOKEN_SAFE_RUN.test(canonical)) {
45
- throw new WorkflowSourceSemanticError("unsafe-run-syntax", "Local run accepts only whitespace-separated safe tokens; shell expansion and operators are unsupported.");
46
- }
47
- return canonical;
38
+ return value;
48
39
  }
49
40
  export function canonicalizeWorkflowWorkingDirectory(value, workspaceRoot) {
50
41
  if (hasControlCharacter(value)) {
@@ -125,17 +116,8 @@ export function validateWorkflowBuiltinCommand(value, mode) {
125
116
  if (effectiveMode === "stored-ref") {
126
117
  throw new WorkflowSourceSemanticError("builtin-command-inputs", "Inline akm/command content cannot use commandMode stored-ref.");
127
118
  }
128
- if (effectiveMode === "literal") {
129
- if (action.arguments !== undefined) {
130
- throw new WorkflowSourceSemanticError("builtin-command-inputs", "Literal akm/command content cannot declare arguments because no substitution occurs.");
131
- }
132
- return action;
133
- }
134
- try {
135
- validatePortableCommandTemplate(action.content, "inline workflow command");
136
- }
137
- catch (cause) {
138
- throw new WorkflowSourceSemanticError("builtin-command-inputs", cause instanceof Error ? cause.message : "Invalid portable command template.");
119
+ if (effectiveMode === "literal" && action.arguments !== undefined) {
120
+ throw new WorkflowSourceSemanticError("builtin-command-inputs", "Literal akm/command content cannot declare arguments because no substitution occurs.");
139
121
  }
140
122
  return action;
141
123
  }
@@ -303,6 +303,26 @@ one akm version reading a given `state.db` at a time; do not alternate
303
303
  versions against the same state directory, and do not downgrade below
304
304
  0.9.2 once a 0.9.2-or-later akm has recorded task history there.
305
305
 
306
+ ## Harness id rename: `claude-code` -> `claude`
307
+
308
+ 0.9.2 also renamed the Claude Code harness id from `claude-code` to
309
+ `claude` — the id used for both agent dispatch and the per-session
310
+ extraction ledger (`state.db`'s `extract_sessions_seen.harness` and
311
+ `workflow_runs.agent_harness`). The 0.9.2 release did not carry a state
312
+ migration for this rename, so any row a pre-0.9.2 akm wrote stayed
313
+ keyed under `claude-code`, invisible to anything querying by the new
314
+ name — a script or dashboard filtering `extract_sessions_seen` or
315
+ `workflow_runs` on `harness = 'claude-code'` (or
316
+ `agent_harness = 'claude-code'`) sees those rows disappear from that
317
+ query, not deleted, once you're on a release carrying the 0.9.12 fix.
318
+ 0.9.12 adds state migration
319
+ `027-extract-sessions-seen-harness-rename`, which runs automatically
320
+ on the next managed `state.db` open (no separate command needed) and
321
+ renames every such row to `claude` in place — conflict-tolerant
322
+ against a session already recorded under `claude`, which is kept as
323
+ the authoritative row. After upgrading to 0.9.12 or later, re-point
324
+ any external query at `harness = 'claude'` / `agent_harness = 'claude'`.
325
+
306
326
  ## Workflow cutover
307
327
 
308
328
  Markdown `.md` and GitHub-shaped `.yml` are peer workflow source formats in
@@ -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`
@@ -113,6 +131,14 @@ The `hint` field is present only when actionable remediation is available
113
131
  `ok === false` on the parsed stderr envelope or a non-zero exit code to
114
132
  detect failure. Scripts can rely on the exit code alone.
115
133
 
134
+ Every success envelope produced by the passthrough stamp — `config`, `clone`,
135
+ `models`, `task-*`, `workflow-*`, `registry-*`, and the rest of that shared
136
+ handler — also carries `ok: true` (0.9.12+), so a caller branching on `.ok`
137
+ sees the same field on both sides — success and failure — instead of
138
+ `undefined` on success. A command that already computes its own `ok` from a
139
+ graded outcome (e.g. `task run`'s exit-code mapping, `akm lint`, `akm proposal
140
+ extract`) keeps that value, `false` included.
141
+
116
142
  `env run`, `secret run`, and `migrate` preserve the spawned process's exact
117
143
  status and raw streams instead of replacing them with an akm failure envelope.
118
144
  `task run` maps completed, active, and disabled status to 0; blocked and failed
@@ -272,17 +298,21 @@ akm health --report --window-compare 7d --format html
272
298
  | `--window-compare` | Compare the current window against the prior window of the same duration (e.g. `24h`, `7d`). With `--report`, overrides the default trend window. |
273
299
  | `--group-by` | Group rows by `run` (one row per `improve_runs` entry). Omit for the default summary. |
274
300
  | `--windows` | Explicit comparison window(s) as `name=...,since=ISO,until=ISO` (repeatable, up to 4). Mutually exclusive with `--window-compare`. |
301
+ | `--no-probe` | Skip the `default-llm-engine` / `configured-engines` reachability probes (for an offline or air-gapped host). |
275
302
 
276
303
  The command reads `state.db`, verifies that the required tables exist, performs a
277
304
  write-read probe against the events stream, inspects `task_history`, checks the
278
- default agent engine, and summarizes recent `improve_*` events.
305
+ default agent engine, and summarizes recent `improve_*` events. Unless
306
+ `--no-probe` is given, it also sends a bounded (3s timeout) reachability probe
307
+ to the `default-llm-engine` and every `configured-engines` LLM connection (and
308
+ an SDK engine's LLM fallback), one probe per distinct endpoint.
279
309
 
280
310
  Primary result fields:
281
311
 
282
312
  | Field | Description |
283
313
  | --- | --- |
284
314
  | `status` | Overall health verdict: `pass`, `warn`, or `fail` |
285
- | `hardChecks` | Deterministic checks such as `state-db-schema`, `state-db-round-trip`, `state-db-migrations`, `task-log-backing`, `active-runs`, `default-engine`, and `model-map-files` |
315
+ | `hardChecks` | Deterministic checks such as `state-db-schema`, `state-db-round-trip`, `state-db-migrations`, `task-log-backing`, `active-runs`, `default-engine`, `model-map-files`, `default-llm-engine`, `configured-engines`, and `active-improve-strategy` |
286
316
  | `advisories` | Non-fatal warnings including `semantic-search-runtime` and `session-extraction` (akmExtract pipeline health) |
287
317
  | `metrics` | Aggregate task/runtime metrics: `taskFailRate`, `agentFailureRate`, `stuckActiveRuns`, `logBackingRate`, `probeRoundTripMs` |
288
318
  | `improve` | Recent improve-loop counts derived from `improve_invoked`, `improve_skipped`, and `improve_completed` events |
@@ -300,9 +330,21 @@ holds a pending historical-destructive migration and something other than
300
330
  `akm upgrade` / `akm migrate apply` opens it directly. Read this check's
301
331
  `status` instead of grepping akm's error text for that case.
302
332
 
303
- The `session-extraction` advisory reflects the health of the `akmExtract` pipeline
304
- (Phase 0.4 of `akm improve`). It warns on harness errors or when no proposals are
305
- generated across five or more scanned sessions.
333
+ `default-llm-engine` and `configured-engines` probe reachability (not just
334
+ configuration) for a `kind: "llm"` engine an unreachable endpoint is a hard
335
+ `fail` for `default-llm-engine` and a `warn` for any other engine. `--no-probe`
336
+ skips this. `active-improve-strategy` names the resolved engine per process
337
+ in its evidence and message, so a strategy-level `engine` pin that shadows
338
+ `defaults.llmEngine` is visible without config archaeology.
339
+
340
+ The `session-extraction` advisory is derived from the `extract_sessions_seen`
341
+ ledger for the last 7 days — not `improve_runs`, which the hook-driven `akm
342
+ proposal extract --session-id ...` invocation never writes. It reports
343
+ `unknown` when nothing was recorded in the window (cannot tell "off on
344
+ purpose" from "broken"), `warn` when every session in the window was skipped
345
+ for an infrastructure reason (`llm_unavailable`, `read_failed`, `exception`,
346
+ `locked_concurrent`) — naming the reason and, when recorded, the engine — and
347
+ `pass` otherwise, with per-outcome counts.
306
348
 
307
349
  The indexed entity graph (entities/relations extracted from bundle assets) has
308
350
  no dedicated inspection command; its summary counts surface as an info-level
@@ -568,8 +610,10 @@ akm workflow create ship-release
568
610
  akm workflow create ship-release --from ./ship-release.md
569
611
  akm workflow run workflows/ship-release --version 1.2.3
570
612
  akm workflow run <run-id> # continue an active partial run
613
+ akm workflow run workflows/ship-release --new # start a fresh run even if one is already active
571
614
  akm workflow status <run-id>
572
615
  akm workflow status workflows/ship-release
616
+ akm workflow status 7c115132 # 8+ char run-id prefix also works
573
617
  akm workflow resume <run-id>
574
618
  akm workflow abandon <run-id>
575
619
  akm workflow list --active
@@ -585,13 +629,21 @@ Subcommands:
585
629
  | Subcommand | Description |
586
630
  | --- | --- |
587
631
  | `create <name>` | Validate and write a Markdown workflow under `workflows/`. `--path <dir>` places it in a subdirectory; `--from <file>` imports content; `--force` (requires `--from` or `--reset`) overwrites; `--print` prints the template that would be written instead of writing it |
588
- | `run <run-id\|ref>` | Stable canonical start/resume/execute command. A ref starts a run or continues the active run in the current scope; a run id continues that exact active run. Executes until completion, failure, verification rejection, interruption, or an explicit limit |
632
+ | `run <run-id\|ref>` | Stable canonical start/resume/execute command. A ref starts a run or resumes the active run in the current scope (announced as `resumed: true`, see below); a run id continues that exact active run. `--new` starts a fresh run even when one is already active. Executes until completion, failure, verification rejection, interruption, or an explicit limit |
589
633
  | `status <run-id\|ref>` | Show the full run state, including all step statuses. `--units` also lists per-unit rows from the run journal (diagnostics only). Renders a `children:` tree when the run composes child workflows |
590
634
  | `list` | List workflow runs (optionally filtered by `--ref`; `--active` shows only `status=active` runs, excluding `blocked`/`failed`/`completed`). Child workflow runs are excluded unless `--children` is passed |
591
635
  | `resume <run-id>` | Flip a `blocked` or `failed` run back to `active`. Completed runs cannot be resumed |
592
636
  | `abandon <run-id>` | Mark a run failed so it stops counting as active (`resume` can reopen it) |
593
637
  | `plan <ref>` | **Evolving.** Compile and freeze a workflow WITHOUT publishing a run: the canonical step graph, per-step frozen target kinds, task/child expansion, input bindings, source read set, and lowering notices — zero durable writes. Returns the full JSON envelope by default, like every other command; pass `--format text` for a human-readable summary |
594
638
 
639
+ Everywhere a run id is accepted (`run`, `status`, `resume`, `abandon`), a
640
+ unique run-id **prefix** of 8 or more characters works too — the same
641
+ convention `akm proposal accept`/`reject` use for proposal UUIDs. A prefix
642
+ matching more than one run is a usage error listing every candidate; a
643
+ prefix matching none is a not-found error. Only strings shaped like a run id
644
+ (hex digits and hyphens, 8+ characters) are ever treated as a prefix, so a
645
+ workflow ref is never mistaken for one.
646
+
595
647
  The public `workflow start`, `next`, and `complete` lifecycle was removed in
596
648
  0.9, along with the experimental `brief`/`report` external-driver protocol.
597
649
  Use `workflow run` for execution and `workflow status` for inspection. The
@@ -633,6 +685,14 @@ The old `--params <json>` bag is removed.
633
685
  | `--max-steps <n>` | Stop once this many steps have finished, leaving a partial run active. Must be at least 1. |
634
686
  | `--max-retries <n>` | When a step fails, reopen the same run and retry the failed step up to this many additional times. Range: 0 through 100; default 0. Gate rejection and interruption are not retried. |
635
687
  | `--timeout <duration>` | Abort the whole invocation after `N`, `Nms`, `Ns`, or `Nm`; bare `N` is milliseconds. The active step remains resumable. |
688
+ | `--new` | Start a fresh run even when one is already active for this ref, instead of resuming it. The existing active run is left untouched — it is never abandoned automatically. A workflow ref only: passing a run id with `--new` is a usage error (exit 2). Parameter flags are allowed together with `--new`, since it is starting a new run. |
689
+
690
+ **Resuming an active run is announced, not silent.** Passing a ref that
691
+ already has an active run in the current scope resumes that run rather than
692
+ starting a new one (unchanged since #485) — but the envelope now carries
693
+ `resumed: true` alongside the resumed run's `run.id`, and the default text
694
+ output leads with `resuming existing run <id> for <ref>; pass --new to start
695
+ a fresh run`. Pass `--new` to start a second, independent run instead.
636
696
 
637
697
  The result includes the current `run`, an `executed` step report list, a
638
698
  `stepsProcessed` count of the steps that finished, and optional `done`,
@@ -668,7 +728,7 @@ repos or directories. akm resolves that context from the nearest `.akm/config.js
668
728
  ancestor when present, otherwise the nearest git root, otherwise the bundle root
669
729
  when the cwd is inside it, otherwise the cwd itself. In practice this means:
670
730
 
671
- - `workflow run workflows/<name>` continues the active run for the current project/worktree/directory, or starts one when none is active.
731
+ - `workflow run workflows/<name>` resumes the active run for the current project/worktree/directory (announced with `resumed: true`), or starts one when none is active. `--new` always starts a fresh run.
672
732
  - `workflow status workflows/<name>` resolves the most-recently-updated run in the current scope only.
673
733
  - `workflow list` shows runs for the current scope only.
674
734
  - Direct run-id commands like `workflow status <run-id>` still work even if the run was started from another directory.
@@ -714,11 +774,12 @@ akm workflow status workflows/ship-release
714
774
  akm workflow status <run-id> --units # also list per-unit rows from the run journal
715
775
  ```
716
776
 
717
- Accepts either a run-id or a workflow ref. When given a workflow ref, resolves
718
- to the most-recently-updated run for that ref in the current working scope.
719
- `--units` adds per-unit rows (unit id, status, failure reason, and any
720
- result/error diagnostic text) from the run journal — diagnostics only; step
721
- evidence stays deterministic and is unaffected.
777
+ Accepts a run id, a unique 8+ character run-id prefix, or a workflow ref.
778
+ When given a workflow ref, resolves to the most-recently-updated run for that
779
+ ref in the current working scope. `--units` adds per-unit rows (unit id,
780
+ status, failure reason, and any result/error diagnostic text) from the run
781
+ journal — diagnostics only; step evidence stays deterministic and is
782
+ unaffected.
722
783
 
723
784
  #### workflow plan
724
785
 
@@ -1517,13 +1578,14 @@ Subcommands:
1517
1578
  | --- | --- |
1518
1579
  | `get <key>` | Read one config key |
1519
1580
  | `list` | List current configuration |
1520
- | `set <key> <value>` | Set one config key |
1521
- | `unset <key>` | Unset an optional key, or a whole `embedding`/engine section |
1581
+ | `set <key> <value>` | Set one config key; prints the resulting config with `ok: true` |
1582
+ | `unset <key>` | Unset an optional key, or a whole `embedding`/engine section; prints the resulting config with `ok: true` |
1522
1583
  | `path` | Show paths to config, bundle, cache, and index. `--all` prints every path; without it, just the config path. Load-bearing: `config path` is the one subcommand the CLI still allows to run when the on-disk config itself fails to load, so you always have a way to locate a broken config. |
1523
1584
 
1524
- `set` and `unset` accept `--silent` to suppress the post-write config dump on
1525
- stdout (the write still happens and errors still print) use it from hooks
1526
- and CI scripts.
1585
+ `set` and `unset` accept `--silent` to suppress the post-write config dump
1586
+ entirely nothing is printed on stdout, and the exit code is the status (the
1587
+ write still happens and errors still print) — use it from hooks and CI
1588
+ scripts.
1527
1589
 
1528
1590
  > **Removed in 0.9.0:** `akm config enable`/`akm config disable`. Use
1529
1591
  > `akm registry add|remove` to toggle a registry, the general mechanism.
@@ -2253,6 +2315,19 @@ schedule) is the answer.
2253
2315
  Requires an LLM engine: pass `--engine`, select a `--strategy` whose
2254
2316
  `processes.extract.engine` is set, or configure `defaults.llmEngine`.
2255
2317
 
2318
+ **Output.** `ok` means the command ran to completion — it is `true` even when
2319
+ every session was skipped (an unreachable LLM engine included); it does not
2320
+ mean anything was harvested. Consumers that need "did this run actually
2321
+ harvest" branch on `skipReasons`, `warnings`, or `sessionsProcessed` /
2322
+ `sessionsSkipped` instead. The envelope also reports:
2323
+
2324
+ | Field | Description |
2325
+ | --- | --- |
2326
+ | `engine` | Resolved LLM engine name for this run. Absent only when extract is disabled by the selected improve strategy (the run returns before an engine is resolved). |
2327
+ | `engineKind` | `"llm"`, `"sdk"`, or `"agent"` — the kind of runner `engine` resolved to. Same absence condition as `engine`. |
2328
+ | `skipReasons` | Per-`skipReason` count across `sessions[]` (e.g. `{ "llm_unavailable": 25 }`). Present only when `sessionsSkipped > 0`. |
2329
+ | `warnings` | Includes one aggregate line per infrastructure skip reason that fired (`llm_unavailable`, `read_failed`, `exception`, `locked_concurrent`) — e.g. `25 of 25 sessions skipped: llm_unavailable (engine "default")` — so an engine outage is visible without inspecting `sessions[]`. Session-content skips (`already_extracted`, `too_short`, `triaged_out`) are counted in `skipReasons` but never produce a warning line. |
2330
+
2256
2331
  #### proposal new
2257
2332
 
2258
2333
  Generate a brand-new asset proposal from a description. Output is always a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.9.11",
3
+ "version": "0.9.13",
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": [
@@ -31,8 +31,7 @@
31
31
  "minLength": 1
32
32
  },
33
33
  "apiKey": {
34
- "type": "string",
35
- "pattern": "^\\$[A-Za-z_][A-Za-z0-9_]*$|^\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}$"
34
+ "type": "string"
36
35
  },
37
36
  "apiKeyFile": {
38
37
  "type": "string",
@@ -197,8 +196,7 @@
197
196
  "type": "string"
198
197
  },
199
198
  "apiKey": {
200
- "type": "string",
201
- "pattern": "^\\$[A-Za-z_][A-Za-z0-9_]*$|^\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}$"
199
+ "type": "string"
202
200
  },
203
201
  "dimension": {
204
202
  "type": "integer",
@@ -1720,8 +1718,7 @@
1720
1718
  "minLength": 1
1721
1719
  },
1722
1720
  "apiKey": {
1723
- "type": "string",
1724
- "pattern": "^\\$[A-Za-z_][A-Za-z0-9_]*$|^\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}$"
1721
+ "type": "string"
1725
1722
  },
1726
1723
  "apiKeyFile": {
1727
1724
  "type": "string",
@@ -1886,8 +1883,7 @@
1886
1883
  "type": "string"
1887
1884
  },
1888
1885
  "apiKey": {
1889
- "type": "string",
1890
- "pattern": "^\\$[A-Za-z_][A-Za-z0-9_]*$|^\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}$"
1886
+ "type": "string"
1891
1887
  },
1892
1888
  "dimension": {
1893
1889
  "type": "integer",
@@ -3456,8 +3452,7 @@
3456
3452
  "type": "string"
3457
3453
  },
3458
3454
  "apiKey": {
3459
- "type": "string",
3460
- "pattern": "^\\$[A-Za-z_][A-Za-z0-9_]*$|^\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}$"
3455
+ "type": "string"
3461
3456
  },
3462
3457
  "dimension": {
3463
3458
  "type": "integer",