akm-cli 0.9.17-alpha.6 → 0.9.17-alpha.7

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 (38) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/STABILITY.md +2 -2
  3. package/dist/akm +7 -7
  4. package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
  5. package/dist/commands/improve/consolidate.js +11 -0
  6. package/dist/commands/improve/improve-cli.js +27 -7
  7. package/dist/commands/improve/ledger.js +7 -3
  8. package/dist/commands/improve/preparation.js +40 -10
  9. package/dist/commands/improve/reflect.js +46 -22
  10. package/dist/commands/improve/retrieval-gate.js +127 -0
  11. package/dist/commands/improve/retrieval-scope.js +77 -0
  12. package/dist/commands/read/curate.js +1 -17
  13. package/dist/commands/tasks/tasks-cli.js +10 -12
  14. package/dist/commands/tasks/tasks.js +57 -56
  15. package/dist/commands/tasks/validate.js +27 -46
  16. package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
  17. package/dist/core/improve-result.js +4 -1
  18. package/dist/core/non-task-input.js +20 -0
  19. package/dist/core/paths.js +0 -4
  20. package/dist/indexer/indexer.js +1 -3
  21. package/dist/indexer/usage/usage-events.js +34 -0
  22. package/dist/scripts/akm-migrate-node.js +5822 -5833
  23. package/dist/scripts/akm-migrate.js +6301 -6312
  24. package/dist/storage/repositories/proposals-repository.js +4 -0
  25. package/dist/tasks/backends/cron.js +80 -43
  26. package/dist/tasks/backends/launchd.js +28 -15
  27. package/dist/tasks/backends/schtasks.js +25 -10
  28. package/dist/tasks/run/load-task.js +1 -1
  29. package/dist/tasks/scheduler-binding.js +4 -2
  30. package/dist/tasks/scheduler-invocation.js +127 -235
  31. package/dist/tasks/scheduler-sync.js +13 -8
  32. package/dist/tasks/source/parse-task-source.js +22 -126
  33. package/dist/tasks/source/task-to-v3.js +1 -55
  34. package/dist/tasks/source/task-to-v4.js +1 -13
  35. package/docs/migration/v0.9.1-to-v0.9.2.md +7 -3
  36. package/docs/reference/cli.md +4 -3
  37. package/docs/reference/tasks.md +58 -40
  38. package/package.json +1 -1
@@ -5,59 +5,32 @@
5
5
  * The task source version router.
6
6
  *
7
7
  * Runs the bounded YAML front end ONCE (`readBoundedTaskSourceYaml`), reads
8
- * `root.version`, and either dispatches into `parseTaskSourceV4Document` or
9
- * routes through the in-memory read shim below — no second parse of the v4
10
- * grammar itself, no re-serialization back to disk, no synthetic document
11
- * (the shim adds a pure bytes-in/bytes-out detour, never a disk write).
12
- *
13
- * The terminal routing table:
8
+ * `root.version`, and dispatches into `parseTaskSourceV4Document`. The
9
+ * runtime reads only task source v4 (#987):
14
10
  *
15
11
  * | root `version` | outcome |
16
12
  * |------------------------|-----------------------------------------------------------------|
17
- * | `4`, no retired `schedule[].enabled` | `parseTaskSourceV4Document` — the current grammar |
18
- * | `4`, some `schedule[]` entry carries `enabled` | in-memory read shim (below): the SAME `planTaskToV4File` call `akm migrate apply` uses for this exact case (`./task-to-v4.ts`'s `version === 4` branch, which strips every `schedule[].enabled` and reports `source-enablement-removed`) runs on the bytes already in hand; the result is parsed and returned with a one-line stderr deprecation warning (once per file per process). The value is never read either way — `enabled: false` cannot suppress a granted task and `enabled: true` cannot schedule an ungranted one, since activation is host-local `scheduler.enabled`. Version 4 is supported, so if the planner cannot produce a valid document it is an ordinary grammar defect: falls back to `TASK_SOURCE_INVALID` with the v4 parser's own error, not the unmigratable-version decision |
19
- * | `2` or `3` | in-memory read shim (below): the SAME pure planners `akm migrate apply` uses (`./task-to-v3.ts`, `./task-to-v4.ts`) convert the bytes already in hand to v4 in memory; the result is parsed and returned with a one-line stderr deprecation warning (once per file per process). If the deterministic conversion itself fails (an unmigratable shape — the file needs a human decision, not a re-run), falls back to `TASK_SCHEMA_VERSION_UNSUPPORTED` naming the specific blocked reason — the shim removes friction for the deterministic case, it never hides a real problem |
13
+ * | `4` | `parseTaskSourceV4Document` — the current grammar |
14
+ * | `4`, some `schedule[]` entry carries `enabled` (0.9.15's grammar) | `TASK_SOURCE_INVALID` naming `akm migrate apply`, which removes the key |
15
+ * | `2` or `3` | `TASK_SCHEMA_VERSION_UNSUPPORTED` naming `akm migrate apply`, which converts the file |
20
16
  * | any other number | `TASK_SCHEMA_VERSION_UNSUPPORTED`, naming the migrator |
21
17
  * | absent / not a number | `parseTaskSourceV4Document` — its own `TASK_SOURCE_INVALID` "version is required and must be 4" / "must be exactly 4" wording |
22
18
  *
19
+ * `akm migrate apply` (`scripts/akm-migrate/`) is the one place a v2/v3
20
+ * document, or a v4 document carrying the retired `schedule[].enabled`, is
21
+ * converted: it rewrites the file once, under a backup. Nothing here converts
22
+ * in memory. Every caller reports the refusal for that one file — `akm task
23
+ * sync` keeps reconciling every other task (#867).
24
+ *
23
25
  * A missing or non-numeric `version:` is a MALFORMED v4 document, not a
24
26
  * legacy one — it routes into the v4 parser so the field error names the
25
- * one grammar `src` still accepts, rather than a generic "unsupported"
26
- * message that would send the user to the migrator for a document that was
27
- * never task v2 or v3 in the first place.
28
- *
29
- * task v2 and task v3 sources are no longer read as their own standing
30
- * grammar anywhere else in `src` — the only readers of that grammar are the
31
- * pure, byte-producing planners (`./task-to-v3.ts`, `./task-to-v4.ts`, and
32
- * the frozen v3 reader `./task-source-v3-frozen.ts`), reached either through
33
- * this shim (bytes in, bytes out, never touches disk) or through
34
- * `akm migrate apply` / `akm-migrate` (`scripts/akm-migrate`, which
35
- * additionally rewrites the file on disk once the user asks for that).
36
- * Policy: a deterministic byte transform is the tool's job, not the user's —
37
- * upgrading past a schema bump must not silently break a scheduled task, so
38
- * v2/v3 files keep reading successfully at the cost of a one-line
39
- * deprecation warning, and `akm migrate apply` remains available to rewrite
40
- * the file and silence it. Activation itself is host-local (scheduler
41
- * config, `src/core/activation-policy.ts`) and this shim never touches it:
42
- * the v3->v4 planner never hoists the retired `akm.enabled` field to v4's
43
- * top level and never carries a schedule entry's `enabled` key, so the
44
- * document this shim hands back carries no enablement at all — the same
45
- * shape a native v4 document has. A declared `version: 4` document that
46
- * still carries a `schedule[].enabled` key (0.9.15's v4 grammar accepted
47
- * it; this release's does not) gets the identical treatment: routed
48
- * through `planTaskToV4File`'s own `version === 4` branch, which strips
49
- * every `schedule[].enabled` key without reading its value and reports
50
- * `source-enablement-removed`, then re-parsed and returned with the same
51
- * one-line deprecation warning. The front end's own pre-version failures
27
+ * one grammar `src` accepts. The front end's own pre-version failures
52
28
  * (source not a string, source too large, YAML parse/warning/expansion)
53
29
  * render with the label `task source`.
54
30
  */
55
31
  import { UsageError } from "../../core/errors.js";
56
- import { warnOnce } from "../../core/warn.js";
57
32
  import { readBoundedTaskSourceYaml } from "./bounded-document.js";
58
- import { parseTaskSourceV4, parseTaskSourceV4Document, TASK_SOURCE_V4_VERSION, } from "./task-source-v4.js";
59
- import { planTaskToV3File } from "./task-to-v3.js";
60
- import { planTaskToV4File } from "./task-to-v4.js";
33
+ import { parseTaskSourceV4Document, TASK_SOURCE_V4_VERSION } from "./task-source-v4.js";
61
34
  /** Read the root `version` field without over-accepting non-number values (e.g. the string `"4"`). */
62
35
  export function peekTaskSourceVersion(root) {
63
36
  if (root === null || typeof root !== "object" || Array.isArray(root))
@@ -66,71 +39,19 @@ export function peekTaskSourceVersion(root) {
66
39
  return typeof value === "number" ? value : undefined;
67
40
  }
68
41
  const TASK_MIGRATE_HINT = "Run `akm migrate apply --dry-run` to preview the task-v3 to task-source-v4 conversion, then run `akm migrate apply`.";
69
- /**
70
- * Thrown only when the deterministic conversion itself could not produce a
71
- * task source v4 document — a case where a person must decide the intended
72
- * behavior (e.g. an ambiguous shell command), not one the migrator can just
73
- * be re-run to fix. `reason`/`detail` are the SAME blocked outcome
74
- * `akm migrate status`/`apply` reports for this file, so the message names
75
- * the actual decision instead of pointing at a command that will report the
76
- * identical block.
77
- */
78
- function unmigratableVersionError(filePath, version, reason, detail) {
79
- return new UsageError(`TASK_SCHEMA_VERSION_UNSUPPORTED: Task at ${filePath} uses task schema version ${version} and needs a human decision before it can run — the deterministic migrator cannot convert it automatically (${reason}${detail ? `: ${detail}` : ""}).`, "TASK_SCHEMA_VERSION_UNSUPPORTED", "Review the file and resolve the ambiguity by hand, then it will convert normally; `akm migrate status` reports the same reason.");
42
+ /** A v2/v3 document: `akm migrate apply` converts it; this release reads only v4. */
43
+ function legacyVersionError(filePath, version) {
44
+ return new UsageError(`TASK_SCHEMA_VERSION_UNSUPPORTED: Task at ${filePath} uses task schema version ${version}; this release reads only version 4. Run \`akm migrate apply\` to convert it.`, "TASK_SCHEMA_VERSION_UNSUPPORTED", TASK_MIGRATE_HINT);
80
45
  }
81
46
  function unsupportedVersionError(filePath, version) {
82
47
  return new UsageError(`TASK_SCHEMA_VERSION_UNSUPPORTED: Task at ${filePath} uses task schema version ${version}, which this release does not accept.`, "TASK_SCHEMA_VERSION_UNSUPPORTED", TASK_MIGRATE_HINT);
83
48
  }
84
- /**
85
- * Plan the SAME bytes already in hand through the pure v3->v4 (and, for v2,
86
- * chained v2->v3->v4) migration planner(s) — never touches disk, never
87
- * writes the file, never re-reads it from disk. `version === 4` runs the
88
- * identical bytes straight through `planTaskToV4File`'s own `version === 4`
89
- * branch instead (the retired `schedule[].enabled` case, `./task-to-v4.ts`),
90
- * so there is exactly one helper for every version this shim reads, not a
91
- * second one for the v4-only case. Returns the produced v4 YAML text, or the
92
- * blocked reason/detail when the deterministic conversion cannot proceed
93
- * (an unmigratable v2/v3 shape, or a v4 document `planTaskToV4File` cannot
94
- * revalidate once `schedule[].enabled` is stripped) — the caller falls back
95
- * to the same hard error this gate threw before the shim existed, now
96
- * naming that reason.
97
- */
98
- function planInMemoryV4Bytes(version, yaml, filePath, workspaceRoot) {
99
- const bytes = Buffer.from(yaml, "utf8");
100
- // `writable`/`onDiskWritable` gate the DISK apply path's "don't touch a
101
- // read-only file" check inside the planners; this shim never writes
102
- // anything to disk, so that check does not apply here and must not block
103
- // an otherwise-legal read of a task file that happens to be read-only.
104
- const baseInput = {
105
- filePath,
106
- bytes,
107
- mode: 0o644,
108
- writable: true,
109
- onDiskWritable: true,
110
- ...(workspaceRoot ? { containmentRoot: workspaceRoot } : {}),
111
- };
112
- let v3Bytes;
113
- if (version === 3 || version === 4) {
114
- v3Bytes = bytes;
115
- }
116
- else {
117
- const v3Outcome = planTaskToV3File(baseInput);
118
- if (v3Outcome.status !== "changed")
119
- return { reason: v3Outcome.reason, detail: v3Outcome.detail };
120
- v3Bytes = v3Outcome.after;
121
- }
122
- const v4Outcome = planTaskToV4File({ ...baseInput, bytes: v3Bytes });
123
- if (v4Outcome.status !== "changed")
124
- return { reason: v4Outcome.reason, detail: v4Outcome.detail };
125
- return v4Outcome.after.toString("utf8");
126
- }
127
49
  /**
128
50
  * True when `root`'s `schedule:` is a sequence with at least one mapping
129
51
  * entry that carries an `enabled` key, regardless of that key's value or
130
52
  * type — the shape 0.9.15's v4 grammar accepted and this release's does
131
- * not (`schedule[].enabled`). The value is never inspected: activation is
132
- * host-local `scheduler.enabled`, so this is purely a presence check that
133
- * decides whether to route through the in-memory shim.
53
+ * not (`schedule[].enabled`). Activation is host-local `scheduler.enabled`,
54
+ * so the value is never read; `akm migrate apply` removes the key.
134
55
  */
135
56
  export function v4ScheduleHasRetiredEnabledKey(root) {
136
57
  if (root === null || typeof root !== "object" || Array.isArray(root))
@@ -140,42 +61,17 @@ export function v4ScheduleHasRetiredEnabledKey(root) {
140
61
  return false;
141
62
  return schedule.some((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && Object.hasOwn(entry, "enabled"));
142
63
  }
143
- /** Parse task source YAML, routing per the terminal table above. */
64
+ /** Parse task source YAML, routing per the table above. */
144
65
  export function parseTaskSource(input) {
145
66
  const { root, lineAt } = readBoundedTaskSourceYaml(input, { sourceLabel: "task source" });
146
67
  const version = peekTaskSourceVersion(root);
147
68
  if (version !== undefined && version !== TASK_SOURCE_V4_VERSION) {
148
- if (version === 2 || version === 3) {
149
- const shimmed = planInMemoryV4Bytes(version, input.yaml, input.filePath, input.workspaceRoot);
150
- if (typeof shimmed === "string") {
151
- const v4 = parseTaskSourceV4({
152
- yaml: shimmed,
153
- filePath: input.filePath,
154
- ...(input.workspaceRoot ? { workspaceRoot: input.workspaceRoot } : {}),
155
- });
156
- warnOnce(`task-source:v${version}-shim:${input.filePath}`, `akm: task ${input.filePath} uses schema v${version} — auto-read as v4; run \`akm migrate apply\` to rewrite it and silence this`);
157
- return Object.freeze({ version: 4, v4 });
158
- }
159
- throw unmigratableVersionError(input.filePath, version, shimmed.reason, shimmed.detail);
160
- }
69
+ if (version === 2 || version === 3)
70
+ throw legacyVersionError(input.filePath, version);
161
71
  throw unsupportedVersionError(input.filePath, version);
162
72
  }
163
73
  if (version === TASK_SOURCE_V4_VERSION && v4ScheduleHasRetiredEnabledKey(root)) {
164
- const shimmed = planInMemoryV4Bytes(4, input.yaml, input.filePath, input.workspaceRoot);
165
- if (typeof shimmed === "string") {
166
- const v4 = parseTaskSourceV4({
167
- yaml: shimmed,
168
- filePath: input.filePath,
169
- ...(input.workspaceRoot ? { workspaceRoot: input.workspaceRoot } : {}),
170
- });
171
- warnOnce(`task-source:v4-schedule-enabled-shim:${input.filePath}`, `akm: task ${input.filePath} uses the retired schedule[].enabled field — auto-read with it ignored; run \`akm migrate apply\` to rewrite it and silence this`);
172
- return Object.freeze({ version: 4, v4 });
173
- }
174
- // Version 4 is supported; a blocked outcome here means the file itself
175
- // is malformed (`generated-v4-validation-failed`), not that this
176
- // version needs a human decision — throw the v4 grammar error, not
177
- // `unmigratableVersionError`.
178
- throw new UsageError(shimmed.detail ?? shimmed.reason, "TASK_SOURCE_INVALID");
74
+ throw new UsageError(`Invalid task source v4 at ${input.filePath}: schedule[].enabled was removed (scheduler activation is host-local config). Run \`akm migrate apply\` to rewrite it.`, "TASK_SOURCE_INVALID", TASK_MIGRATE_HINT);
179
75
  }
180
76
  const documentOptions = {
181
77
  filePath: input.filePath,
@@ -374,7 +374,7 @@ function isReason(value) {
374
374
  return typeof value === "string";
375
375
  }
376
376
  /** Convert one already-normalized legacy record directly to final task v3. */
377
- export function planLegacyTaskDataToV3(input, data) {
377
+ function planLegacyTaskDataToV3(input, data) {
378
378
  if (data.version !== 2) {
379
379
  return blocked(input, "unsupported-task-version", `expected normalized legacy version 2, got ${String(data.version)}`);
380
380
  }
@@ -451,57 +451,3 @@ export function planTaskToV3File(input) {
451
451
  }
452
452
  return planLegacyTaskDataToV3(input, data);
453
453
  }
454
- function generationFor(files) {
455
- const digest = crypto.createHash("sha256");
456
- digest.update("akm-task-to-v3-plan-v1\0");
457
- for (const file of files) {
458
- digest.update(file.filePath);
459
- digest.update("\0");
460
- digest.update(file.status);
461
- digest.update("\0");
462
- digest.update(file.reason);
463
- digest.update("\0");
464
- digest.update(String(file.mode));
465
- digest.update("\0");
466
- digest.update(file.writable ? "writable" : "read-only");
467
- digest.update("\0");
468
- digest.update(file.onDiskWritable === false ? "disk-read-only" : "disk-writable-or-unspecified");
469
- digest.update("\0");
470
- if (file.containmentRoot)
471
- digest.update(file.containmentRoot);
472
- digest.update("\0");
473
- digest.update(file.beforeHash);
474
- digest.update("\0");
475
- if (file.status === "changed")
476
- digest.update(file.afterHash);
477
- digest.update("\0");
478
- if (file.detail)
479
- digest.update(file.detail);
480
- digest.update("\0");
481
- }
482
- return digest.digest("hex");
483
- }
484
- /** Build/fingerprint a plan from already-derived immutable outcomes. */
485
- export function taskToV3PlanFromOutcomes(outcomes) {
486
- const files = [...outcomes].sort((left, right) => left.filePath < right.filePath ? -1 : left.filePath > right.filePath ? 1 : 0);
487
- for (let index = 1; index < files.length; index += 1) {
488
- const previous = files[index - 1];
489
- const current = files[index];
490
- if (previous && current && path.resolve(previous.filePath) === path.resolve(current.filePath)) {
491
- throw new Error(`duplicate task migration file path: ${current.filePath}`);
492
- }
493
- }
494
- return Object.freeze({ schemaVersion: 1, generation: generationFor(files), files: Object.freeze(files) });
495
- }
496
- /** Plan a complete, stable file set. Input order cannot change the result. */
497
- export function planTaskToV3Migration(inputs) {
498
- const sorted = [...inputs].sort((left, right) => left.filePath < right.filePath ? -1 : left.filePath > right.filePath ? 1 : 0);
499
- let previous;
500
- for (const current of sorted) {
501
- if (previous && path.resolve(previous.filePath) === path.resolve(current.filePath)) {
502
- throw new Error(`duplicate task migration file path: ${current.filePath}`);
503
- }
504
- previous = current;
505
- }
506
- return taskToV3PlanFromOutcomes(sorted.map(planTaskToV3File));
507
- }
@@ -318,7 +318,7 @@ function planV3DataToV4(input, data) {
318
318
  // `uses: workflows/` it was equally inert there — nothing ever consumed
319
319
  // it. Hoisting it unconditionally would therefore emit bytes the real
320
320
  // parseTaskSourceV4 below rejects, blocking a valid, previously-runnable
321
- // v3 file from both the in-memory read shim and `akm migrate apply`
321
+ // v3 file from `akm migrate apply`
322
322
  // (scripts/akm-migrate/migrate/task-files.ts). Dropping an
323
323
  // already-inert field and SAYING SO is the faithful translation, and
324
324
  // keeps spec row B-66 / §5.3's `changed` guarantee intact.
@@ -473,15 +473,3 @@ export function taskToV4PlanFromOutcomes(outcomes) {
473
473
  }
474
474
  return Object.freeze({ schemaVersion: 1, generation: generationFor(files), files: Object.freeze(files) });
475
475
  }
476
- /** Plan a complete, stable file set. Input order cannot change the result. */
477
- export function planTaskToV4Migration(inputs) {
478
- const sorted = [...inputs].sort((left, right) => left.filePath < right.filePath ? -1 : left.filePath > right.filePath ? 1 : 0);
479
- let previous;
480
- for (const current of sorted) {
481
- if (previous && path.resolve(previous.filePath) === path.resolve(current.filePath)) {
482
- throw new Error(`duplicate task migration file path: ${current.filePath}`);
483
- }
484
- previous = current;
485
- }
486
- return taskToV4PlanFromOutcomes(sorted.map(planTaskToV4File));
487
- }
@@ -57,7 +57,9 @@ pace: an unmigrated v2/v3 task source keeps reading and running through the
57
57
  in-memory read shim, with a one-line deprecation warning, until you migrate
58
58
  it (nothing silently breaks or half-runs, and only a genuinely unmigratable
59
59
  shape fails closed with an actionable hint), and pre-`irVersion`-5 runs stay
60
- inspectable indefinitely.
60
+ inspectable indefinitely. (0.9.17-alpha.7 removed that shim: a v2/v3 task
61
+ source now fails on its own, naming `akm migrate apply`, which `akm upgrade`
62
+ runs after an install.)
61
63
 
62
64
  ## Task sources
63
65
 
@@ -72,7 +74,9 @@ apply` remains the way to rewrite the file on disk and silence the warning.
72
74
  current grammar — extended the same kind of shim to a `version: 4` document
73
75
  whose `schedule[]` still carries a per-entry `enabled` key, retired from
74
76
  task source v4's own grammar since; the field is stripped without being
75
- read, never re-added.)
77
+ read, never re-added. 0.9.17-alpha.7 removed both shims: akm now reads only
78
+ task source v4, and each older file fails on its own, naming
79
+ `akm migrate apply`.)
76
80
 
77
81
  **Before — 0.9.1 task v2:**
78
82
 
@@ -679,7 +683,7 @@ phase-specific code:
679
683
  | Code | Domain |
680
684
  |---|---|
681
685
  | `TASK_SOURCE_INVALID` | A task document's field- or semantic-level validation failure, or a malformed/oversized/too-deep YAML front end failure. |
682
- | `TASK_SCHEMA_VERSION_UNSUPPORTED` | A task document's `version:` is `3` or `2` and the in-memory read shim's deterministic conversion cannot resolve it — a human decision is needed (see [Task sources](#task-sources)); a convertible v2/v3 document reads through the shim instead of failing. |
686
+ | `TASK_SCHEMA_VERSION_UNSUPPORTED` | A task document's `version:` is `3` or `2` and the in-memory read shim's deterministic conversion cannot resolve it — a human decision is needed (see [Task sources](#task-sources)); a convertible v2/v3 document reads through the shim instead of failing. From 0.9.17-alpha.7, every v2/v3 document fails this way, naming `akm migrate apply`. |
683
687
  | `TARGET_REF_INVALID` | A value is not a canonical `commands/`, `scripts/`, `tasks/`, or `workflows/` asset ref (malformed shapes, GitHub locators, other asset families). |
684
688
  | `WORKFLOW_SOURCE_INVALID` | A workflow-source compile failure other than the one below. |
685
689
  | `COMPOSITION_INVALID` | A composition-policy rejection: a rejected `with:`, a multi-job document, a composition cycle/depth/size violation. |
@@ -2980,9 +2980,10 @@ crontab whose akm markers are malformed is refused unmodified, and another
2980
2980
  akm process holding the scheduler lock makes sync exit 75 (retry shortly).
2981
2981
 
2982
2982
  `akm task prune` reclaims installed scheduler entries that `sync` can never
2983
- clean up on its own: entries whose own `--scheduler-context` descriptor no
2984
- longer resolves to a live bundle (a corrupt/missing descriptor, or the
2985
- bundle directory it pointed at is gone). It never touches an entry that
2983
+ clean up on its own: entries that no longer resolve to a live bundle (a row
2984
+ whose `AKM_BUNDLE_DIR` names a directory that is gone, or a row written
2985
+ before 0.9.17-alpha.7 whose `--scheduler-context` descriptor cannot be
2986
+ read). It never touches an entry that
2986
2987
  still resolves to a live bundle — that's `sync`'s job. Like `sync
2987
2988
  --dry-run`, the default is a dry-run preview (zero scheduler writes) that
2988
2989
  exits non-zero when there are candidates to remove; `--yes` executes the
@@ -5,27 +5,22 @@ Task assets are strict, local automation sources. They live at
5
5
  launchd, or Windows Task Scheduler with `akm task sync`. The task file is
6
6
  authored source; scheduler entries are derived OS state.
7
7
 
8
- **Task source v4 (`version: 4`) is the current task source grammar.** A
9
- document with `version: 3` or `version: 2` still reads and runs: an
10
- in-memory shim converts it to v4 on the same bytes `akm migrate apply`
11
- would produce, prints a one-line stderr deprecation warning (once per file
12
- per process), and never writes anything to disk. Only a v2/v3 document the
13
- deterministic conversion itself cannot resolve (an ambiguous shell command,
14
- say) fails to load, with `UsageError` code `TASK_SCHEMA_VERSION_UNSUPPORTED`
15
- naming the specific blocked reason and the human decision it needs. A
16
- declared `version: 4` document whose `schedule[]` still carries a
17
- per-entry `enabled` key — 0.9.15's v4 grammar accepted it, this release's
18
- does not — reads through the same kind of in-memory shim: the key is
19
- stripped without ever being read (activation is host-local, below) and the
20
- same one-line deprecation warning is printed. Task source v4 adds typed
21
- `inputs:` and a single bounded `output:` schema (command targets only), and
22
- makes scheduling OPTIONAL rather than mandatory. `akm task add` authors
23
- task source v4 directly.
8
+ **Task source v4 (`version: 4`) is the only task source grammar akm reads.**
9
+ A document with `version: 3` or `version: 2` fails to load with `UsageError`
10
+ code `TASK_SCHEMA_VERSION_UNSUPPORTED`, naming `akm migrate apply`, which
11
+ converts it. A declared `version: 4` document whose `schedule[]` still
12
+ carries a per-entry `enabled` key — 0.9.15's v4 grammar accepted it, this
13
+ release's does not — fails the same way with `TASK_SOURCE_INVALID`
14
+ (activation is host-local, below). Each such file fails on its own:
15
+ `akm task sync` reports it and keeps reconciling every other task. Task
16
+ source v4 adds typed `inputs:` and a single bounded `output:` schema
17
+ (command targets only), and makes scheduling OPTIONAL rather than
18
+ mandatory. `akm task add` authors task source v4 directly.
24
19
 
25
20
  If you have `version: 3` or `version: 2` files on disk (from an earlier
26
21
  akm release), see [Migrating to task source v4](#migrating-to-task-source-v4)
27
22
  below — `akm migrate apply` converts both generations in one pass and
28
- rewrites the file on disk, silencing the read-time deprecation warning. The
23
+ rewrites the file on disk (`akm upgrade` runs it after an install). The
29
24
  retired v3 grammar itself is documented at the bottom of this page
30
25
  ([Task v3 (retired): grammar reference for migration](#task-v3-retired-grammar-reference-for-migration))
31
26
  purely so you can read an old file while migrating it; it is no longer
@@ -38,10 +33,9 @@ indexed, scheduled, or run. Every task should declare `version: 4`; a
38
33
  document with no `version:` key, or a `version:` that is not a number,
39
34
  fails with `TASK_SOURCE_INVALID` (`must be exactly 4.` / `is required and
40
35
  must be exactly 4.`) — a genuinely malformed v4 document, not a legacy one.
41
- `version: 3` and `version: 2` read via the in-memory deprecation shim
42
- described above and, only when the deterministic conversion itself cannot
43
- resolve the document, fail with `TASK_SCHEMA_VERSION_UNSUPPORTED` instead
44
- (see [Migrating to task source v4](#migrating-to-task-source-v4)).
36
+ `version: 3` and `version: 2` fail with `TASK_SCHEMA_VERSION_UNSUPPORTED`
37
+ naming `akm migrate apply` (see
38
+ [Migrating to task source v4](#migrating-to-task-source-v4)).
45
39
  The published [task schema](../../schemas/akm-task.json) describes the
46
40
  hand-authored contract; `src/tasks/source/task-source-v4.ts` is the
47
41
  authoritative bounded parser.
@@ -414,17 +408,14 @@ for full before/after examples and recovery guidance.
414
408
  anything — see [`akm task explain`](#akm-task-explain) above.
415
409
  - `akm task validate <path>` parses one task file by filesystem path (the
416
410
  file need not live in a configured bundle) and reports the same
417
- `valid`/`converts`/`blocked`/`invalid`/`not-a-task` diagnostic
411
+ `valid`/`blocked`/`invalid`/`not-a-task` diagnostic
418
412
  `akm task sync` would produce for it — including sync's own cron-dialect
419
413
  check and its per-schedule-entry input-contract check — without touching
420
414
  the scheduler and without requiring a configured engine, even for a
421
415
  command-kind task. The envelope's own `sourceVersion` field names the
422
- file's declared schema version. A version 2/3 file the in-memory shim
423
- converts reports `converts` (exit 0); one the shim's deterministic
424
- planner cannot resolve reports `blocked` (exit 1) naming the human
425
- decision it needs. A `version: 4` file whose only defect is a retired
426
- `schedule[].enabled` also reports `converts` (`sourceVersion` still `4`)
427
- — it read through the shim too, not the direct v4 path.
416
+ file's declared schema version. A version 2/3 file, and a `version: 4`
417
+ file still carrying a retired `schedule[].enabled`, report `blocked`
418
+ (exit 1) naming `akm migrate apply`, which converts them.
428
419
  - `akm task add` validates a task source v4 document, writes it, adds its ref
429
420
  to local scheduler activation, and syncs its bundle. `--params` renders
430
421
  typed `inputs:` declarations instead of a `with:` bag; `--schedule` is
@@ -443,8 +434,8 @@ for full before/after examples and recovery guidance.
443
434
  A row that fails to install or remove is reported in `failures` and every
444
435
  other row still applies. A source that fails to parse is reported the same
445
436
  way, and its installed row is left exactly as it is. Rows akm cannot attribute to a bundle this sync covers —
446
- another bundle's, another installation's (the row's own descriptor names a
447
- different bundle path), or anything outside akm's `# akm:task` markers,
437
+ another bundle's, another installation's (the row's `AKM_BUNDLE_DIR`
438
+ names a different working stash), or anything outside akm's `# akm:task` markers,
448
439
  `com.akm.task.` labels, or `\akm\` task folder — are never touched. A
449
440
  Task Scheduler row is compared by the fingerprint akm writes into its
450
441
  `<Source>` plus its enabled state, so an edit made in Task Scheduler that
@@ -457,9 +448,10 @@ for full before/after examples and recovery guidance.
457
448
  removals annotated with their owning bundle) without writing to the
458
449
  scheduler; exits non-zero when removals are pending.
459
450
  - `akm task prune` removes installed scheduler entries `sync` cannot reach
460
- because their own descriptor no longer resolves to a live bundle
461
- (corrupt/missing `--scheduler-context`, or the owning bundle directory is
462
- gone). It never touches an entry that still resolves to a live bundle.
451
+ because they no longer resolve to a live bundle: a row whose
452
+ `AKM_BUNDLE_DIR` names a directory that is gone, or a row written before
453
+ 0.9.17-alpha.7 whose `--scheduler-context` descriptor cannot be read. It
454
+ never touches an entry that still resolves to a live bundle.
463
455
  Defaults to a dry-run preview (zero writes); `--yes` executes it; `--id
464
456
  <id1,id2,...>` scopes to specific ids and refuses any id that isn't a
465
457
  current orphan candidate.
@@ -473,13 +465,39 @@ for full before/after examples and recovery guidance.
473
465
  section directly above the first akm task block in the crontab (on macOS,
474
466
  an `EnvironmentVariables` entry in each plist). It is the PATH of the shell
475
467
  that ran the sync, rewritten on every crontab write and removed with the
476
- last akm block; cron applies it to every row below it. The
477
- `--scheduler-context` descriptor a row references holds directories only:
478
- the bundle path, plus any `AKM_CONFIG_DIR`, `AKM_DATA_DIR`, `AKM_CACHE_DIR`
479
- or `AKM_STATE_DIR` that shell had set explicitly. Defaults resolve at fire
480
- time, so a scheduled run uses the same state, data and cache directories an
481
- interactive command does. Run the sync from a shell whose environment you
482
- would want scheduled.
468
+ last akm block; cron applies it to every row below it.
469
+ - A task's row is its command plus its schedule:
470
+ `<launcher> task run <id> --bundle <bundle> --scheduled`, and it sets its
471
+ own environment. Every row sets `AKM_BUNDLE_DIR` to the working stash of
472
+ the shell that ran the sync (its `AKM_BUNDLE_DIR`, or the default bundle),
473
+ so the scheduled run uses the same working stash, `--bundle` finds a stash
474
+ no config names, and sync tells rows of other installations sharing the
475
+ scheduler apart (#846). Rows synced from a shell that set
476
+ `AKM_CONFIG_DIR`, `AKM_DATA_DIR`, `AKM_CACHE_DIR` or `AKM_STATE_DIR`
477
+ explicitly set those too. Each backend does it its own way: a
478
+ `VAR=value` prefix in the crontab, an `EnvironmentVariables` entry in the
479
+ plist, a `$env:VAR='value';` assignment ahead of the command in Task
480
+ Scheduler. Other defaults resolve at fire time, so a scheduled run uses
481
+ the same state, data and cache directories an interactive command does.
482
+ Run the sync from a shell whose environment you would want scheduled.
483
+
484
+ ```text
485
+ 15 2 * * * AKM_BUNDLE_DIR=/home/u/akm /home/u/.bun/bin/bun /home/u/.bun/lib/node_modules/akm-cli/dist/akm task run nightly --bundle work --scheduled > /home/u/.cache/akm/tasks/logs/nightly.log 2>&1
486
+ ```
487
+
488
+ A row whose command is over 1,000 bytes runs a wrapper script under the
489
+ log directory instead (`sh <log dir>/.akm-cron-wrapper-<id>-<hash>.sh`);
490
+ sync reads the script to tell which task the row runs.
491
+
492
+ Releases 0.9.0 through 0.9.17-alpha.6 wrote a `--scheduler-context
493
+ <file>` argument into each row instead, naming a descriptor file under
494
+ `$DATA/tasks/context/` that held the same values. akm still applies that
495
+ file when such a row fires, and the first `akm task sync` after upgrading
496
+ rewrites each row in place: it shows as an update, keeps the row's
497
+ launcher and schedule, and sets the values inline. A row the sync leaves as it is (its task file failed to load, or
498
+ a `--bundle` sync did not cover it) still names its file; once
499
+ `akm task doctor` lists no binding with a `contextPath`, the old descriptor
500
+ files are not read and can be deleted.
483
501
 
484
502
  Scheduler execution is at least once. Backends provide a stable invocation
485
503
  identity and AKM fences stale attempts, but an ambiguous process crash can be
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.9.17-alpha.6",
3
+ "version": "0.9.17-alpha.7",
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": [