akm-cli 0.9.2-alpha.4 → 0.9.2
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 +493 -0
- package/STABILITY.md +23 -5
- package/dist/assets/hints/cli-hints-full.md +12 -7
- package/dist/assets/tasks/core/extract.yml +3 -5
- package/dist/assets/tasks/core/improve.yml +3 -5
- package/dist/assets/tasks/core/index-refresh.yml +3 -5
- package/dist/assets/tasks/core/sync.yml +3 -5
- package/dist/assets/tasks/core/version-check.yml +3 -5
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +3 -5
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -6
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +3 -5
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +3 -5
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +3 -5
- package/dist/cli/unknown-flags.js +12 -1
- package/dist/cli.js +8 -1
- package/dist/commands/command/command-execution.js +23 -2
- package/dist/commands/health/improve-metrics.js +38 -0
- package/dist/commands/health/windows.js +8 -4
- package/dist/commands/health.js +8 -4
- package/dist/commands/lint/index.js +1 -1
- package/dist/commands/migrate-cli.js +130 -24
- package/dist/commands/proposal/validators/proposal-validators.js +7 -2
- package/dist/commands/tasks/explain.js +304 -0
- package/dist/commands/tasks/tasks-cli.js +185 -3
- package/dist/commands/tasks/tasks.js +265 -52
- package/dist/commands/workflow/plan.js +159 -0
- package/dist/commands/workflow-cli.js +94 -2
- package/dist/core/activation-policy.js +2 -12
- package/dist/core/adapter/adapters/akm-lint.js +7 -4
- package/dist/core/adapter/adapters/akm-metadata.js +26 -14
- package/dist/core/adapter/adapters/akm-task-adapter.js +13 -10
- package/dist/core/errors.js +45 -0
- package/dist/core/json-schema.js +15 -5
- package/dist/core/state/migrations.js +57 -0
- package/dist/core/state-db.js +16 -14
- package/dist/core/subprocess.js +47 -13
- package/dist/execution/guarded-source.js +44 -0
- package/dist/execution/input-contract.js +250 -0
- package/dist/execution/target-ref.js +63 -0
- package/dist/indexer/usage/usage-events.js +14 -3
- package/dist/integrations/agent/execution-lowering.js +12 -1
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +12 -3
- package/dist/output/text/workflow-format.js +192 -10
- package/dist/output/text/workflow.js +2 -1
- package/dist/runtime.js +1 -0
- package/dist/scripts/akm-migrate-node.js +11838 -10118
- package/dist/scripts/akm-migrate.js +11828 -10117
- package/dist/setup/steps/tasks.js +34 -17
- package/dist/storage/repositories/task-history-repository.js +5 -1
- package/dist/storage/repositories/workflow-runs-repository.js +144 -6
- package/dist/tasks/backends/launchd.js +31 -84
- package/dist/tasks/embedded.js +13 -7
- package/dist/tasks/model/invocation.js +4 -0
- package/dist/tasks/prepare/prepare-script-target.js +9 -0
- package/dist/tasks/prepare/prepare-support.js +154 -0
- package/dist/tasks/prepare/prepare.js +117 -0
- package/dist/tasks/prepare/prepared-execution.js +4 -0
- package/dist/tasks/prepare/script-capture.js +80 -0
- package/dist/tasks/run/attempt-lifecycle.js +165 -0
- package/dist/tasks/run/load-task.js +117 -0
- package/dist/tasks/run/provenance.js +20 -0
- package/dist/tasks/run/run-command-task.js +92 -0
- package/dist/tasks/run/run-native-task.js +222 -0
- package/dist/tasks/run/run-task.js +99 -0
- package/dist/tasks/run/run-workflow-task.js +222 -0
- package/dist/tasks/run/task-history.js +134 -0
- package/dist/tasks/run/task-log.js +179 -0
- package/dist/tasks/run/task-result.js +19 -0
- package/dist/tasks/scheduler-binding.js +66 -2
- package/dist/tasks/scheduler-invocation.js +63 -3
- package/dist/tasks/scheduler-sync.js +77 -14
- package/dist/tasks/source/bounded-document.js +455 -0
- package/dist/tasks/source/parse-task-source.js +59 -0
- package/dist/tasks/source/project-v4.js +62 -0
- package/dist/tasks/source/task-input-diagnostics.js +36 -0
- package/dist/tasks/source/task-source-v4.js +626 -0
- package/dist/tasks/source-v3.js +10 -733
- package/dist/tasks/task-run-reserved-flags.js +79 -0
- package/dist/workflows/authoring/authoring.js +17 -8
- package/dist/workflows/exec/child-invocation.js +34 -0
- package/dist/workflows/exec/child-workflow.js +370 -0
- package/dist/workflows/exec/exec-unit.js +50 -170
- package/dist/workflows/exec/frozen-judge.js +19 -2
- package/dist/workflows/exec/native-executor.js +49 -27
- package/dist/workflows/exec/param-secrets.js +12 -0
- package/dist/workflows/exec/run-workflow.js +48 -59
- package/dist/workflows/exec/step-work.js +222 -80
- package/dist/workflows/exec/unit-dispatch.js +72 -0
- package/dist/workflows/freeze/child-output-references.js +94 -0
- package/dist/workflows/freeze/environment.js +174 -0
- package/dist/workflows/freeze/identity.js +22 -0
- package/dist/workflows/freeze/resolve-steps.js +78 -0
- package/dist/workflows/freeze/source-freeze.js +57 -0
- package/dist/workflows/freeze/step-values.js +68 -0
- package/dist/workflows/freeze/targets/child-workflow.js +206 -0
- package/dist/workflows/freeze/targets/command.js +81 -0
- package/dist/workflows/freeze/targets/script.js +57 -0
- package/dist/workflows/freeze/targets/shell.js +31 -0
- package/dist/workflows/freeze/targets/task.js +179 -0
- package/dist/workflows/freeze/task-bindings.js +180 -0
- package/dist/workflows/ir/compile.js +59 -11
- package/dist/workflows/ir/environment-v4.js +3 -3
- package/dist/workflows/ir/freeze-v4.js +41 -7
- package/dist/workflows/ir/params.js +58 -131
- package/dist/workflows/ir/plan-hash.js +3 -3
- package/dist/workflows/ir/schema-v4.js +246 -17
- package/dist/workflows/parser.js +74 -2
- package/dist/workflows/program/schema.js +5 -2
- package/dist/workflows/resource-limits.js +20 -0
- package/dist/workflows/runtime/plan-classifier.js +24 -7
- package/dist/workflows/runtime/run-outputs.js +103 -0
- package/dist/workflows/runtime/runs.js +114 -9
- package/dist/workflows/runtime/workflow-asset-loader.js +14 -6
- package/dist/workflows/source-files.js +5 -5
- package/dist/workflows/source-ir/compare.js +17 -0
- package/dist/workflows/source-ir/compile.js +7 -3
- package/dist/workflows/source-ir/github-yaml.js +64 -17
- package/dist/workflows/source-ir/schema.js +69 -21
- package/dist/workflows/source-ir/semantics.js +7 -25
- package/dist/workflows/source-ir/triggers.js +79 -0
- package/dist/workflows/source-ir/uses.js +33 -7
- package/docs/migration/README.md +1 -1
- package/docs/migration/release-notes/0.9.2.md +87 -11
- package/docs/migration/release-notes/README.md +3 -2
- package/docs/migration/v0.8-to-v0.9.md +13 -11
- package/docs/migration/v0.9.0-troubleshooting.md +20 -13
- package/docs/migration/v0.9.1-to-v0.9.2.md +598 -49
- package/docs/reference/README.md +1 -1
- package/docs/reference/cli.md +140 -46
- package/docs/reference/configuration.md +6 -5
- package/docs/reference/supported-formats.md +9 -5
- package/docs/reference/tasks.md +338 -75
- package/docs/reference/workflow-schema.md +290 -16
- package/docs/reference/workflows.md +57 -7
- package/package.json +1 -1
- package/schemas/akm-task.json +173 -118
- package/schemas/akm-workflow.json +28 -0
- package/dist/tasks/runner.js +0 -941
- package/dist/tasks/runtime-v3.js +0 -281
- package/dist/workflows/ir/source-freeze-v4.js +0 -506
- package/dist/workflows/source-ir/ordering.js +0 -38
|
@@ -7,15 +7,22 @@
|
|
|
7
7
|
* `create --print` emits Markdown; execution accepts peer `.md` and
|
|
8
8
|
* GitHub-shaped `.yml` workflow sources. Validate with `akm lint --type workflows`.
|
|
9
9
|
*/
|
|
10
|
+
import { getParsedInvocation } from "../cli/invocation.js";
|
|
10
11
|
import { getStringArg } from "../cli/parse-args.js";
|
|
11
12
|
import { defineGroupCommand, defineJsonCommand, EXIT_CODES, output } from "../cli/shared.js";
|
|
12
13
|
import { armAbortDeadline } from "../core/abort-deadline.js";
|
|
13
14
|
import { assertFlatAssetName, combineCreatePath, normalizeCreateSubPath } from "../core/asset/asset-create.js";
|
|
14
15
|
import { NotFoundError, UsageError } from "../core/errors.js";
|
|
15
16
|
import { akmIndex } from "../indexer/indexer.js";
|
|
17
|
+
import { getOutputMode } from "../output/context.js";
|
|
18
|
+
import { renderGenericText } from "../output/generic-render.js";
|
|
19
|
+
import { deliverRendered } from "../output/html-render.js";
|
|
20
|
+
import { shapeForCommand } from "../output/shapes.js";
|
|
21
|
+
import { formatPlain } from "../output/text.js";
|
|
16
22
|
import { assertWorkflowMarkdownName, createWorkflowAsset, getWorkflowTemplate } from "../workflows/authoring/authoring.js";
|
|
17
23
|
import { WORKFLOW_MAX_RETRIES, WORKFLOW_MAX_TIMEOUT_MS } from "../workflows/ir/schema.js";
|
|
18
24
|
import { abandonWorkflowRun, getWorkflowStatus, hasWorkflowRun, listWorkflowRuns, resumeWorkflowRun, } from "../workflows/runtime/runs.js";
|
|
25
|
+
import { akmWorkflowPlan } from "./workflow/plan.js";
|
|
19
26
|
const workflowStatusCommand = defineJsonCommand({
|
|
20
27
|
meta: {
|
|
21
28
|
name: "status",
|
|
@@ -63,9 +70,18 @@ const workflowListCommand = defineJsonCommand({
|
|
|
63
70
|
args: {
|
|
64
71
|
ref: { type: "string", description: "Filter to one workflow ref" },
|
|
65
72
|
active: { type: "boolean", description: "Only show active runs", default: false },
|
|
73
|
+
children: {
|
|
74
|
+
type: "boolean",
|
|
75
|
+
description: "Also include child workflow runs (hidden by default, P3b)",
|
|
76
|
+
default: false,
|
|
77
|
+
},
|
|
66
78
|
},
|
|
67
79
|
async run({ args }) {
|
|
68
|
-
const result = await listWorkflowRuns({
|
|
80
|
+
const result = await listWorkflowRuns({
|
|
81
|
+
workflowRef: args.ref,
|
|
82
|
+
activeOnly: args.active,
|
|
83
|
+
includeChildren: args.children,
|
|
84
|
+
});
|
|
69
85
|
output("workflow-list", result);
|
|
70
86
|
},
|
|
71
87
|
});
|
|
@@ -131,7 +147,9 @@ const workflowCreateCommand = defineJsonCommand({
|
|
|
131
147
|
// Index the newly-written workflow so `akm workflow run` can resolve
|
|
132
148
|
// a workflowEntryId without requiring an explicit `akm index` call
|
|
133
149
|
// first. Uses the same incremental index path that `akm add` uses.
|
|
134
|
-
|
|
150
|
+
// `result.bundleDir` — the indexer's own `IndexOptions.stashDir` field
|
|
151
|
+
// name is unchanged (indexer vocabulary is out of scope, P4 row B-50).
|
|
152
|
+
await akmIndex({ stashDir: result.bundleDir });
|
|
135
153
|
output("workflow-create", { ok: true, ...result });
|
|
136
154
|
},
|
|
137
155
|
});
|
|
@@ -286,6 +304,79 @@ function parseWorkflowTimeout(raw) {
|
|
|
286
304
|
}
|
|
287
305
|
return timeoutMs;
|
|
288
306
|
}
|
|
307
|
+
// P3b Lane B (spec docs/plans/specs/p3b-child-executor.md §4.6): read-only
|
|
308
|
+
// compile+freeze introspection — zero durable writes, zero usage/event rows
|
|
309
|
+
// (row B-48). `--json` is deliberately NOT a flag anywhere in this CLI
|
|
310
|
+
// (B-N9); the global `--format json` is spliced on by `defineJsonCommand`.
|
|
311
|
+
const workflowPlanCommand = defineJsonCommand({
|
|
312
|
+
meta: {
|
|
313
|
+
name: "plan",
|
|
314
|
+
description: "Compile and freeze a workflow WITHOUT publishing it: the canonical step graph, per-step frozen target " +
|
|
315
|
+
"kinds, task/child expansion, input bindings, source read set, and lowering notices. Zero durable writes.",
|
|
316
|
+
},
|
|
317
|
+
args: {
|
|
318
|
+
ref: { type: "positional", description: "Workflow ref (workflows/<name>)", required: true },
|
|
319
|
+
},
|
|
320
|
+
async run({ args }) {
|
|
321
|
+
const result = await akmWorkflowPlan(args.ref);
|
|
322
|
+
// B-46/B-57: `akm workflow plan` is read-only introspection whose UNMARKED
|
|
323
|
+
// default is a human summary — `--format json` (B-N9) is the opt-in for
|
|
324
|
+
// the full structure, the mirror image of every other verb's json-by-
|
|
325
|
+
// default (DEFAULT_CONFIG.output.format).
|
|
326
|
+
//
|
|
327
|
+
// Detecting "the caller named no format at all" MUST NOT read
|
|
328
|
+
// `args.format` (code-review round 4, finding 3 / Review log R3):
|
|
329
|
+
// citty's one-parse rule (GLOBAL_OUTPUT_ARGS's own doc comment, "no
|
|
330
|
+
// command body may read these args") isn't just style here — reading it
|
|
331
|
+
// is actively wrong. citty parses each command level against only that
|
|
332
|
+
// level's own remaining argv, so a GLOBAL, pre-subcommand `--format json`
|
|
333
|
+
// (e.g. `akm --format json workflow plan <ref>`) is consumed by the ROOT
|
|
334
|
+
// command's own declared `format` arg before the `workflow`/`plan`
|
|
335
|
+
// subcommand tokens are even resolved — this LEAF's `args.format` reads
|
|
336
|
+
// `undefined` in exactly that case too, indistinguishable from "no
|
|
337
|
+
// format was named anywhere". Reproduced live: that invocation printed
|
|
338
|
+
// the human TEXT summary at exit 0 even though `getOutputMode().format`
|
|
339
|
+
// was already `"json"` (the control, `akm --format json workflow list`,
|
|
340
|
+
// correctly emitted JSON — only this leaf's own arg-read was wrong).
|
|
341
|
+
// Detect it instead off the process-wide invocation singleton
|
|
342
|
+
// (`getParsedInvocation`, src/cli/invocation.ts) — the same canonical,
|
|
343
|
+
// position-independent argv parse `src/cli.ts` mints ONCE at startup
|
|
344
|
+
// (`setParsedInvocation`, immediately before `initOutputMode` builds the
|
|
345
|
+
// `getOutputMode()` singleton from that identical argv), so this agrees
|
|
346
|
+
// with `getOutputMode()` regardless of where `--format` appeared. A bare
|
|
347
|
+
// `process.argv` read is reserved for `src/cli.ts`/`cli/invocation.ts`
|
|
348
|
+
// themselves (`lint-process-argv.ts`); every other module reads through
|
|
349
|
+
// this singleton instead. When explicit, this defers to the normal
|
|
350
|
+
// `output()` path (json/yaml/text/md/html/jsonl, `--output <path>`)
|
|
351
|
+
// unchanged; when absent, it reproduces `output()`'s OWN "text" branch
|
|
352
|
+
// verbatim (same shape/detail projection, same registered-formatter-or-
|
|
353
|
+
// generic-fallback, same `--output <path>` handling) without touching
|
|
354
|
+
// the shared dispatcher other commands rely on.
|
|
355
|
+
//
|
|
356
|
+
// "No format named anywhere" also has to check the RESOLVED mode, not
|
|
357
|
+
// just argv: `getOutputMode().format` already folds a persisted
|
|
358
|
+
// `output.format` config default in ahead of the hardcoded "json"
|
|
359
|
+
// fallback (`resolveOutputMode`, src/output/context.ts — argv ?? config
|
|
360
|
+
// default ?? "json"). A user who has configured e.g. `output.format:
|
|
361
|
+
// "yaml"` gets yaml from every other command with no `--format` on the
|
|
362
|
+
// line; `workflow plan` must honor that too instead of forcing its
|
|
363
|
+
// human-text branch over a real persisted default. A resolved format of
|
|
364
|
+
// exactly "json" is deliberately left on the text branch below: it's
|
|
365
|
+
// indistinguishable from "nothing configured" (DEFAULT_CONFIG.output.format
|
|
366
|
+
// is also "json" — OutputConfigSchema's `format` carries no independent
|
|
367
|
+
// zod default, so the merge is the only source), and collapsing that case
|
|
368
|
+
// onto the JSON envelope would erase the documented unmarked-default text
|
|
369
|
+
// summary for the overwhelmingly common "user configured nothing" case.
|
|
370
|
+
const mode = getOutputMode();
|
|
371
|
+
if (getParsedInvocation().getFlagValue("--format") === undefined && mode.format === "json") {
|
|
372
|
+
const shaped = shapeForCommand("workflow-plan", result, mode.detail, mode.shape);
|
|
373
|
+
const plain = formatPlain("workflow-plan", shaped, mode.detail);
|
|
374
|
+
deliverRendered(plain ?? renderGenericText("workflow-plan", shaped), mode.outputPath);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
output("workflow-plan", result);
|
|
378
|
+
},
|
|
379
|
+
});
|
|
289
380
|
const workflowAbandonCommand = defineJsonCommand({
|
|
290
381
|
meta: {
|
|
291
382
|
name: "abandon",
|
|
@@ -324,6 +415,7 @@ export const workflowCommand = defineGroupCommand({
|
|
|
324
415
|
resume: workflowResumeCommand,
|
|
325
416
|
abandon: workflowAbandonCommand,
|
|
326
417
|
run: workflowRunCommand,
|
|
418
|
+
plan: workflowPlanCommand,
|
|
327
419
|
},
|
|
328
420
|
// No `defaultRun`: bare `akm workflow` is a usage error (exit 2), the
|
|
329
421
|
// canonical bare-group behavior — owner ruling 12. Run `akm workflow list
|
|
@@ -27,22 +27,12 @@ export function decideDangerousKeyInstall(input) {
|
|
|
27
27
|
return "allow";
|
|
28
28
|
return input.allowInsecure ? "warn-allow" : "gate";
|
|
29
29
|
}
|
|
30
|
-
// ── Rule 3:
|
|
31
|
-
/**
|
|
32
|
-
* Whether a scheduler-generated task invocation must be skipped because the
|
|
33
|
-
* task is not activated (its `enabled:` is false). Manual (non-scheduled) runs
|
|
34
|
-
* are always dispatched — installing a task grants nothing until enabled, but
|
|
35
|
-
* the operator may still run it by hand for catch-up / testing. See rule 3.
|
|
36
|
-
*/
|
|
37
|
-
export function shouldSkipUnactivatedTask(input) {
|
|
38
|
-
return !input.enabled && input.scheduled;
|
|
39
|
-
}
|
|
40
|
-
// ── Rule 4: write activation (search-source.ts, installations.ts) ────────────
|
|
30
|
+
// ── Rule 3: write activation (search-source.ts, installations.ts) ────────────
|
|
41
31
|
/**
|
|
42
32
|
* Whether a resolved source is write-activated. Only the primary stash and
|
|
43
33
|
* sources explicitly marked `writable: true` are writable; registry-cached
|
|
44
34
|
* (installed, read-only) sources are never written in place because
|
|
45
|
-
* `akm update` overwrites them. See rule
|
|
35
|
+
* `akm update` overwrites them. See rule 3 above.
|
|
46
36
|
*/
|
|
47
37
|
export function isSourceWriteActivated(source) {
|
|
48
38
|
return source.writable === true;
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
*/
|
|
53
53
|
import path from "node:path";
|
|
54
54
|
import { isDangerousEnvKey } from "../../../commands/lint/env-key-rules.js";
|
|
55
|
-
import {
|
|
55
|
+
import { parseTaskSource } from "../../../tasks/source/parse-task-source.js";
|
|
56
|
+
import { taskSourceErrorDetail } from "../../../tasks/source-v3.js";
|
|
56
57
|
import { compileWorkflowPlan } from "../../../workflows/ir/compile.js";
|
|
57
58
|
import { compileWorkflowSource } from "../../../workflows/source-ir/compile.js";
|
|
58
59
|
import { conceptIdForStashFile } from "../../asset/resolve-ref.js";
|
|
@@ -271,13 +272,15 @@ export function factDiagnostics(relPath, data) {
|
|
|
271
272
|
return [];
|
|
272
273
|
}
|
|
273
274
|
/**
|
|
274
|
-
* Task validation has one semantic owner: the
|
|
275
|
+
* Task validation has one semantic owner: the version-routed task source
|
|
276
|
+
* parser (spec docs/plans/specs/p2a-task-source-v4.md §3.6) — `version: 3`
|
|
277
|
+
* through the strict task-v3 grammar, `version: 4` through task source v4.
|
|
275
278
|
* Keeping raw YAML at this boundary preserves duplicate-key, alias/tag,
|
|
276
279
|
* source-location, descriptor, resource-bound, and migration-hint behavior.
|
|
277
280
|
*/
|
|
278
281
|
export function taskDiagnostics(relPath, raw, workspaceRoot) {
|
|
279
282
|
try {
|
|
280
|
-
|
|
283
|
+
parseTaskSource({
|
|
281
284
|
filePath: relPath,
|
|
282
285
|
yaml: raw,
|
|
283
286
|
...(workspaceRoot ? { workspaceRoot } : {}),
|
|
@@ -289,7 +292,7 @@ export function taskDiagnostics(relPath, raw, workspaceRoot) {
|
|
|
289
292
|
{
|
|
290
293
|
file: relPath,
|
|
291
294
|
issue: "invalid-task-yaml",
|
|
292
|
-
detail:
|
|
295
|
+
detail: taskSourceErrorDetail(cause),
|
|
293
296
|
fixed: false,
|
|
294
297
|
},
|
|
295
298
|
];
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
* distinction never triggers.
|
|
56
56
|
*/
|
|
57
57
|
import { scanEnvKeyNames } from "../../../commands/env/env.js";
|
|
58
|
-
import {
|
|
58
|
+
import { parseTaskSource } from "../../../tasks/source/parse-task-source.js";
|
|
59
59
|
import { compileWorkflowSource } from "../../../workflows/source-ir/compile.js";
|
|
60
60
|
import { sourceStepInstructions } from "../../../workflows/source-ir/program.js";
|
|
61
61
|
import { parseFrontmatter } from "../../asset/frontmatter.js";
|
|
@@ -229,25 +229,37 @@ export function foldRecognizedMetadata(rendererName, file) {
|
|
|
229
229
|
case "task-yaml": {
|
|
230
230
|
out.tags = Array.from(new Set([...(out.tags ?? []), "task", "scheduled"]));
|
|
231
231
|
try {
|
|
232
|
-
const
|
|
232
|
+
const parsed = parseTaskSource({ yaml: file.content(), filePath: file.absPath, workspaceRoot: file.stashRoot });
|
|
233
|
+
const v4 = parsed.v4;
|
|
234
|
+
const target = v4.target;
|
|
233
235
|
const hints = new Set();
|
|
234
|
-
for (const binding of
|
|
236
|
+
for (const binding of v4.schedule)
|
|
235
237
|
hints.add(`schedule:${binding.cron}`);
|
|
236
|
-
if (
|
|
237
|
-
if (
|
|
238
|
-
hints.add(`workflow:${
|
|
239
|
-
else if (
|
|
240
|
-
hints.add(`prompt:${
|
|
241
|
-
else if (
|
|
242
|
-
hints.add(`prompt:${
|
|
243
|
-
else if (
|
|
244
|
-
hints.add(`prompt:${
|
|
238
|
+
if (target.kind === "uses") {
|
|
239
|
+
if (target.uses.kind === "workflow")
|
|
240
|
+
hints.add(`workflow:${target.uses.ref}`);
|
|
241
|
+
else if (target.uses.kind === "command")
|
|
242
|
+
hints.add(`prompt:${target.uses.ref}`);
|
|
243
|
+
else if (target.command?.kind === "inline")
|
|
244
|
+
hints.add(`prompt:${target.command.content}`);
|
|
245
|
+
else if (target.command?.kind === "stored")
|
|
246
|
+
hints.add(`prompt:${target.command.ref}`);
|
|
245
247
|
else
|
|
246
|
-
hints.add(`uses:${
|
|
248
|
+
hints.add(`uses:${target.uses.ref}`);
|
|
247
249
|
}
|
|
248
250
|
else {
|
|
249
|
-
hints.add(`run:${
|
|
251
|
+
hints.add(`run:${target.run}`);
|
|
250
252
|
}
|
|
253
|
+
if (v4.description && !out.description) {
|
|
254
|
+
out.description = v4.description;
|
|
255
|
+
out.source = "task-source";
|
|
256
|
+
out.confidence = 0.9;
|
|
257
|
+
}
|
|
258
|
+
if (v4.tags && v4.tags.length > 0) {
|
|
259
|
+
out.tags = Array.from(new Set([...(out.tags ?? []), ...v4.tags]));
|
|
260
|
+
}
|
|
261
|
+
if (v4.when_to_use)
|
|
262
|
+
hints.add(`when_to_use:${v4.when_to_use}`);
|
|
251
263
|
finalizeHints(out, hints);
|
|
252
264
|
}
|
|
253
265
|
catch {
|
|
@@ -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
|
/**
|
|
5
|
-
* The `akm-task` adapter for
|
|
5
|
+
* The `akm-task` adapter for task `.yml` sources.
|
|
6
6
|
*
|
|
7
7
|
* A native akm task-YAML bundle (spec §6/§7). A `.yml` file derives
|
|
8
8
|
* `type: task`; conceptId strips the `.yml` extension. Tasks are AKM-native
|
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
*
|
|
17
17
|
* ── validate (spec §6 task validation column) ──
|
|
18
18
|
*
|
|
19
|
-
* Validation enters the canonical task
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* Validation enters the canonical task source parser (`parseTaskSource`,
|
|
20
|
+
* task source v4 only as of P4 — a `version: 3` or `version: 2` document now
|
|
21
|
+
* fails closed with `TASK_SCHEMA_VERSION_UNSUPPORTED`). That parser owns the
|
|
22
|
+
* closed key sets, the executable-selector XOR, hostile YAML policy, the
|
|
23
|
+
* `akm/command` builtin, bounds, and physical `working-directory`
|
|
24
|
+
* containment. The adapter only translates a parser failure into the
|
|
25
|
+
* format-family diagnostic shape.
|
|
24
26
|
*
|
|
25
27
|
* Conformance oracle (authored, DO NOT modify): fixture
|
|
26
28
|
* `tests/fixtures/bundles/akm-task/` + goldens
|
|
@@ -28,7 +30,8 @@
|
|
|
28
30
|
*/
|
|
29
31
|
import fs from "node:fs";
|
|
30
32
|
import path from "node:path";
|
|
31
|
-
import {
|
|
33
|
+
import { parseTaskSource } from "../../../tasks/source/parse-task-source.js";
|
|
34
|
+
import { TASK_EXTENSION, TASK_NEAR_MISS_EXTENSION, taskExtensionDetail, taskSourceErrorDetail, } from "../../../tasks/source-v3.js";
|
|
32
35
|
import { hashContent } from "./shared.js";
|
|
33
36
|
/** A native task bundle is single-component; its one component is `main`. */
|
|
34
37
|
const COMPONENT_ID = "main";
|
|
@@ -83,13 +86,13 @@ async function validate(c, changes, ctx) {
|
|
|
83
86
|
continue;
|
|
84
87
|
}
|
|
85
88
|
try {
|
|
86
|
-
|
|
89
|
+
parseTaskSource({ yaml: raw, filePath: relPath, workspaceRoot: c.root });
|
|
87
90
|
}
|
|
88
91
|
catch (cause) {
|
|
89
92
|
diagnostics.push({
|
|
90
93
|
file: relPath,
|
|
91
94
|
issue: "invalid-task-yaml",
|
|
92
|
-
detail:
|
|
95
|
+
detail: taskSourceErrorDetail(cause),
|
|
93
96
|
fixed: false,
|
|
94
97
|
});
|
|
95
98
|
}
|
|
@@ -146,7 +149,7 @@ export const akmTaskAdapter = {
|
|
|
146
149
|
continue;
|
|
147
150
|
}
|
|
148
151
|
try {
|
|
149
|
-
|
|
152
|
+
parseTaskSource({ yaml: raw, filePath: entry.name, workspaceRoot: root });
|
|
150
153
|
return true;
|
|
151
154
|
}
|
|
152
155
|
catch {
|
package/dist/core/errors.js
CHANGED
|
@@ -19,6 +19,20 @@ const CONFIG_HINTS = {
|
|
|
19
19
|
UNKNOWN_IMPROVE_STRATEGY: "Pass one of the listed strategy names to `--strategy`, or define it under `improve.strategies`. Names are case-sensitive.",
|
|
20
20
|
EXECUTION_NOT_AUTHORIZED: "Change the selected tools or update the machine/user execution policy, then retry.",
|
|
21
21
|
};
|
|
22
|
+
// Code-review finding: COMPOSITION_INVALID covers several unrelated causes
|
|
23
|
+
// (a rejected with:, a multi-job source, a composition cycle/depth/size
|
|
24
|
+
// violation, an invalid child-output reference). USAGE_HINTS below carries
|
|
25
|
+
// only the with:-rejection text — accurate for every with:-rejection throw
|
|
26
|
+
// site (none of which pass an explicit constructor hint), but wrong for the
|
|
27
|
+
// others. Those throw sites pass their OWN explicit hint (the constructor's
|
|
28
|
+
// 3rd argument overrides USAGE_HINTS, per errors-usage-hints.test.ts's
|
|
29
|
+
// "explicit constructor hint still overrides the USAGE_HINTS default").
|
|
30
|
+
// Multi-job rejection is thrown from 5 separate call sites across
|
|
31
|
+
// src/tasks/prepare/prepare-support.ts and src/workflows/**, so its hint is
|
|
32
|
+
// centralized here as the one shared string all 5 import, rather than
|
|
33
|
+
// duplicated at each site and risking drift.
|
|
34
|
+
export const COMPOSITION_INVALID_MULTI_JOB_HINT = "AKM workflows support exactly one job per source, with no needs: between jobs. Split the extra job(s) into " +
|
|
35
|
+
"their own workflow file, and compose them with uses: workflows/<ref> instead.";
|
|
22
36
|
/** Default hint for each UsageError code. */
|
|
23
37
|
const USAGE_HINTS = {
|
|
24
38
|
INVALID_FLAG_VALUE: "Run `akm <command> --help` to see accepted values.",
|
|
@@ -32,6 +46,37 @@ const USAGE_HINTS = {
|
|
|
32
46
|
MISSING_REQUIRED_ARGUMENT: "Refs use the form [bundle//]conceptId, e.g. `akm show knowledge/guide.md` or `akm show skills/deploy`.",
|
|
33
47
|
UNKNOWN_COMMAND: "Run `akm --help` to see available commands.",
|
|
34
48
|
UNKNOWN_FLAG: "Run the command with `--help` to see its accepted flags.",
|
|
49
|
+
// P2b (docs/plans/specs/p2b-input-bindings.md §1.7 A-N5, §7 F-A3): the
|
|
50
|
+
// "arrives in a later 0.9.x release" promise is gone now that task-call
|
|
51
|
+
// inputs are implemented. Names the two real rejection causes instead:
|
|
52
|
+
// (1) a task target that declares no inputs: at all, (2) commands/<ref> /
|
|
53
|
+
// scripts/<ref>, which are never binding surfaces. F-A3 authorizes this
|
|
54
|
+
// edit and the matching pinned-string update in
|
|
55
|
+
// tests/core/errors-usage-hints.test.ts in the same commit.
|
|
56
|
+
//
|
|
57
|
+
// Code-review finding: this default is reached ONLY by with:-rejection
|
|
58
|
+
// throw sites (the ones above, plus task.ts's noDeclaredInputsError and
|
|
59
|
+
// resolve-steps.ts's rejectNonTaskBindingWith) — every other
|
|
60
|
+
// COMPOSITION_INVALID throw site (multi-job source, composition
|
|
61
|
+
// cycle/depth/size, invalid child-output reference, an env: on a
|
|
62
|
+
// composing step) passes its own explicit constructor hint instead of
|
|
63
|
+
// falling through to this text, so this stays scoped and accurate rather
|
|
64
|
+
// than generalized into something vaguer.
|
|
65
|
+
COMPOSITION_INVALID: "Remove the with: block, or target a tasks/<ref> whose source declares inputs: — commands/<ref> and scripts/<ref> steps are not binding surfaces.",
|
|
66
|
+
TASK_SOURCE_INVALID: "Fix the task source at the reported path and line, then re-run.",
|
|
67
|
+
TARGET_REF_INVALID: "Targets are canonical asset refs: `commands/review`, `scripts/build.sh`, `tasks/nightly`, `workflows/release`.",
|
|
68
|
+
// P4 (docs/plans/specs/p4-deletions-closeout.md §4.1, row B-53, R-R5): the
|
|
69
|
+
// original hint named `akm workflow validate`, a verb that was never
|
|
70
|
+
// implemented. Points at the two verbs that actually inspect a workflow
|
|
71
|
+
// source without executing it.
|
|
72
|
+
WORKFLOW_SOURCE_INVALID: "Run `akm lint` to see the failing source location, or `akm workflow plan <ref>` to compile it without writing.",
|
|
73
|
+
INPUT_BINDING_INVALID: "Check the step's with: keys against the target's declared inputs.",
|
|
74
|
+
TASK_TARGET_UNSUPPORTED: "Task definitions support command, script, workflow, and shell (run:) targets; akm/command is layered by callers.",
|
|
75
|
+
// P3a (docs/plans/specs/p3a-plan-v5-child-freeze.md §3.2, A-N2): the
|
|
76
|
+
// complete-or-abandon policy for a stored pre-irVersion-5 run.
|
|
77
|
+
WORKFLOW_IR_VERSION_UNSUPPORTED: "Abandon the run with `akm workflow abandon <id>`, then start it again from the workflow source — pre-0.9.2 frozen plans are not re-executable.",
|
|
78
|
+
// P3b (docs/plans/specs/p3b-child-executor.md §4.3).
|
|
79
|
+
WORKFLOW_OUTPUT_INVALID: "Check each `outputs:` entry's `from:` against the step artifact it names, and its `schema:` against the value that step actually promotes.",
|
|
35
80
|
};
|
|
36
81
|
/** Default hint for each NotFoundError code. */
|
|
37
82
|
const NOT_FOUND_HINTS = {
|
package/dist/core/json-schema.js
CHANGED
|
@@ -62,10 +62,10 @@
|
|
|
62
62
|
const MAX_DEFINITION_DEPTH = 64;
|
|
63
63
|
/** Total (schema node × value node) visits one {@link validateJsonSchemaSubset} call may make. */
|
|
64
64
|
const MAX_VALIDATION_NODES = 100_000;
|
|
65
|
-
export function validateJsonSchemaSubset(value, schema) {
|
|
65
|
+
export function validateJsonSchemaSubset(value, schema, options) {
|
|
66
66
|
const errors = [];
|
|
67
67
|
const budget = { nodes: MAX_VALIDATION_NODES };
|
|
68
|
-
validateNode(value, schema, "$", { errors, budget, depth: 0 });
|
|
68
|
+
validateNode(value, schema, "$", { errors, budget, depth: 0, redactValues: options?.redactValues ?? false });
|
|
69
69
|
if (budget.nodes < 0) {
|
|
70
70
|
errors.push(`$: schema evaluation exceeded the limit of ${MAX_VALIDATION_NODES} checks and was stopped`);
|
|
71
71
|
}
|
|
@@ -390,7 +390,13 @@ function validateNode(value, schema, path, ctx) {
|
|
|
390
390
|
if (Array.isArray(schema.enum) && schema.enum.length > 0) {
|
|
391
391
|
const allowed = schema.enum;
|
|
392
392
|
if (!allowed.some((candidate) => candidate === value)) {
|
|
393
|
-
|
|
393
|
+
// Never echo the supplied value when redacting: enum-constrained inputs
|
|
394
|
+
// can carry credentials, and this detail lands in stderr envelopes that
|
|
395
|
+
// get pasted into CI logs. The allowed list is safe — it comes from the
|
|
396
|
+
// author-declared schema, not from user-supplied data.
|
|
397
|
+
errors.push(ctx.redactValues
|
|
398
|
+
? `${path}: value is not one of ${JSON.stringify(allowed)}`
|
|
399
|
+
: `${path}: value ${JSON.stringify(value)} is not one of ${JSON.stringify(allowed)}`);
|
|
394
400
|
return;
|
|
395
401
|
}
|
|
396
402
|
}
|
|
@@ -409,10 +415,14 @@ function validateNode(value, schema, path, ctx) {
|
|
|
409
415
|
}
|
|
410
416
|
if ((actual === "number" || actual === "integer") && typeof value === "number") {
|
|
411
417
|
if (typeof schema.minimum === "number" && value < schema.minimum) {
|
|
412
|
-
errors.push(
|
|
418
|
+
errors.push(ctx.redactValues
|
|
419
|
+
? `${path}: value is below minimum ${schema.minimum}`
|
|
420
|
+
: `${path}: ${value} is below minimum ${schema.minimum}`);
|
|
413
421
|
}
|
|
414
422
|
if (typeof schema.maximum === "number" && value > schema.maximum) {
|
|
415
|
-
errors.push(
|
|
423
|
+
errors.push(ctx.redactValues
|
|
424
|
+
? `${path}: value is above maximum ${schema.maximum}`
|
|
425
|
+
: `${path}: ${value} is above maximum ${schema.maximum}`);
|
|
416
426
|
}
|
|
417
427
|
return;
|
|
418
428
|
}
|
|
@@ -33,6 +33,8 @@ export const STATE_MIGRATION_SAFETY_BY_ID = Object.freeze({
|
|
|
33
33
|
"020-three-db-cutover": "additive",
|
|
34
34
|
"021-asset-state-missing-since": "additive",
|
|
35
35
|
"022-workflow-unit-attempts": "additive",
|
|
36
|
+
"023-child-workflow-runs": "additive",
|
|
37
|
+
"024-workflow-run-outputs": "additive",
|
|
36
38
|
});
|
|
37
39
|
export const STATE_MIGRATIONS = [
|
|
38
40
|
// ── Migration 001 — initial schema ──────────────────────────────────────────
|
|
@@ -1022,6 +1024,61 @@ export const STATE_MIGRATIONS = [
|
|
|
1022
1024
|
ON workflow_run_unit_attempts(run_id, status, claim_expires_at);
|
|
1023
1025
|
`,
|
|
1024
1026
|
},
|
|
1027
|
+
// ── Migration 023 — child workflow run parentage (P3a) ────────────────────
|
|
1028
|
+
//
|
|
1029
|
+
// Adds the columns and partial unique index `publishChildWorkflowRun`
|
|
1030
|
+
// (src/storage/repositories/workflow-runs-repository.ts) needs to record a
|
|
1031
|
+
// durable child workflow run under the parent unit that spawned it, and to
|
|
1032
|
+
// make that publication idempotent per invocation
|
|
1033
|
+
// (docs/plans/specs/p3a-plan-v5-child-freeze.md §5.1).
|
|
1034
|
+
//
|
|
1035
|
+
// Disambiguation: this migration's `workflow_runs.parent_unit_id` is the
|
|
1036
|
+
// PARENT RUN's unit that spawned this CHILD RUN — a cross-run composition
|
|
1037
|
+
// edge, new in P3a. It is NOT the same concept as the pre-existing
|
|
1038
|
+
// `workflow_run_units.parent_unit_id` (migration 004, consolidated above
|
|
1039
|
+
// into migration 020's `workflow_run_units` table, migrations.ts:934),
|
|
1040
|
+
// which records MAP FAN-OUT TEMPLATE PARENTAGE — one expanded unit's row
|
|
1041
|
+
// pointing back at the map-template unit it fanned out from, WITHIN a
|
|
1042
|
+
// single run. Same column name, two tables, two different concepts. So the
|
|
1043
|
+
// two are never confused at a call site, the TypeScript repository API
|
|
1044
|
+
// deliberately never spells this one `parentUnitId`: it is always
|
|
1045
|
+
// `spawnedByUnitId` on `PublishChildWorkflowRunInput` and on every child
|
|
1046
|
+
// run row the repository returns.
|
|
1047
|
+
//
|
|
1048
|
+
// `invocation_key` is the deterministic identity of one spawn attempt
|
|
1049
|
+
// (`computeChildInvocationKey`, src/workflows/exec/child-invocation.ts).
|
|
1050
|
+
// The unique index is partial (`WHERE parent_run_id IS NOT NULL`) because a
|
|
1051
|
+
// top-level run — every run before this migration, and every run started
|
|
1052
|
+
// directly by `akm workflow run` — has no parent and no invocation key, and
|
|
1053
|
+
// must never be constrained by this uniqueness rule.
|
|
1054
|
+
{
|
|
1055
|
+
id: "023-child-workflow-runs",
|
|
1056
|
+
up: `
|
|
1057
|
+
ALTER TABLE workflow_runs ADD COLUMN parent_run_id TEXT;
|
|
1058
|
+
ALTER TABLE workflow_runs ADD COLUMN parent_unit_id TEXT;
|
|
1059
|
+
ALTER TABLE workflow_runs ADD COLUMN invocation_key TEXT;
|
|
1060
|
+
CREATE INDEX idx_workflow_runs_parent ON workflow_runs(parent_run_id);
|
|
1061
|
+
CREATE UNIQUE INDEX idx_workflow_runs_invocation_key
|
|
1062
|
+
ON workflow_runs(parent_run_id, invocation_key)
|
|
1063
|
+
WHERE parent_run_id IS NOT NULL;
|
|
1064
|
+
`,
|
|
1065
|
+
},
|
|
1066
|
+
// ── Migration 024 — workflow run outputs (P3b) ─────────────────────────────
|
|
1067
|
+
//
|
|
1068
|
+
// Adds workflow_runs.outputs_json — the resolved map of a plan's declared
|
|
1069
|
+
// outputs:, persisted once at run completion in the SAME transaction as the
|
|
1070
|
+
// final step's completion (docs/plans/specs/p3b-child-executor.md §4.3,
|
|
1071
|
+
// B-N13). NULL for every run whose plan declares no outputs: (the
|
|
1072
|
+
// overwhelming majority, including every run before this migration) and for
|
|
1073
|
+
// every run that fails or blocks before completing — never `{}`.
|
|
1074
|
+
// `WorkflowRunsRepository.setRunOutputs` is the only writer;
|
|
1075
|
+
// `src/workflows/runtime/run-outputs.ts` is the only resolver.
|
|
1076
|
+
{
|
|
1077
|
+
id: "024-workflow-run-outputs",
|
|
1078
|
+
up: `
|
|
1079
|
+
ALTER TABLE workflow_runs ADD COLUMN outputs_json TEXT;
|
|
1080
|
+
`,
|
|
1081
|
+
},
|
|
1025
1082
|
];
|
|
1026
1083
|
assertMigrationRegistry(STATE_MIGRATIONS);
|
|
1027
1084
|
function assertStateMigrationSafetyRegistry() {
|
package/dist/core/state-db.js
CHANGED
|
@@ -158,15 +158,17 @@ function descriptorAlias(handle) {
|
|
|
158
158
|
}
|
|
159
159
|
return undefined;
|
|
160
160
|
}
|
|
161
|
-
function sqliteBoundFilePath(handle
|
|
162
|
-
|
|
163
|
-
if
|
|
164
|
-
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
161
|
+
function sqliteBoundFilePath(handle) {
|
|
162
|
+
// A descriptor-backed alias (/proc/self/fd, /dev/fd) lets SQLite open the
|
|
163
|
+
// exact held inode even if its path gets swapped out from under it. It is
|
|
164
|
+
// an optimization, not the actual protection: every caller re-verifies the
|
|
165
|
+
// held identity (dev/ino/uid) immediately before and after every open that
|
|
166
|
+
// uses this path, so a plain path is safe whenever no alias is available —
|
|
167
|
+
// Windows never has one, and macOS's /dev/fd is a small fixed-size devfs
|
|
168
|
+
// table that a process holding higher fd numbers (as a bundled standalone
|
|
169
|
+
// binary routinely does) can miss entirely. Either way, a swap in that
|
|
170
|
+
// window is still caught by the surrounding identity checks.
|
|
171
|
+
return descriptorAlias(handle) ?? handle.path;
|
|
170
172
|
}
|
|
171
173
|
function closeFileIdentity(handle) {
|
|
172
174
|
try {
|
|
@@ -301,16 +303,16 @@ function createHistoricalStateSafetyCopy(source, migrationId) {
|
|
|
301
303
|
// The migration connection already holds BEGIN IMMEDIATE. A distinct
|
|
302
304
|
// read-only connection bound to the held source inode can snapshot the
|
|
303
305
|
// committed WAL view without trying to VACUUM from inside that transaction.
|
|
304
|
-
reader = openDatabase(sqliteBoundFilePath(source
|
|
306
|
+
reader = openDatabase(sqliteBoundFilePath(source), { readonly: true });
|
|
305
307
|
assertStateDatabaseSource(source);
|
|
306
|
-
reader.prepare("VACUUM INTO ?").run(sqliteBoundFilePath(reservation
|
|
308
|
+
reader.prepare("VACUUM INTO ?").run(sqliteBoundFilePath(reservation));
|
|
307
309
|
assertStateDatabaseSource(source);
|
|
308
310
|
reader.close();
|
|
309
311
|
reader = undefined;
|
|
310
312
|
assertOwnedFileReservation(reservation, "Reserved state.db safety copy");
|
|
311
313
|
fs.fsyncSync(reservation.fd);
|
|
312
314
|
assertOwnedFileReservation(reservation, "Reserved state.db safety copy");
|
|
313
|
-
const verified = openDatabase(sqliteBoundFilePath(reservation
|
|
315
|
+
const verified = openDatabase(sqliteBoundFilePath(reservation), {
|
|
314
316
|
readonly: true,
|
|
315
317
|
});
|
|
316
318
|
try {
|
|
@@ -403,7 +405,7 @@ export function openStateDatabase(dbPath, options) {
|
|
|
403
405
|
if (!freshReservation) {
|
|
404
406
|
existingSource = openExistingStateDatabaseSource(resolvedPath);
|
|
405
407
|
assertStateDatabaseSource(existingSource);
|
|
406
|
-
const preflight = openDatabase(sqliteBoundFilePath(existingSource
|
|
408
|
+
const preflight = openDatabase(sqliteBoundFilePath(existingSource), {
|
|
407
409
|
readonly: true,
|
|
408
410
|
});
|
|
409
411
|
try {
|
|
@@ -425,7 +427,7 @@ export function openStateDatabase(dbPath, options) {
|
|
|
425
427
|
if (boundSource)
|
|
426
428
|
assertStateDatabaseSource(boundSource);
|
|
427
429
|
openedDb = openManagedDatabase({
|
|
428
|
-
path: boundSource ? sqliteBoundFilePath(boundSource
|
|
430
|
+
path: boundSource ? sqliteBoundFilePath(boundSource) : resolvedPath,
|
|
429
431
|
pragmas: { dataDir: path.dirname(resolvedPath) },
|
|
430
432
|
init: (db) => {
|
|
431
433
|
if (boundSource)
|