akm-cli 0.9.0-rc.0 → 0.9.0-rc.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 +339 -2
- package/SECURITY.md +23 -24
- package/dist/assets/help/help-improve.md +10 -10
- package/dist/assets/hints/cli-hints-full.md +44 -10
- package/dist/assets/hints/cli-hints-short.md +6 -2
- package/dist/assets/{profiles → improve-strategies}/default.json +1 -0
- package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +1 -1
- package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +2 -3
- package/dist/assets/{profiles → improve-strategies}/reflect-distill.json +3 -4
- package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
- package/dist/assets/stash-skeleton/README.md +28 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +12 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +11 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +9 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +7 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +7 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +98 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +63 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +127 -0
- package/dist/assets/tasks/core/backup.yml +1 -0
- package/dist/assets/tasks/core/extract.yml +1 -0
- package/dist/assets/tasks/core/improve.yml +1 -0
- package/dist/assets/tasks/core/index-refresh.yml +1 -0
- package/dist/assets/tasks/core/sync.yml +1 -0
- package/dist/assets/tasks/core/version-check.yml +1 -0
- package/dist/assets/tasks/graph-refresh-weekly.yml +4 -4
- package/dist/assets/templates/html/health.html +5 -1
- package/dist/cli/config-migrate.js +31 -138
- package/dist/cli/config-validate.js +10 -8
- package/dist/cli.js +48 -14
- package/dist/commands/agent/agent-dispatch.js +17 -16
- package/dist/commands/agent/agent-support.js +0 -24
- package/dist/commands/agent/contribute-cli.js +5 -15
- package/dist/commands/backup-cli.js +54 -0
- package/dist/commands/config-cli.js +45 -159
- package/dist/commands/env/env-binding.js +95 -0
- package/dist/commands/env/env-cli.js +8 -65
- package/dist/commands/env/secret.js +8 -5
- package/dist/commands/health/checks.js +130 -83
- package/dist/commands/health/html-report.js +4 -0
- package/dist/commands/health/improve-metrics.js +30 -32
- package/dist/commands/health/llm-usage.js +19 -19
- package/dist/commands/health/md-report.js +4 -0
- package/dist/commands/health/metrics.js +2 -1
- package/dist/commands/health/surfaces.js +5 -4
- package/dist/commands/health.js +1 -1
- package/dist/commands/improve/consolidate/chunking.js +2 -2
- package/dist/commands/improve/consolidate.js +28 -25
- package/dist/commands/improve/distill/promote-memory.js +5 -12
- package/dist/commands/improve/distill/quality-gate.js +5 -7
- package/dist/commands/improve/distill.js +16 -5
- package/dist/commands/improve/eligibility.js +22 -12
- package/dist/commands/improve/extract-cli.js +47 -19
- package/dist/commands/improve/extract.js +110 -62
- package/dist/commands/improve/improve-cli.js +38 -16
- package/dist/commands/improve/improve-result-file.js +30 -24
- package/dist/commands/improve/improve-strategies.js +137 -0
- package/dist/commands/improve/improve.js +60 -30
- package/dist/commands/improve/locks.js +66 -45
- package/dist/commands/improve/loop-stages.js +75 -33
- package/dist/commands/improve/memory/memory-belief.js +79 -7
- package/dist/commands/improve/memory/memory-contradiction-detect.js +12 -4
- package/dist/commands/improve/preparation.js +71 -73
- package/dist/commands/improve/procedural.js +3 -2
- package/dist/commands/improve/recombine.js +2 -1
- package/dist/commands/improve/reflect.js +119 -214
- package/dist/commands/improve/shared.js +11 -5
- package/dist/commands/lint/base-linter.js +152 -42
- package/dist/commands/mv-cli.js +809 -0
- package/dist/commands/proposal/proposal-cli.js +18 -8
- package/dist/commands/proposal/propose.js +64 -69
- package/dist/commands/read/knowledge.js +436 -4
- package/dist/commands/read/remember-cli.js +39 -2
- package/dist/commands/read/search-cli.js +6 -1
- package/dist/commands/registry-cli.js +29 -14
- package/dist/commands/remember.js +2 -0
- package/dist/commands/sources/init.js +13 -14
- package/dist/commands/sources/migration-help.js +7 -4
- package/dist/commands/sources/schema-repair.js +2 -4
- package/dist/commands/sources/source-add.js +62 -73
- package/dist/commands/sources/source-manage.js +50 -46
- package/dist/commands/sources/stash-cli.js +41 -4
- package/dist/commands/tasks/default-tasks.js +12 -12
- package/dist/commands/tasks/tasks-cli.js +7 -3
- package/dist/commands/tasks/tasks.js +113 -18
- package/dist/commands/wiki-cli.js +9 -10
- package/dist/commands/workflow-cli.js +276 -12
- package/dist/core/asset/asset-spec.js +58 -1
- package/dist/core/asset/frontmatter.js +12 -2
- package/dist/core/common.js +5 -3
- package/dist/core/config/config-io.js +28 -17
- package/dist/core/config/config-schema.js +379 -66
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config-version.js +29 -0
- package/dist/core/config/config-walker.js +98 -27
- package/dist/core/config/config.js +132 -266
- package/dist/core/config/deep-merge.js +41 -0
- package/dist/core/config/engine-semantics.js +32 -0
- package/dist/core/errors.js +2 -2
- package/dist/core/extra-params.js +61 -0
- package/dist/core/file-lock.js +201 -56
- package/dist/core/improve-result.js +178 -0
- package/dist/core/json-schema.js +142 -0
- package/dist/core/maintenance-barrier.js +119 -0
- package/dist/core/migration-backup.js +416 -0
- package/dist/core/paths.js +3 -0
- package/dist/core/redaction.js +358 -0
- package/dist/core/state/migrations.js +17 -2
- package/dist/core/state-db.js +44 -1
- package/dist/indexer/db/db.js +118 -2
- package/dist/indexer/graph/graph-extraction.js +28 -16
- package/dist/indexer/index-writer-lock.js +31 -24
- package/dist/indexer/index-written-assets.js +15 -6
- package/dist/indexer/indexer.js +47 -2
- package/dist/indexer/passes/memory-inference.js +10 -6
- package/dist/indexer/passes/metadata.js +250 -0
- package/dist/indexer/search/db-search.js +111 -44
- package/dist/indexer/search/fts-query.js +41 -0
- package/dist/indexer/search/ranking-contributors.js +48 -0
- package/dist/indexer/search/ranking.js +36 -23
- package/dist/indexer/search/search-fields.js +11 -1
- package/dist/indexer/walk/matchers.js +39 -0
- package/dist/integrations/agent/builder-shared.js +7 -0
- package/dist/integrations/agent/builders.js +5 -50
- package/dist/integrations/agent/config.js +3 -143
- package/dist/integrations/agent/detect.js +17 -2
- package/dist/integrations/agent/engine-resolution.js +202 -0
- package/dist/integrations/agent/index.js +1 -2
- package/dist/integrations/agent/model-aliases.js +16 -2
- package/dist/integrations/agent/profiles.js +36 -62
- package/dist/integrations/agent/runner-dispatch.js +91 -4
- package/dist/integrations/agent/runner.js +76 -207
- package/dist/integrations/agent/spawn.js +141 -20
- package/dist/integrations/harnesses/aider/agent-builder.js +112 -0
- package/dist/integrations/harnesses/aider/index.js +57 -0
- package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
- package/dist/integrations/harnesses/amazonq/agent-builder.js +152 -0
- package/dist/integrations/harnesses/amazonq/index.js +58 -0
- package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
- package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
- package/dist/integrations/harnesses/claude/index.js +25 -25
- package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
- package/dist/integrations/harnesses/codex/agent-builder.js +136 -0
- package/dist/integrations/harnesses/codex/index.js +62 -0
- package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
- package/dist/integrations/harnesses/copilot/agent-builder.js +121 -0
- package/dist/integrations/harnesses/copilot/index.js +59 -0
- package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
- package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
- package/dist/integrations/harnesses/gemini/index.js +59 -0
- package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
- package/dist/integrations/harnesses/index.js +27 -28
- package/dist/integrations/harnesses/opencode/agent-builder.js +2 -3
- package/dist/integrations/harnesses/opencode/index.js +15 -22
- package/dist/integrations/harnesses/opencode-sdk/harness.js +60 -0
- package/dist/integrations/harnesses/opencode-sdk/index.js +8 -32
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
- package/dist/integrations/harnesses/openhands/agent-builder.js +125 -0
- package/dist/integrations/harnesses/openhands/index.js +57 -0
- package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
- package/dist/integrations/harnesses/pi/agent-builder.js +103 -0
- package/dist/integrations/harnesses/pi/index.js +57 -0
- package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
- package/dist/integrations/harnesses/types.js +8 -32
- package/dist/integrations/lockfile.js +32 -21
- package/dist/integrations/session-logs/index.js +24 -11
- package/dist/llm/client.js +48 -14
- package/dist/llm/feature-gate.js +15 -47
- package/dist/llm/graph-extract.js +1 -1
- package/dist/llm/index-passes.js +8 -42
- package/dist/llm/memory-infer-impl.js +1 -1
- package/dist/llm/usage-persist.js +4 -0
- package/dist/llm/usage-telemetry.js +35 -5
- package/dist/output/renderers.js +3 -2
- package/dist/output/shapes/helpers.js +2 -1
- package/dist/output/shapes/passthrough.js +6 -0
- package/dist/output/text/helpers.js +215 -2
- package/dist/output/text/workflow.js +3 -1
- package/dist/schemas/akm-config.json +16638 -0
- package/dist/schemas/akm-task.json +87 -0
- package/dist/schemas/akm-workflow.json +372 -0
- package/dist/scripts/migrate-storage.js +10944 -8801
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9247 -350
- package/dist/setup/detected-engines.js +142 -0
- package/dist/setup/engine-config.js +89 -0
- package/dist/setup/setup.js +236 -132
- package/dist/setup/steps/connection.js +61 -32
- package/dist/setup/steps/platforms.js +4 -4
- package/dist/setup/steps.js +3 -2
- package/dist/storage/database.js +13 -1
- package/dist/storage/engines/sqlite-migrations.js +1 -0
- package/dist/storage/repositories/improve-runs-repository.js +5 -5
- package/dist/storage/repositories/task-history-repository.js +78 -0
- package/dist/storage/repositories/workflow-runs-repository.js +190 -1
- package/dist/tasks/parser.js +138 -52
- package/dist/tasks/runner.js +71 -75
- package/dist/tasks/schema.js +1 -1
- package/dist/tasks/validator.js +11 -6
- package/dist/text-import-hook.mjs +1 -1
- package/dist/wiki/wiki.js +9 -8
- package/dist/workflows/authoring/authoring.js +123 -10
- package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
- package/dist/workflows/cli.js +4 -0
- package/dist/workflows/concurrency-policy.js +15 -0
- package/dist/workflows/db.js +200 -13
- package/dist/workflows/exec/brief.js +478 -0
- package/dist/workflows/exec/frozen-judge.js +47 -0
- package/dist/workflows/exec/native-executor.js +1034 -0
- package/dist/workflows/exec/param-secrets.js +115 -0
- package/dist/workflows/exec/report.js +1355 -0
- package/dist/workflows/exec/run-workflow.js +609 -0
- package/dist/workflows/exec/scheduler.js +71 -0
- package/dist/workflows/exec/step-work.js +1212 -0
- package/dist/workflows/exec/unit-writer.js +23 -0
- package/dist/workflows/exec/watch.js +116 -0
- package/dist/workflows/exec/worktree.js +171 -0
- package/dist/workflows/ir/compile.js +375 -0
- package/dist/workflows/ir/freeze.js +243 -0
- package/dist/workflows/ir/params.js +54 -0
- package/dist/workflows/ir/plan-hash.js +68 -0
- package/dist/workflows/ir/schema.js +545 -0
- package/dist/workflows/parser.js +10 -1
- package/dist/workflows/program/expressions.js +369 -0
- package/dist/workflows/program/parser.js +869 -0
- package/dist/workflows/program/project.js +104 -0
- package/dist/workflows/program/schema.js +54 -0
- package/dist/workflows/renderer.js +82 -5
- package/dist/workflows/resource-limits.js +20 -0
- package/dist/workflows/runtime/agent-identity.js +59 -14
- package/dist/workflows/runtime/plan-classifier.js +187 -0
- package/dist/workflows/runtime/runs.js +246 -69
- package/dist/workflows/runtime/unit-checkin.js +45 -0
- package/dist/workflows/runtime/workflow-asset-loader.js +42 -1
- package/dist/workflows/validate-summary.js +24 -3
- package/dist/workflows/validator.js +26 -1
- package/docs/data-and-telemetry.md +4 -3
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +5 -4
- package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
- package/docs/migration/v0.8-to-v0.9.md +401 -0
- package/package.json +4 -2
- package/schemas/akm-config.json +16638 -0
- package/schemas/akm-task.json +87 -0
- package/schemas/akm-workflow.json +372 -0
- package/dist/commands/improve/improve-profiles.js +0 -168
- package/dist/core/config/config-migration.js +0 -602
- package/dist/core/deep-merge.js +0 -38
- package/dist/llm/call-ai.js +0 -62
- package/dist/setup/legacy-config.js +0 -106
- package/docs/README.md +0 -104
- /package/dist/assets/{profiles → improve-strategies}/catchup.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/consolidate.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/frequent.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/memory-focus.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/quick.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/recombine-only.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/synthesize.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/thorough.json +0 -0
|
@@ -17,7 +17,6 @@ import { loadConfig } from "../../core/config/config.js";
|
|
|
17
17
|
import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
|
|
18
18
|
import { getTaskHistoryDir, getTaskLogDir } from "../../core/paths.js";
|
|
19
19
|
import { commitWriteTargetBoundary, deleteAssetFromSource, resolveWriteTarget, writeAssetToSource, } from "../../core/write-source.js";
|
|
20
|
-
import { listAgentProfileNames } from "../../integrations/agent/index.js";
|
|
21
20
|
import { resolveAssetPath } from "../../sources/resolve.js";
|
|
22
21
|
import { backendNameForPlatform, selectBackend } from "../../tasks/backends/index.js";
|
|
23
22
|
import { parseTaskDocument } from "../../tasks/parser.js";
|
|
@@ -25,7 +24,7 @@ import { resolveAkmInvocation } from "../../tasks/resolve-akm-bin.js";
|
|
|
25
24
|
import { exitCodeForStatus, readTaskHistory, runTask } from "../../tasks/runner.js";
|
|
26
25
|
import { parseSchedule, SCHEDULE_SUPPORTED_SUBSET_HINT, translateToCron } from "../../tasks/schedule.js";
|
|
27
26
|
import { validateTaskDocument } from "../../tasks/validator.js";
|
|
28
|
-
import {
|
|
27
|
+
import { resolveImproveStrategy } from "../improve/improve-strategies.js";
|
|
29
28
|
export async function akmTasksAdd(input) {
|
|
30
29
|
const id = normaliseTaskId(input.id);
|
|
31
30
|
const hasCommand = input.command !== undefined &&
|
|
@@ -36,6 +35,12 @@ export async function akmTasksAdd(input) {
|
|
|
36
35
|
if (targetCount !== 1) {
|
|
37
36
|
throw new UsageError("Pass exactly one of --workflow <ref>, --prompt <asset-ref|./file.md|text>, or --command <shell-command>.", "INVALID_FLAG_VALUE");
|
|
38
37
|
}
|
|
38
|
+
if (input.workflow && (input.engine !== undefined || input.model !== undefined || input.timeoutMs !== undefined)) {
|
|
39
|
+
throw new UsageError("Workflow tasks accept only --params; engine, model, and timeout are prompt-task fields.", "INVALID_FLAG_VALUE");
|
|
40
|
+
}
|
|
41
|
+
if (hasCommand && (input.engine !== undefined || input.model !== undefined)) {
|
|
42
|
+
throw new UsageError("Command tasks accept --timeout-ms but not --engine or --model.", "INVALID_FLAG_VALUE");
|
|
43
|
+
}
|
|
39
44
|
// Validate the schedule for the active backend before writing anything.
|
|
40
45
|
const backend = backendNameForPlatform();
|
|
41
46
|
parseSchedule(input.schedule, backend);
|
|
@@ -56,7 +61,9 @@ export async function akmTasksAdd(input) {
|
|
|
56
61
|
workflow: input.workflow,
|
|
57
62
|
prompt: input.prompt,
|
|
58
63
|
command: input.command,
|
|
59
|
-
|
|
64
|
+
engine: input.engine,
|
|
65
|
+
model: input.model,
|
|
66
|
+
timeoutMs: input.timeoutMs,
|
|
60
67
|
params: input.params,
|
|
61
68
|
name: input.name,
|
|
62
69
|
description: input.description,
|
|
@@ -137,11 +144,12 @@ export async function akmTasksList() {
|
|
|
137
144
|
const stashDir = resolveStashDir();
|
|
138
145
|
const typeRoot = path.join(stashDir, "tasks");
|
|
139
146
|
if (!fs.existsSync(typeRoot))
|
|
140
|
-
return { tasks: [] };
|
|
147
|
+
return { tasks: [], stale: [] };
|
|
141
148
|
const entries = fs.readdirSync(typeRoot);
|
|
142
149
|
warnLegacyMdTaskFiles(typeRoot);
|
|
143
150
|
const files = entries.filter((f) => f.endsWith(".yml"));
|
|
144
151
|
const tasks = [];
|
|
152
|
+
const stale = [];
|
|
145
153
|
for (const file of files) {
|
|
146
154
|
const id = file.slice(0, -4);
|
|
147
155
|
const filePath = path.join(typeRoot, file);
|
|
@@ -149,7 +157,9 @@ export async function akmTasksList() {
|
|
|
149
157
|
try {
|
|
150
158
|
task = parseTaskDocument({ yaml: fs.readFileSync(filePath, "utf8"), filePath, id });
|
|
151
159
|
}
|
|
152
|
-
catch {
|
|
160
|
+
catch (err) {
|
|
161
|
+
if (isStaleTaskError(err))
|
|
162
|
+
stale.push(id);
|
|
153
163
|
continue; // skip malformed files; `akm tasks show <id>` will surface the error
|
|
154
164
|
}
|
|
155
165
|
tasks.push({
|
|
@@ -165,7 +175,9 @@ export async function akmTasksList() {
|
|
|
165
175
|
tags: task.tags,
|
|
166
176
|
});
|
|
167
177
|
}
|
|
168
|
-
|
|
178
|
+
if (stale.length > 0)
|
|
179
|
+
warnStaleTaskFiles(stale);
|
|
180
|
+
return { tasks, stale };
|
|
169
181
|
}
|
|
170
182
|
export async function akmTasksShow(id) {
|
|
171
183
|
const normalised = normaliseTaskId(id);
|
|
@@ -234,6 +246,9 @@ export async function akmTasksSetEnabled(id, enabled) {
|
|
|
234
246
|
warnLegacyMdTaskFiles(typeRoot);
|
|
235
247
|
const filePath = await resolveAssetPath(stashDir, "task", normalised);
|
|
236
248
|
const yaml = fs.readFileSync(filePath, "utf8");
|
|
249
|
+
// Parse before writing so stale v1 tasks are diagnosed without changing the
|
|
250
|
+
// source file or its installed scheduler entry.
|
|
251
|
+
parseTaskDocument({ yaml, filePath, id: normalised });
|
|
237
252
|
const updated = setEnabledInYaml(yaml, enabled);
|
|
238
253
|
const ref = taskAssetRef(normalised);
|
|
239
254
|
await writeAssetToSource(target.source, target.config, ref, updated);
|
|
@@ -374,16 +389,15 @@ export async function akmTasksDoctor() {
|
|
|
374
389
|
}
|
|
375
390
|
const backend = backendNameForPlatform();
|
|
376
391
|
const config = loadConfig();
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
const profiles = config.profiles?.agent ? Object.keys(config.profiles.agent) : listAgentProfileNames(config);
|
|
392
|
+
const defaultEngine = config.defaults?.engine;
|
|
393
|
+
const engines = Object.keys(config.engines ?? {});
|
|
380
394
|
// §6.1: surface the effective triage settings for the default improve
|
|
381
|
-
//
|
|
382
|
-
const
|
|
383
|
-
const triage =
|
|
395
|
+
// strategy. The struct is a fixed shape, so this is a deliberate addition.
|
|
396
|
+
const improveStrategyName = typeof config.defaults?.improveStrategy === "string" ? config.defaults.improveStrategy : "default";
|
|
397
|
+
const triage = resolveImproveStrategy(config.defaults?.improveStrategy, config).config.processes?.triage;
|
|
384
398
|
const improveTriage = triage
|
|
385
399
|
? {
|
|
386
|
-
|
|
400
|
+
defaultStrategy: improveStrategyName,
|
|
387
401
|
enabled: triage.enabled === true,
|
|
388
402
|
applyMode: triage.applyMode ?? "queue",
|
|
389
403
|
policy: triage.policy ?? "personal-stash",
|
|
@@ -394,7 +408,9 @@ export async function akmTasksDoctor() {
|
|
|
394
408
|
akm: invocation,
|
|
395
409
|
logDir: getTaskLogDir(),
|
|
396
410
|
historyDir: getTaskHistoryDir(),
|
|
397
|
-
|
|
411
|
+
engine: { defaultEngine, available: engines },
|
|
412
|
+
stale: collectStaleTaskIds(),
|
|
413
|
+
staleGeneratedCommands: collectStaleGeneratedCommands(),
|
|
398
414
|
scheduleSubset: SCHEDULE_SUPPORTED_SUBSET_HINT,
|
|
399
415
|
warnings,
|
|
400
416
|
...(improveTriage ? { improveTriage } : {}),
|
|
@@ -421,7 +437,7 @@ function resolveTaskWriteTarget() {
|
|
|
421
437
|
return resolveWriteTarget(loadConfig());
|
|
422
438
|
}
|
|
423
439
|
function renderTaskYaml(input) {
|
|
424
|
-
const obj = { schedule: input.schedule };
|
|
440
|
+
const obj = { version: 2, schedule: input.schedule, enabled: input.enabled };
|
|
425
441
|
if (input.workflow) {
|
|
426
442
|
obj.workflow = input.workflow;
|
|
427
443
|
if (input.params) {
|
|
@@ -430,16 +446,21 @@ function renderTaskYaml(input) {
|
|
|
430
446
|
}
|
|
431
447
|
else if (input.prompt) {
|
|
432
448
|
obj.prompt = input.prompt;
|
|
433
|
-
if (input.
|
|
434
|
-
obj.
|
|
449
|
+
if (input.engine)
|
|
450
|
+
obj.engine = input.engine;
|
|
451
|
+
if (input.model)
|
|
452
|
+
obj.model = input.model;
|
|
453
|
+
if (input.timeoutMs !== undefined)
|
|
454
|
+
obj.timeoutMs = input.timeoutMs;
|
|
435
455
|
}
|
|
436
456
|
else if (input.command !== undefined) {
|
|
437
457
|
// Emit a string when given a string, an array when given an array. The
|
|
438
458
|
// parser accepts both forms; preserving the caller's shape keeps the YAML
|
|
439
459
|
// ergonomic for humans editing the file later.
|
|
440
460
|
obj.command = input.command;
|
|
461
|
+
if (input.timeoutMs !== undefined)
|
|
462
|
+
obj.timeoutMs = input.timeoutMs;
|
|
441
463
|
}
|
|
442
|
-
obj.enabled = input.enabled;
|
|
443
464
|
if (input.name)
|
|
444
465
|
obj.name = input.name;
|
|
445
466
|
if (input.description)
|
|
@@ -450,6 +471,80 @@ function renderTaskYaml(input) {
|
|
|
450
471
|
obj.tags = input.tags;
|
|
451
472
|
return yamlStringify(obj);
|
|
452
473
|
}
|
|
474
|
+
function isStaleTaskError(err) {
|
|
475
|
+
return err instanceof UsageError && err.code === "TASK_SCHEMA_VERSION_UNSUPPORTED";
|
|
476
|
+
}
|
|
477
|
+
function warnStaleTaskFiles(ids) {
|
|
478
|
+
process.stderr.write(`WARNING: ${ids.length} task file(s) use retired task schema v1 and were not loaded.\n` +
|
|
479
|
+
` Rewrite them with version: 2 and replace profile with engine. See docs/migration/v0.8-to-v0.9.md#task-assets.\n` +
|
|
480
|
+
` Affected: ${ids.map((id) => `tasks/${id}.yml`).join(", ")}\n`);
|
|
481
|
+
}
|
|
482
|
+
function collectStaleTaskIds() {
|
|
483
|
+
const typeRoot = path.join(resolveStashDir(), "tasks");
|
|
484
|
+
if (!fs.existsSync(typeRoot))
|
|
485
|
+
return [];
|
|
486
|
+
const stale = [];
|
|
487
|
+
for (const file of fs.readdirSync(typeRoot)) {
|
|
488
|
+
if (!file.endsWith(".yml"))
|
|
489
|
+
continue;
|
|
490
|
+
const id = file.slice(0, -4);
|
|
491
|
+
try {
|
|
492
|
+
parseTaskDocument({
|
|
493
|
+
yaml: fs.readFileSync(path.join(typeRoot, file), "utf8"),
|
|
494
|
+
filePath: path.join(typeRoot, file),
|
|
495
|
+
id,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
catch (err) {
|
|
499
|
+
if (isStaleTaskError(err))
|
|
500
|
+
stale.push(id);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return stale;
|
|
504
|
+
}
|
|
505
|
+
const STALE_GENERATED_COMMANDS = {
|
|
506
|
+
"akm-improve-frequent": {
|
|
507
|
+
command: "akm improve --profile frequent --auto-accept safe",
|
|
508
|
+
replacement: "akm improve --strategy frequent --auto-accept safe",
|
|
509
|
+
},
|
|
510
|
+
"akm-improve-consolidate": {
|
|
511
|
+
command: "akm improve --profile consolidate --auto-accept safe",
|
|
512
|
+
replacement: "akm improve --strategy consolidate --auto-accept safe",
|
|
513
|
+
},
|
|
514
|
+
"akm-improve-nightly": {
|
|
515
|
+
command: "akm improve --profile thorough --auto-accept safe",
|
|
516
|
+
replacement: "akm improve --strategy thorough --auto-accept safe",
|
|
517
|
+
},
|
|
518
|
+
"akm-improve-catchup": {
|
|
519
|
+
command: "akm improve --profile catchup --auto-accept safe",
|
|
520
|
+
replacement: "akm improve --strategy catchup --auto-accept safe",
|
|
521
|
+
},
|
|
522
|
+
"akm-graph-refresh-weekly": {
|
|
523
|
+
command: "akm improve --profile graph-refresh --auto-accept safe",
|
|
524
|
+
replacement: "akm improve --strategy graph-refresh --auto-accept safe",
|
|
525
|
+
},
|
|
526
|
+
};
|
|
527
|
+
function collectStaleGeneratedCommands() {
|
|
528
|
+
const typeRoot = path.join(resolveStashDir(), "tasks");
|
|
529
|
+
if (!fs.existsSync(typeRoot))
|
|
530
|
+
return [];
|
|
531
|
+
const stale = [];
|
|
532
|
+
for (const [id, expected] of Object.entries(STALE_GENERATED_COMMANDS)) {
|
|
533
|
+
const filePath = path.join(typeRoot, `${id}.yml`);
|
|
534
|
+
if (!fs.existsSync(filePath))
|
|
535
|
+
continue;
|
|
536
|
+
try {
|
|
537
|
+
const task = parseTaskDocument({ yaml: fs.readFileSync(filePath, "utf8"), filePath, id });
|
|
538
|
+
if (task.target.kind === "command" && task.target.cmd.join(" ") === expected.command) {
|
|
539
|
+
stale.push({ id, replacement: expected.replacement });
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
// Stale-schema reporting owns files that cannot be parsed.
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return stale;
|
|
547
|
+
}
|
|
453
548
|
function parseJsonObjectArg(raw) {
|
|
454
549
|
let parsed;
|
|
455
550
|
try {
|
|
@@ -220,13 +220,13 @@ const wikiLintCommand = defineCommand({
|
|
|
220
220
|
const wikiIngestCommand = defineJsonCommand({
|
|
221
221
|
meta: {
|
|
222
222
|
name: "ingest",
|
|
223
|
-
description: "Dispatch an agent to execute the ingest workflow for this wiki. Uses --
|
|
223
|
+
description: "Dispatch an agent to execute the ingest workflow for this wiki. Uses --engine or config.defaults.engine.",
|
|
224
224
|
},
|
|
225
225
|
args: {
|
|
226
226
|
name: { type: "positional", description: "Wiki name", required: true },
|
|
227
|
-
|
|
227
|
+
engine: {
|
|
228
228
|
type: "string",
|
|
229
|
-
description: "Agent
|
|
229
|
+
description: "Agent engine to use (default: config.defaults.engine).",
|
|
230
230
|
},
|
|
231
231
|
model: {
|
|
232
232
|
type: "string",
|
|
@@ -239,17 +239,15 @@ const wikiIngestCommand = defineJsonCommand({
|
|
|
239
239
|
const stashDir = resolveStashDir();
|
|
240
240
|
const built = buildIngestWorkflow(stashDir, args.name);
|
|
241
241
|
const config = loadConfig();
|
|
242
|
-
const
|
|
243
|
-
if (!
|
|
244
|
-
throw new UsageError("akm wiki ingest requires an agent
|
|
242
|
+
const engine = getStringArg(args, "engine") ?? config.defaults?.engine;
|
|
243
|
+
if (!engine) {
|
|
244
|
+
throw new UsageError("akm wiki ingest requires an agent engine. Pass --engine <name> or set defaults.engine in config.", "MISSING_REQUIRED_ARGUMENT", "Available engines are listed under engines in your config. Run `akm config get engines` to inspect.");
|
|
245
245
|
}
|
|
246
246
|
const timeoutMs = parsePositiveIntFlag(args["timeout-ms"], "--timeout-ms");
|
|
247
247
|
const model = getStringArg(args, "model");
|
|
248
|
-
const { getDefaultLlmConfig } = await import("../core/config/config.js");
|
|
249
248
|
const dispatchResult = await akmAgentDispatch({
|
|
250
|
-
|
|
249
|
+
engine,
|
|
251
250
|
agentConfig: config,
|
|
252
|
-
llmConfig: getDefaultLlmConfig(config),
|
|
253
251
|
prompt: built.workflow,
|
|
254
252
|
dispatch: {
|
|
255
253
|
prompt: built.workflow,
|
|
@@ -258,11 +256,12 @@ const wikiIngestCommand = defineJsonCommand({
|
|
|
258
256
|
...(timeoutMs !== undefined && Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
|
|
259
257
|
});
|
|
260
258
|
output("wiki-ingest", {
|
|
259
|
+
schemaVersion: 2,
|
|
261
260
|
wiki: built.wiki,
|
|
262
261
|
path: built.path,
|
|
263
262
|
schemaPath: built.schemaPath,
|
|
264
263
|
dispatched: true,
|
|
265
|
-
|
|
264
|
+
engine,
|
|
266
265
|
agentResult: dispatchResult,
|
|
267
266
|
});
|
|
268
267
|
},
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* `looksLikeWorkflowRunId` and `resolveWorkflowFilePath` move with the family.
|
|
13
13
|
*/
|
|
14
14
|
import { defineCommand } from "citty";
|
|
15
|
+
import { getStringArg } from "../cli/parse-args.js";
|
|
15
16
|
import { defineJsonCommand, output, runWithJsonErrors } from "../cli/shared.js";
|
|
16
17
|
import { assertFlatAssetName, combineCreatePath, normalizeCreateSubPath } from "../core/asset/asset-create.js";
|
|
17
18
|
import { parseAssetRef } from "../core/asset/asset-ref.js";
|
|
@@ -22,8 +23,9 @@ import { resolveSourceEntries } from "../indexer/search/search-source.js";
|
|
|
22
23
|
import { hasBooleanFlag } from "../output/context.js";
|
|
23
24
|
import { resolveSourcesForOrigin } from "../registry/origin-resolve.js";
|
|
24
25
|
import { resolveAssetPath } from "../sources/resolve.js";
|
|
25
|
-
import { createWorkflowAsset, formatWorkflowErrors, getWorkflowTemplate, validateWorkflowSource, } from "../workflows/authoring/authoring.js";
|
|
26
|
+
import { createWorkflowAsset, formatWorkflowErrors, getWorkflowProgramTemplate, getWorkflowTemplate, validateWorkflowProgramSource, validateWorkflowSource, } from "../workflows/authoring/authoring.js";
|
|
26
27
|
import { hasWorkflowSubcommand, parseWorkflowJsonObject, parseWorkflowStepState, WORKFLOW_STEP_STATES, } from "../workflows/cli.js";
|
|
28
|
+
import { isWorkflowProgramPath } from "../workflows/program/project.js";
|
|
27
29
|
import { abandonWorkflowRun, completeWorkflowStep, getNextWorkflowStep, getWorkflowStatus, listWorkflowRuns, resumeWorkflowRun, startWorkflowRun, } from "../workflows/runtime/runs.js";
|
|
28
30
|
const workflowStartCommand = defineJsonCommand({
|
|
29
31
|
meta: {
|
|
@@ -140,9 +142,16 @@ const workflowStatusCommand = defineJsonCommand({
|
|
|
140
142
|
},
|
|
141
143
|
args: {
|
|
142
144
|
target: { type: "positional", description: "Workflow run id or workflow ref (workflow:<name>)", required: true },
|
|
145
|
+
units: {
|
|
146
|
+
type: "boolean",
|
|
147
|
+
description: "Also list per-unit rows from the run journal (unit id, status, failure_reason, and any result/error " +
|
|
148
|
+
"diagnostic text). Diagnostics only — step evidence stays deterministic and is unaffected (#22).",
|
|
149
|
+
default: false,
|
|
150
|
+
},
|
|
143
151
|
},
|
|
144
152
|
async run({ args }) {
|
|
145
153
|
const target = args.target;
|
|
154
|
+
const includeUnits = args.units === true;
|
|
146
155
|
// Check if target looks like a workflow ref
|
|
147
156
|
const parsed = (() => {
|
|
148
157
|
try {
|
|
@@ -161,11 +170,11 @@ const workflowStatusCommand = defineJsonCommand({
|
|
|
161
170
|
const mostRecent = runs[0];
|
|
162
171
|
if (!mostRecent)
|
|
163
172
|
throw new NotFoundError(`No workflow runs found for ${ref}`, "WORKFLOW_NOT_FOUND");
|
|
164
|
-
const result = await getWorkflowStatus(mostRecent.id);
|
|
173
|
+
const result = await getWorkflowStatus(mostRecent.id, { includeUnits });
|
|
165
174
|
output("workflow-status", result);
|
|
166
175
|
}
|
|
167
176
|
else {
|
|
168
|
-
const result = await getWorkflowStatus(target);
|
|
177
|
+
const result = await getWorkflowStatus(target, { includeUnits });
|
|
169
178
|
output("workflow-status", result);
|
|
170
179
|
}
|
|
171
180
|
},
|
|
@@ -187,19 +196,22 @@ const workflowListCommand = defineJsonCommand({
|
|
|
187
196
|
const workflowCreateCommand = defineJsonCommand({
|
|
188
197
|
meta: {
|
|
189
198
|
name: "create",
|
|
190
|
-
description: "Create a workflow markdown document
|
|
199
|
+
description: "Create a workflow in the working stash (markdown document by default; a .yaml/.yml name writes a YAML program)",
|
|
191
200
|
},
|
|
192
201
|
args: {
|
|
193
202
|
name: {
|
|
194
203
|
type: "positional",
|
|
195
|
-
description: "Workflow name (flat, no '/'; use --path for a subdirectory)",
|
|
204
|
+
description: "Workflow name (flat, no '/'; use --path for a subdirectory). A .yaml/.yml suffix creates a YAML program.",
|
|
196
205
|
required: true,
|
|
197
206
|
},
|
|
198
207
|
path: {
|
|
199
208
|
type: "string",
|
|
200
209
|
description: "Relative subdirectory under workflows/ to place the workflow in (e.g. 'release'). The filename comes from the name.",
|
|
201
210
|
},
|
|
202
|
-
from: {
|
|
211
|
+
from: {
|
|
212
|
+
type: "string",
|
|
213
|
+
description: "Import and validate content from an existing file (parsed per the destination extension)",
|
|
214
|
+
},
|
|
203
215
|
force: {
|
|
204
216
|
type: "boolean",
|
|
205
217
|
description: "Overwrite an existing workflow (requires --from or --reset)",
|
|
@@ -237,26 +249,54 @@ const workflowCreateCommand = defineJsonCommand({
|
|
|
237
249
|
const workflowTemplateCommand = defineCommand({
|
|
238
250
|
meta: {
|
|
239
251
|
name: "template",
|
|
240
|
-
description: "Print a valid workflow markdown
|
|
252
|
+
description: "Print a valid workflow template (markdown by default, --yaml for a YAML program)",
|
|
253
|
+
},
|
|
254
|
+
args: {
|
|
255
|
+
yaml: {
|
|
256
|
+
type: "boolean",
|
|
257
|
+
description: "Print a minimal valid YAML workflow program instead of the markdown template",
|
|
258
|
+
default: false,
|
|
259
|
+
},
|
|
241
260
|
},
|
|
242
|
-
run() {
|
|
243
|
-
process.stdout.write(getWorkflowTemplate());
|
|
261
|
+
run({ args }) {
|
|
262
|
+
process.stdout.write(args.yaml ? getWorkflowProgramTemplate() : getWorkflowTemplate());
|
|
244
263
|
},
|
|
245
264
|
});
|
|
246
265
|
const workflowValidateCommand = defineJsonCommand({
|
|
247
266
|
meta: {
|
|
248
267
|
name: "validate",
|
|
249
|
-
description: "Validate a workflow
|
|
268
|
+
description: "Validate a workflow file or ref (markdown document or YAML program) and print any errors",
|
|
250
269
|
},
|
|
251
270
|
args: {
|
|
252
271
|
target: {
|
|
253
272
|
type: "positional",
|
|
254
|
-
description: "Workflow ref (workflow:<name>) or filesystem path to a workflow .md",
|
|
273
|
+
description: "Workflow ref (workflow:<name>) or filesystem path to a workflow .md/.yaml",
|
|
255
274
|
required: true,
|
|
256
275
|
},
|
|
257
276
|
},
|
|
258
277
|
async run({ args }) {
|
|
259
278
|
const filePath = await resolveWorkflowFilePath(args.target);
|
|
279
|
+
// YAML programs (redesign addendum, R1) validate through the program
|
|
280
|
+
// parser AND compiler so expression/reference errors surface at lint
|
|
281
|
+
// time; both error lists carry line numbers. Markdown is unchanged.
|
|
282
|
+
if (isWorkflowProgramPath(filePath)) {
|
|
283
|
+
const { result } = validateWorkflowProgramSource(filePath);
|
|
284
|
+
if (!result.ok) {
|
|
285
|
+
throw new UsageError(formatWorkflowErrors(filePath, result.errors));
|
|
286
|
+
}
|
|
287
|
+
// Non-fatal WARNINGS ride the envelope additively — `ok` stays true. The
|
|
288
|
+
// text formatter renders them clearly marked for humans; the JSON key is
|
|
289
|
+
// the machine channel. Empty array when the program is fully typed/declared.
|
|
290
|
+
output("workflow-validate", {
|
|
291
|
+
ok: true,
|
|
292
|
+
path: filePath,
|
|
293
|
+
format: "program",
|
|
294
|
+
title: result.program.name,
|
|
295
|
+
stepCount: result.program.steps.length,
|
|
296
|
+
warnings: result.warnings.map((w) => ({ line: w.line, message: w.message })),
|
|
297
|
+
});
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
260
300
|
const { parse } = validateWorkflowSource(filePath);
|
|
261
301
|
if (parse.ok) {
|
|
262
302
|
output("workflow-validate", {
|
|
@@ -271,7 +311,15 @@ const workflowValidateCommand = defineJsonCommand({
|
|
|
271
311
|
},
|
|
272
312
|
});
|
|
273
313
|
async function resolveWorkflowFilePath(target) {
|
|
274
|
-
|
|
314
|
+
// A bare (`workflow:<name>`) OR origin-qualified (`<origin>//workflow:<name>`)
|
|
315
|
+
// ref resolves through the source search, exactly like `workflow start` /
|
|
316
|
+
// `status` / `next`. Anything else is treated as a filesystem path. Detecting
|
|
317
|
+
// the origin-qualified form here (not just the bare prefix) keeps `validate`'s
|
|
318
|
+
// ref contract in lockstep with the rest of the workflow command family — an
|
|
319
|
+
// `extra//workflow:foo` ref validates the file that `extra//workflow:foo`
|
|
320
|
+
// starts, rather than being mistaken for a relative path that does not exist.
|
|
321
|
+
const looksLikeWorkflowRef = target.startsWith("workflow:") || target.includes("//workflow:");
|
|
322
|
+
if (!looksLikeWorkflowRef)
|
|
275
323
|
return target;
|
|
276
324
|
const parsed = parseAssetRef(target);
|
|
277
325
|
if (parsed.type !== "workflow") {
|
|
@@ -290,6 +338,218 @@ async function resolveWorkflowFilePath(target) {
|
|
|
290
338
|
}
|
|
291
339
|
throw new UsageError(`Workflow not found for ref: workflow:${parsed.name}`);
|
|
292
340
|
}
|
|
341
|
+
const workflowRunCommand = defineJsonCommand({
|
|
342
|
+
meta: {
|
|
343
|
+
name: "run",
|
|
344
|
+
description: "EXPERIMENTAL: execute a workflow's steps with the native engine — akm dispatches each step's units " +
|
|
345
|
+
"(fan-out, schema output) to the configured runner and advances the run through the normal completion gates",
|
|
346
|
+
},
|
|
347
|
+
args: {
|
|
348
|
+
target: { type: "positional", description: "Workflow run id or workflow ref (auto-starts a run)", required: true },
|
|
349
|
+
params: { type: "string", description: "Workflow parameters as a JSON object (only for auto-started runs)" },
|
|
350
|
+
"max-steps": { type: "string", description: "Stop after executing this many steps" },
|
|
351
|
+
"require-gates": {
|
|
352
|
+
type: "boolean",
|
|
353
|
+
description: "Treat every criteria-bearing completion gate as required: if no LLM judge is available, BLOCK the step " +
|
|
354
|
+
"(for a human to resolve via `akm workflow resume`) instead of failing open. A per-step `gate.required: true` " +
|
|
355
|
+
"in the workflow does the same on every surface; this is the run-wide override (#18).",
|
|
356
|
+
default: false,
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
async run({ args }) {
|
|
360
|
+
const { runWorkflowSteps } = await import("../workflows/exec/run-workflow.js");
|
|
361
|
+
const rawMaxSteps = getStringArg(args, "max-steps");
|
|
362
|
+
let maxSteps;
|
|
363
|
+
if (rawMaxSteps !== undefined) {
|
|
364
|
+
maxSteps = Number.parseInt(rawMaxSteps, 10);
|
|
365
|
+
if (!/^\d+$/.test(rawMaxSteps) || maxSteps <= 0) {
|
|
366
|
+
throw new UsageError(`--max-steps must be a positive integer, got "${rawMaxSteps}".`, "INVALID_FLAG_VALUE");
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const result = await runWorkflowSteps({
|
|
370
|
+
target: args.target,
|
|
371
|
+
...(args.params ? { params: parseWorkflowJsonObject(args.params, "--params") } : {}),
|
|
372
|
+
...(maxSteps !== undefined ? { maxSteps } : {}),
|
|
373
|
+
...(args["require-gates"] === true ? { requireGates: true } : {}),
|
|
374
|
+
});
|
|
375
|
+
output("workflow-run", result);
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
const workflowBriefCommand = defineJsonCommand({
|
|
379
|
+
meta: {
|
|
380
|
+
name: "brief",
|
|
381
|
+
description: "EXPERIMENTAL: describe a run's active step as an executable work-list for ANY agent session (the " +
|
|
382
|
+
"harness-neutral driver protocol) — read-only, takes no engine lease, mutates nothing; prints per-unit " +
|
|
383
|
+
"instructions, output schema, env binding names, and the exact `akm workflow report` command lines",
|
|
384
|
+
},
|
|
385
|
+
args: {
|
|
386
|
+
target: {
|
|
387
|
+
type: "positional",
|
|
388
|
+
description: "Workflow run id (or a workflow ref with an active run)",
|
|
389
|
+
required: true,
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
async run({ args }) {
|
|
393
|
+
const { buildWorkflowBrief } = await import("../workflows/exec/brief.js");
|
|
394
|
+
const result = await buildWorkflowBrief(args.target);
|
|
395
|
+
output("workflow-brief", result);
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
const WORKFLOW_REPORT_STATES = ["completed", "failed", "running"];
|
|
399
|
+
const workflowReportCommand = defineJsonCommand({
|
|
400
|
+
meta: {
|
|
401
|
+
name: "report",
|
|
402
|
+
description: "EXPERIMENTAL: report a unit's result back into a run (the mutating half of the harness-neutral driver " +
|
|
403
|
+
"protocol) — ingested through the SAME shared step semantics the engine uses. --status running claims/" +
|
|
404
|
+
"heartbeats a unit; completed/failed records it and, when the step's work-list is fully terminal, runs the " +
|
|
405
|
+
"engine's completion path (reducer, artifact + schema validation, gate). --settle (no --unit) advances a run " +
|
|
406
|
+
"parked on a route-only/empty step. Refused while a live engine lease exists",
|
|
407
|
+
},
|
|
408
|
+
args: {
|
|
409
|
+
target: {
|
|
410
|
+
type: "positional",
|
|
411
|
+
description: "Workflow run id (or a workflow ref with an active run)",
|
|
412
|
+
required: true,
|
|
413
|
+
},
|
|
414
|
+
unit: {
|
|
415
|
+
type: "string",
|
|
416
|
+
description: "Content-derived unit id from `akm workflow brief` (copy it verbatim). Omit with --settle.",
|
|
417
|
+
},
|
|
418
|
+
settle: {
|
|
419
|
+
type: "boolean",
|
|
420
|
+
description: "Advance/finalize a run whose active step has NO unit left to report: a non-dispatching step (params-based route, empty fan-out, all-unresolvable) OR a fully-terminal step still needing finalization (every unit ran but the gate never judged — e.g. after resuming a required-gate block). Runs the deterministic completion path. Mutually exclusive with --unit; refused when the step still has genuinely pending units",
|
|
421
|
+
default: false,
|
|
422
|
+
},
|
|
423
|
+
"expect-step": {
|
|
424
|
+
type: "string",
|
|
425
|
+
description: "Guard: the step id you briefed against. Refuses the report if the run's active step has since moved (from the `brief` report/settle command line)",
|
|
426
|
+
},
|
|
427
|
+
status: { type: "string", description: `Unit status: ${WORKFLOW_REPORT_STATES.join(", ")}` },
|
|
428
|
+
result: { type: "string", description: "Result payload (JSON for a schema unit, else text). completed only." },
|
|
429
|
+
"result-file": { type: "string", description: "Read the result payload from this file instead of --result/stdin" },
|
|
430
|
+
tokens: { type: "string", description: "Tokens spent on this unit (counts against a declared budget)" },
|
|
431
|
+
"session-id": { type: "string", description: "Harness-native session id revealed while executing the unit" },
|
|
432
|
+
"failure-reason": { type: "string", description: "Structured failure vocabulary for a --status failed report" },
|
|
433
|
+
note: { type: "string", description: "Short progress note for a --status running heartbeat (not persisted)" },
|
|
434
|
+
rerun: {
|
|
435
|
+
type: "boolean",
|
|
436
|
+
description: "Re-run an already-FAILED unit: record a NEW attempt (re-applies budget) instead of refusing a differing re-report",
|
|
437
|
+
default: false,
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
async run({ args }) {
|
|
441
|
+
// --settle: the unit-less verb that advances a run parked on a
|
|
442
|
+
// non-dispatching step. Mutually exclusive with the per-unit report flags.
|
|
443
|
+
if (args.settle === true) {
|
|
444
|
+
if (getStringArg(args, "unit") !== undefined || getStringArg(args, "status") !== undefined) {
|
|
445
|
+
throw new UsageError("--settle advances a route-only/empty step and takes no --unit or --status. Drop them, or report a " +
|
|
446
|
+
"specific unit with `--unit <id> --status <state>` instead.", "INVALID_FLAG_VALUE");
|
|
447
|
+
}
|
|
448
|
+
const { settleWorkflowSpine } = await import("../workflows/exec/report.js");
|
|
449
|
+
const result = await settleWorkflowSpine({
|
|
450
|
+
target: args.target,
|
|
451
|
+
...(getStringArg(args, "expect-step") !== undefined ? { expectStep: getStringArg(args, "expect-step") } : {}),
|
|
452
|
+
});
|
|
453
|
+
output("workflow-report", result);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const status = args.status;
|
|
457
|
+
if (!status) {
|
|
458
|
+
throw new UsageError("--status is required (completed | failed | running), or pass --settle to advance a non-dispatching step.", "MISSING_REQUIRED_ARGUMENT");
|
|
459
|
+
}
|
|
460
|
+
if (!WORKFLOW_REPORT_STATES.includes(status)) {
|
|
461
|
+
throw new UsageError(`Invalid --status "${status}". Expected one of: ${WORKFLOW_REPORT_STATES.join(", ")}.`, "INVALID_FLAG_VALUE");
|
|
462
|
+
}
|
|
463
|
+
const unitId = getStringArg(args, "unit");
|
|
464
|
+
if (!unitId) {
|
|
465
|
+
throw new UsageError("--unit is required (the content-derived unit id from `akm workflow brief`), or pass --settle for a route-only/empty step.", "MISSING_REQUIRED_ARGUMENT");
|
|
466
|
+
}
|
|
467
|
+
let tokens;
|
|
468
|
+
const rawTokens = getStringArg(args, "tokens");
|
|
469
|
+
if (rawTokens !== undefined) {
|
|
470
|
+
tokens = Number.parseInt(rawTokens, 10);
|
|
471
|
+
if (!/^\d+$/.test(rawTokens)) {
|
|
472
|
+
throw new UsageError(`--tokens must be a non-negative integer, got "${rawTokens}".`, "INVALID_FLAG_VALUE");
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// Result payload precedence: --result, then --result-file, then stdin
|
|
476
|
+
// (completed/failed only; a running heartbeat carries no result).
|
|
477
|
+
let resultRaw;
|
|
478
|
+
if (status !== "running") {
|
|
479
|
+
const resultFile = getStringArg(args, "result-file");
|
|
480
|
+
if (args.result !== undefined && resultFile !== undefined) {
|
|
481
|
+
throw new UsageError("Pass at most one of --result or --result-file.", "INVALID_FLAG_VALUE");
|
|
482
|
+
}
|
|
483
|
+
if (args.result !== undefined) {
|
|
484
|
+
resultRaw = String(args.result);
|
|
485
|
+
}
|
|
486
|
+
else if (resultFile !== undefined) {
|
|
487
|
+
const fs = await import("node:fs");
|
|
488
|
+
resultRaw = fs.readFileSync(resultFile, "utf8");
|
|
489
|
+
}
|
|
490
|
+
else if (!process.stdin.isTTY) {
|
|
491
|
+
resultRaw = await readStdin();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const { reportWorkflowUnit } = await import("../workflows/exec/report.js");
|
|
495
|
+
const result = await reportWorkflowUnit({
|
|
496
|
+
target: args.target,
|
|
497
|
+
unitId,
|
|
498
|
+
status: status,
|
|
499
|
+
...(getStringArg(args, "expect-step") !== undefined ? { expectStep: getStringArg(args, "expect-step") } : {}),
|
|
500
|
+
...(resultRaw !== undefined ? { resultRaw } : {}),
|
|
501
|
+
...(tokens !== undefined ? { tokens } : {}),
|
|
502
|
+
...(args.rerun === true ? { rerun: true } : {}),
|
|
503
|
+
...(getStringArg(args, "session-id") !== undefined ? { sessionId: getStringArg(args, "session-id") } : {}),
|
|
504
|
+
...(getStringArg(args, "failure-reason") !== undefined
|
|
505
|
+
? { failureReason: getStringArg(args, "failure-reason") }
|
|
506
|
+
: {}),
|
|
507
|
+
...(getStringArg(args, "note") !== undefined ? { note: getStringArg(args, "note") } : {}),
|
|
508
|
+
});
|
|
509
|
+
output("workflow-report", result);
|
|
510
|
+
},
|
|
511
|
+
});
|
|
512
|
+
async function readStdin() {
|
|
513
|
+
const chunks = [];
|
|
514
|
+
for await (const chunk of process.stdin)
|
|
515
|
+
chunks.push(chunk);
|
|
516
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
517
|
+
}
|
|
518
|
+
const workflowWatchCommand = defineJsonCommand({
|
|
519
|
+
meta: {
|
|
520
|
+
name: "watch",
|
|
521
|
+
description: "Print a run's workflow_* events (state.db events table) as NDJSON and exit; --stream polls in the " +
|
|
522
|
+
"foreground until the run reaches a terminal status (no daemon)",
|
|
523
|
+
},
|
|
524
|
+
args: {
|
|
525
|
+
runId: { type: "positional", description: "Workflow run id", required: true },
|
|
526
|
+
stream: {
|
|
527
|
+
type: "boolean",
|
|
528
|
+
description: "Keep polling for new events until the run leaves 'active' (completed/failed/blocked)",
|
|
529
|
+
default: false,
|
|
530
|
+
},
|
|
531
|
+
"interval-ms": { type: "string", description: "Poll interval in milliseconds for --stream (default: 1000)" },
|
|
532
|
+
},
|
|
533
|
+
async run({ args }) {
|
|
534
|
+
const rawInterval = getStringArg(args, "interval-ms");
|
|
535
|
+
let intervalMs;
|
|
536
|
+
if (rawInterval !== undefined) {
|
|
537
|
+
intervalMs = Number.parseInt(rawInterval, 10);
|
|
538
|
+
if (!/^\d+$/.test(rawInterval) || intervalMs <= 0) {
|
|
539
|
+
throw new UsageError(`--interval-ms must be a positive integer, got "${rawInterval}".`, "INVALID_FLAG_VALUE");
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
const { watchWorkflowRun } = await import("../workflows/exec/watch.js");
|
|
543
|
+
const result = await watchWorkflowRun({
|
|
544
|
+
runId: args.runId,
|
|
545
|
+
stream: args.stream === true,
|
|
546
|
+
...(intervalMs !== undefined ? { intervalMs } : {}),
|
|
547
|
+
});
|
|
548
|
+
// The event lines above are raw NDJSON on stdout; this trailing envelope
|
|
549
|
+
// is the machine-readable command result (counts + terminal status).
|
|
550
|
+
output("workflow-watch", { ok: true, ...result });
|
|
551
|
+
},
|
|
552
|
+
});
|
|
293
553
|
const workflowAbandonCommand = defineJsonCommand({
|
|
294
554
|
meta: {
|
|
295
555
|
name: "abandon",
|
|
@@ -332,6 +592,10 @@ export const workflowCommand = defineCommand({
|
|
|
332
592
|
resume: workflowResumeCommand,
|
|
333
593
|
abandon: workflowAbandonCommand,
|
|
334
594
|
validate: workflowValidateCommand,
|
|
595
|
+
run: workflowRunCommand,
|
|
596
|
+
brief: workflowBriefCommand,
|
|
597
|
+
report: workflowReportCommand,
|
|
598
|
+
watch: workflowWatchCommand,
|
|
335
599
|
},
|
|
336
600
|
run({ args }) {
|
|
337
601
|
return runWithJsonErrors(async () => {
|