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
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
version:
|
|
1
|
+
version: 4
|
|
2
2
|
run: akm improve --strategy catchup --skip-if-locked
|
|
3
|
-
akm
|
|
4
|
-
schedule: "0 4 * * *"
|
|
3
|
+
description: Manual recovery — consolidation + triage drain (run on demand via `akm task run akm-improve-catchup`)
|
|
5
4
|
# Manual-recovery task: ships disabled (the retired registerDefaultTasks
|
|
6
5
|
# marked it enableMode: "manual"). `akm task run` works while disabled;
|
|
7
|
-
# opting into the schedule is `enabled: true` + `akm task sync`.
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
# opting into the schedule is `schedule[].enabled: true` + `akm task sync`.
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: "0 4 * * *"
|
|
9
|
+
enabled: false
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
version:
|
|
1
|
+
version: 4
|
|
2
2
|
run: akm improve --strategy consolidate --skip-if-locked
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
enabled: true
|
|
6
|
-
description: Consolidation-only pass (every 4h at :20)
|
|
3
|
+
description: Consolidation-only pass (every 4h at :20)
|
|
4
|
+
schedule: "20 */4 * * *"
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
version:
|
|
1
|
+
version: 4
|
|
2
2
|
run: akm improve --strategy frequent --skip-if-locked
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
enabled: true
|
|
6
|
-
description: Frequent inference pass (hourly at :40; improve-stage extract off)
|
|
3
|
+
description: Frequent inference pass (hourly at :40; improve-stage extract off)
|
|
4
|
+
schedule: "40 * * * *"
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
version:
|
|
1
|
+
version: 4
|
|
2
2
|
run: akm improve --strategy thorough --skip-if-locked
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
enabled: true
|
|
6
|
-
description: Full nightly quality sweep (daily 2:15am; suggested for server installs)
|
|
3
|
+
description: Full nightly quality sweep (daily 2:15am; suggested for server installs)
|
|
4
|
+
schedule: "15 2 * * *"
|
|
@@ -181,7 +181,18 @@ export function assertKnownFlags(root, rawArgs) {
|
|
|
181
181
|
const known = collectKnownArgs(root, rawArgs);
|
|
182
182
|
if (!known.resolved)
|
|
183
183
|
return;
|
|
184
|
-
|
|
184
|
+
// `workflow run`, `task run`, and `task explain` each own one deliberately
|
|
185
|
+
// dynamic namespace: long options become exact-name parameter/input flags
|
|
186
|
+
// and are checked against the frozen plan / task source's own contract
|
|
187
|
+
// before this gate would otherwise reject them (spec
|
|
188
|
+
// docs/plans/specs/p2a-task-source-v4.md §5.1 — task run's
|
|
189
|
+
// UNKNOWN_FLAG/INPUT_BINDING_INVALID must be raised from INSIDE the command
|
|
190
|
+
// body, where runWithJsonErrors renders the JSON envelope, not from this
|
|
191
|
+
// generic pre-dispatch gate; docs/plans/specs/p2b-input-bindings.md §4.5,
|
|
192
|
+
// B-55 — `task explain` reuses the identical `parseTaskInputFlags`
|
|
193
|
+
// scanner, so it needs the identical exemption).
|
|
194
|
+
const dynamicNamedFlagCommands = new Set(["workflow run", "task run", "task explain"]);
|
|
195
|
+
const dynamicWorkflowParams = dynamicNamedFlagCommands.has(known.path.join(" "));
|
|
185
196
|
const selfDiagnosed = SELF_DIAGNOSED_FLAGS.get(known.path.join(" "));
|
|
186
197
|
for (let i = 0; i < ownArgs.length; i += 1) {
|
|
187
198
|
const token = ownArgs[i];
|
package/dist/cli.js
CHANGED
|
@@ -557,7 +557,14 @@ export const main = defineCommand({
|
|
|
557
557
|
},
|
|
558
558
|
});
|
|
559
559
|
const MAIN_TOP_LEVEL_ARGS = main.args;
|
|
560
|
-
|
|
560
|
+
/**
|
|
561
|
+
* F-5 (spec docs/plans/specs/p2a-task-source-v4.md §6): exported so
|
|
562
|
+
* `akm task run`'s input-flags coverage can pin that an undeclared,
|
|
563
|
+
* per-task dynamic flag (e.g. `--scope all`) does not change this
|
|
564
|
+
* classification — it parses only `args.id` off the declared args and
|
|
565
|
+
* tolerates extra tokens (citty's non-strict `parseArgs`).
|
|
566
|
+
*/
|
|
567
|
+
export function isTaskRunWithId(argv) {
|
|
561
568
|
const args = argv.slice(2);
|
|
562
569
|
const commandIndex = findCittyTopLevelCommandIndex(args, MAIN_TOP_LEVEL_ARGS);
|
|
563
570
|
const command = commandIndex >= 0 ? args[commandIndex] : undefined;
|
|
@@ -14,6 +14,23 @@ import { prepareResolvedExecution } from "../../integrations/agent/execution-pre
|
|
|
14
14
|
import { parseBuiltinCommandAction } from "./builtin-action.js";
|
|
15
15
|
import { loadAdapterExecutionSource, } from "./execution-source-loader.js";
|
|
16
16
|
import { applyPortableCommandArguments } from "./portable-template.js";
|
|
17
|
+
/**
|
|
18
|
+
* F-1 (spec §5.2 point 3): resolve the ambient-first provenance stamp
|
|
19
|
+
* (matching the native arm's own `process.env.AKM_EVENT_SOURCE ?? …`) and
|
|
20
|
+
* hand it to `dispatchLoweredExecutionRequest`'s dedicated, single-purpose
|
|
21
|
+
* `eventSource` field — never through `runOptions`/`agentOptions`, which
|
|
22
|
+
* stays exactly as untrusted for overriding resolved content as it was
|
|
23
|
+
* before P1b (a caller-supplied `runOptions.env` still cannot replace frozen
|
|
24
|
+
* request data, including a scheduler-restored directory value — see
|
|
25
|
+
* `tests/integration/tasks-runner.test.ts`'s "forwards scheduled AKM
|
|
26
|
+
* directory context … without trusting task or caller overrides").
|
|
27
|
+
*/
|
|
28
|
+
function loweredDispatchOptions(options) {
|
|
29
|
+
const { eventSource, ...rest } = options;
|
|
30
|
+
if (eventSource === undefined)
|
|
31
|
+
return rest;
|
|
32
|
+
return { ...rest, eventSource: process.env.AKM_EVENT_SOURCE ?? eventSource };
|
|
33
|
+
}
|
|
17
34
|
function own(value, key) {
|
|
18
35
|
return Object.hasOwn(value, key);
|
|
19
36
|
}
|
|
@@ -291,13 +308,17 @@ export async function dispatchPreparedCommandInvocation(prepared, options = {})
|
|
|
291
308
|
if (!selectedEngine) {
|
|
292
309
|
throw new ConfigError(`command ${NO_ENGINE_MESSAGE_SUFFIX} ${NO_ENGINE_REMEDY}`, "INVALID_CONFIG_FILE");
|
|
293
310
|
}
|
|
294
|
-
const result = await dispatchLoweredExecutionRequest(lowered, options);
|
|
311
|
+
const result = await dispatchLoweredExecutionRequest(lowered, loweredDispatchOptions(options));
|
|
295
312
|
const consumedRefs = new Set();
|
|
296
313
|
if (request.command.source)
|
|
297
314
|
consumedRefs.add(request.command.source.ref);
|
|
298
315
|
if (request.persona)
|
|
299
316
|
consumedRefs.add(request.persona.source.ref);
|
|
300
|
-
|
|
317
|
+
// F-1 (spec §5.2 point 3): options.eventSource is only a FALLBACK — an
|
|
318
|
+
// ambient AKM_EVENT_SOURCE still wins (D5 clause d). Absent, this is
|
|
319
|
+
// byte-identical to the pre-P1b bare resolveUsageEventSource() call (P-07's
|
|
320
|
+
// own default is "user").
|
|
321
|
+
const eventSource = resolveUsageEventSource(process.env, options.eventSource ?? "user");
|
|
301
322
|
for (const ref of consumedRefs)
|
|
302
323
|
recordIndexedShowUsage(ref, eventSource);
|
|
303
324
|
const announcement = fallbackAnnouncement(prepared.fallbackEngineName, selectedEngine);
|
|
@@ -15,6 +15,44 @@ export function parseTaskMetadata(row) {
|
|
|
15
15
|
...(metadata.engine !== undefined ? { engine: metadata.engine } : {}),
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* D8 read-boundary predicate (spec docs/plans/specs/p1b-model-extraction.md
|
|
20
|
+
* §5.3) for `akm health`'s `agentFailureRate`: true for a `task_history` row
|
|
21
|
+
* that represents a prepared command (agent/LLM) result, across both
|
|
22
|
+
* vocabularies. Mirrors src/tasks/run/task-history.ts's
|
|
23
|
+
* `taskHistoryRowToResult` read mapping:
|
|
24
|
+
* - NEW rows mark themselves with metadata `targetVocab: 2` and store
|
|
25
|
+
* `target_kind: "command"` for the agent/LLM arm.
|
|
26
|
+
* - LEGACY rows (no marker, written before P1b's F-2 re-code) stored
|
|
27
|
+
* `target_kind: "prompt"` for the same arm — and `"command"` for the
|
|
28
|
+
* UNRELATED native shell/script arm, which must NOT be counted here.
|
|
29
|
+
* So an unmarked `"command"` row is a legacy shell/script run, not an
|
|
30
|
+
* agent/LLM one; a marked `"command"` row (or an unmarked `"prompt"` row) is.
|
|
31
|
+
*/
|
|
32
|
+
export function isAgentTaskHistoryRow(row) {
|
|
33
|
+
// Check target_kind BEFORE decoding metadata: some rows in the wild
|
|
34
|
+
// (e.g. improve-pipeline task_history rows, target_kind "improve") carry
|
|
35
|
+
// metadata_json that predates the metadataVersion:2 shape entirely, and
|
|
36
|
+
// decodeTaskHistoryMetadata throws on that — exactly like the pre-fix
|
|
37
|
+
// `target_kind === "prompt"` filter, which never called it for a row this
|
|
38
|
+
// function isn't going to count anyway. Only decode for the two target
|
|
39
|
+
// kinds this predicate can return true for.
|
|
40
|
+
if (row.target_kind !== "command" && row.target_kind !== "prompt")
|
|
41
|
+
return false;
|
|
42
|
+
// An undecodable metadata_json is by definition unmarked: pre-P1b rows can
|
|
43
|
+
// carry shapes decodeTaskHistoryMetadata rejects, and `akm health` must
|
|
44
|
+
// classify them as legacy rather than throw (round-3 review advisory).
|
|
45
|
+
let marked = false;
|
|
46
|
+
try {
|
|
47
|
+
marked = decodeTaskHistoryMetadata(row.metadata_json).targetVocab === 2;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
marked = false;
|
|
51
|
+
}
|
|
52
|
+
if (row.target_kind === "command")
|
|
53
|
+
return marked;
|
|
54
|
+
return !marked;
|
|
55
|
+
}
|
|
18
56
|
function createUnknownImproveMetrics() {
|
|
19
57
|
return {
|
|
20
58
|
invoked: 0,
|
|
@@ -11,7 +11,7 @@ import { readEvents } from "../../core/events.js";
|
|
|
11
11
|
import { buildTaskRunId, getLoggedRunIds } from "../../core/logs-db.js";
|
|
12
12
|
import { DURATION_UNITS, parseDuration } from "../../core/time.js";
|
|
13
13
|
import { queryTaskHistory } from "../../storage/repositories/task-history-repository.js";
|
|
14
|
-
import { buildImproveSkipSummary, computeWallTimeStats, parseTaskMetadata, roundRate, summarizeImproveCompleted, summarizeImproveRuns, } from "./improve-metrics.js";
|
|
14
|
+
import { buildImproveSkipSummary, computeWallTimeStats, isAgentTaskHistoryRow, parseTaskMetadata, roundRate, summarizeImproveCompleted, summarizeImproveRuns, } from "./improve-metrics.js";
|
|
15
15
|
import { readLlmUsageAggregate } from "./llm-usage.js";
|
|
16
16
|
import { computeDegradationMetrics, computeDenominatorFixedCoverage } from "./metrics.js";
|
|
17
17
|
import { buildPerRunSummaries } from "./task-runs.js";
|
|
@@ -148,14 +148,18 @@ export function buildWindowMetrics(db, stateDbPath, since, until, now = () => Da
|
|
|
148
148
|
const failedTaskRows = taskRows.filter((row) => row.status === "failed");
|
|
149
149
|
const activeRows = taskRows.filter((row) => row.status === "active" && row.completed_at === null);
|
|
150
150
|
const stuckActiveRuns = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
// D8 (spec §5.3): a marked "command" row or a legacy (unmarked) "prompt"
|
|
152
|
+
// row is the agent/LLM arm; an unmarked "command" row is the legacy
|
|
153
|
+
// native shell/script arm and must not be counted here (see
|
|
154
|
+
// isAgentTaskHistoryRow's header comment for the full mapping).
|
|
155
|
+
const agentRows = taskRows.filter((row) => isAgentTaskHistoryRow(row));
|
|
156
|
+
const agentFailures = agentRows.filter((row) => {
|
|
153
157
|
const detail = parseTaskMetadata(row).detail;
|
|
154
158
|
return typeof detail?.reason === "string" && detail.reason.length > 0;
|
|
155
159
|
});
|
|
156
160
|
const logBackingRate = taskRowsWithLogs.length === 0 ? 1 : existingLogRows.length / taskRowsWithLogs.length;
|
|
157
161
|
const taskFailRate = taskRows.length === 0 ? 0 : failedTaskRows.length / taskRows.length;
|
|
158
|
-
const agentFailureRate =
|
|
162
|
+
const agentFailureRate = agentRows.length === 0 ? 0 : agentFailures.length / agentRows.length;
|
|
159
163
|
const improveInvoked = readEvents({ since, type: "improve_invoked" }, { dbPath: stateDbPath }).events.filter((event) => new Date(event.ts ?? since).getTime() < new Date(until).getTime()).length;
|
|
160
164
|
const improveCompletedEvents = readEvents({ since, type: IMPROVE_COMPLETED_EVENT }, { dbPath: stateDbPath }).events.filter((event) => new Date(event.ts ?? since).getTime() < new Date(until).getTime());
|
|
161
165
|
const improveSkippedEvents = readEvents({ since, type: "improve_skipped" }, { dbPath: stateDbPath }).events.filter((event) => new Date(event.ts ?? since).getTime() < new Date(until).getTime());
|
package/dist/commands/health.js
CHANGED
|
@@ -20,7 +20,7 @@ import { queryTaskHistory } from "../storage/repositories/task-history-repositor
|
|
|
20
20
|
import { pkgVersion } from "../version.js";
|
|
21
21
|
import { collectImproveAdvisories } from "./health/advisories.js";
|
|
22
22
|
import { HEALTH_CHECKS, runHealthEngineProbes } from "./health/checks.js";
|
|
23
|
-
import { buildImproveSkipSummary, computeWallTimeStats, parseTaskMetadata, roundRate, summarizeImproveCompleted, summarizeImproveRuns, } from "./health/improve-metrics.js";
|
|
23
|
+
import { buildImproveSkipSummary, computeWallTimeStats, isAgentTaskHistoryRow, parseTaskMetadata, roundRate, summarizeImproveCompleted, summarizeImproveRuns, } from "./health/improve-metrics.js";
|
|
24
24
|
import { emptyLlmUsageAggregate, readLlmUsageAggregate } from "./health/llm-usage.js";
|
|
25
25
|
import { computeDegradationMetrics, computeDenominatorFixedCoverage, computeEnrichmentMintingRollup, probeStateDbRoundTrip, } from "./health/metrics.js";
|
|
26
26
|
import { collectPluginStalenessAdvisories } from "./health/plugin-staleness.js";
|
|
@@ -124,14 +124,18 @@ function gatherTaskHistoryPhase(db, logsDb, since, stateDbPath, now) {
|
|
|
124
124
|
const failedTaskRows = taskRows.filter((row) => row.status === "failed");
|
|
125
125
|
const activeRows = taskRows.filter((row) => row.status === "active" && row.completed_at === null);
|
|
126
126
|
const stuckActiveRows = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS);
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
// D8 (spec §5.3): a marked "command" row or a legacy (unmarked) "prompt"
|
|
128
|
+
// row is the agent/LLM arm; an unmarked "command" row is the legacy
|
|
129
|
+
// native shell/script arm and must not be counted here (see
|
|
130
|
+
// isAgentTaskHistoryRow's header comment for the full mapping).
|
|
131
|
+
const agentRows = taskRows.filter((row) => isAgentTaskHistoryRow(row));
|
|
132
|
+
const agentFailures = agentRows.filter((row) => {
|
|
129
133
|
const detail = parseTaskMetadata(row).detail;
|
|
130
134
|
return typeof detail?.reason === "string" && detail.reason.length > 0;
|
|
131
135
|
});
|
|
132
136
|
const logBackingRate = taskRowsWithLogs.length === 0 ? 1 : existingLogRows.length / taskRowsWithLogs.length;
|
|
133
137
|
const taskFailRate = taskRows.length === 0 ? 0 : failedTaskRows.length / taskRows.length;
|
|
134
|
-
const agentFailureRate =
|
|
138
|
+
const agentFailureRate = agentRows.length === 0 ? 0 : agentFailures.length / agentRows.length;
|
|
135
139
|
return {
|
|
136
140
|
tableNames,
|
|
137
141
|
missingTables,
|
|
@@ -18,7 +18,7 @@ import { warn } from "../../core/warn.js";
|
|
|
18
18
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
19
19
|
import { TASK_EXTENSION, TASK_NEAR_MISS_EXTENSION, taskExtensionDetail } from "../../tasks/source-v3.js";
|
|
20
20
|
import { resolveWorkflowSourceDomains } from "../../workflows/source-files.js";
|
|
21
|
-
import { compareWorkflowSourceCodePoints } from "../../workflows/source-ir/
|
|
21
|
+
import { compareWorkflowSourceCodePoints } from "../../workflows/source-ir/compare.js";
|
|
22
22
|
import { runBaseChecks } from "./base-linter.js";
|
|
23
23
|
import { checkEnvForDangerousKeys } from "./env-key-rules.js";
|
|
24
24
|
import { isAdvisoryLintIssue } from "./types.js";
|
|
@@ -1,44 +1,149 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
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
|
-
import { defineGroupCommand, defineJsonCommand, output } from "../cli/shared.js";
|
|
4
|
+
import { defineGroupCommand, defineJsonCommand, EXIT_CODES, output } from "../cli/shared.js";
|
|
5
5
|
import { runMigrationTool } from "./migration-tool.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* output pipeline.
|
|
9
|
-
*/
|
|
10
|
-
async function runMigrateSubcommand(command, args) {
|
|
11
|
-
const result = await runMigrationTool(args);
|
|
6
|
+
async function callMigrateTool(args, runTool) {
|
|
7
|
+
const result = await runTool(args);
|
|
12
8
|
if (result.stderr)
|
|
13
9
|
process.stderr.write(result.stderr);
|
|
14
10
|
const resultLine = result.stdout.trim();
|
|
15
|
-
if (resultLine)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
if (!resultLine)
|
|
12
|
+
return { status: result.status };
|
|
13
|
+
try {
|
|
14
|
+
return { status: result.status, plan: JSON.parse(resultLine) };
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
console.log(resultLine);
|
|
18
|
+
return { status: result.status };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function worstStatus(left, right) {
|
|
22
|
+
if (left === "blocked" || right === "blocked")
|
|
23
|
+
return "blocked";
|
|
24
|
+
if (left === "ready" || right === "ready")
|
|
25
|
+
return "ready";
|
|
26
|
+
return "current";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Resolve one generation's contribution to the combined status — fail
|
|
30
|
+
* CLOSED, never open (code-review finding: this tool advertises itself as
|
|
31
|
+
* "blocked-not-guessed").
|
|
32
|
+
*
|
|
33
|
+
* A generation that exited SUCCESS with no plan on stdout legitimately means
|
|
34
|
+
* "nothing to report" and defaults to `"current"`. A generation that exited
|
|
35
|
+
* NON-SUCCESS (by the caller's own guard, this can only be `EXIT_CODES.
|
|
36
|
+
* GENERAL` — the "blocked" code) with a parsed `plan.status` reports that
|
|
37
|
+
* status verbatim, same as before.
|
|
38
|
+
*
|
|
39
|
+
* The gap this closes: NON-SUCCESS with NO parseable plan at all —
|
|
40
|
+
* `runMigrationTool` coerces a `spawnSync` `status` of `null` (the child was
|
|
41
|
+
* killed by a signal — OOM, a timeout, a manual kill — never scheduled to
|
|
42
|
+
* exit) to `1`, indistinguishable from the migrator's own legitimate
|
|
43
|
+
* "blocked" exit code, and truncated/malformed stdout hits the same
|
|
44
|
+
* `JSON.parse` catch in `callMigrateTool`. Previously `?? "current"` silently
|
|
45
|
+
* read a crashed generation as "nothing to migrate"; this reports it as
|
|
46
|
+
* `"blocked"` with an explanatory blocker instead, so the combined exit code
|
|
47
|
+
* (`EXIT_CODES.GENERAL` below) actually reflects that the generation's real
|
|
48
|
+
* state is unknown, rather than reporting success at exit 0.
|
|
49
|
+
*/
|
|
50
|
+
export function resolveGenerationStatus(call, label) {
|
|
51
|
+
const planStatus = call.plan?.status;
|
|
52
|
+
if (planStatus !== undefined)
|
|
53
|
+
return { status: planStatus };
|
|
54
|
+
if (call.status !== EXIT_CODES.SUCCESS) {
|
|
55
|
+
return {
|
|
56
|
+
status: "blocked",
|
|
57
|
+
error: `${label}: the child process exited without printing a plan (exit status ${call.status}) — its real migration state is unknown.`,
|
|
58
|
+
};
|
|
22
59
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
60
|
+
return { status: "current" };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Run BOTH migration generations — task-v2-to-v3, then task-v3-to-task-
|
|
64
|
+
* source-v4 — and print one combined plan (spec
|
|
65
|
+
* docs/plans/specs/p4-deletions-closeout.md §3.2.5, rows B-31/B-32).
|
|
66
|
+
*
|
|
67
|
+
* Each generation is its OWN subprocess call into the standalone migrator
|
|
68
|
+
* (`scripts/akm-migrate.ts`'s `status`/`apply` and `task-v4-status`/
|
|
69
|
+
* `task-v4-apply` verbs, UNCHANGED — row B-33), so each keeps its own
|
|
70
|
+
* `withConfigLock` + `O_EXCL` backup root + prevalidate + TOCTOU recheck +
|
|
71
|
+
* atomic replace + reverse rollback + convergence check, and the two are
|
|
72
|
+
* NEVER interleaved. The two calls are unconditional and independent of
|
|
73
|
+
* each other's outcome: a blocked (or otherwise incomplete) generation-1
|
|
74
|
+
* result does not stop generation 2 from running against whatever is
|
|
75
|
+
* already task source v4 — exactly `akm-migrate status`/`task-v4-status`
|
|
76
|
+
* (or `apply`/`task-v4-apply`) run back to back by hand. Only a genuine
|
|
77
|
+
* hard failure (a status neither SUCCESS nor the "blocked" GENERAL code —
|
|
78
|
+
* a config error, a crash) aborts the second call, since generation 1 never
|
|
79
|
+
* got to look at a stable tree in that case.
|
|
80
|
+
*/
|
|
81
|
+
export async function runMigrateSubcommand(command, genOneArgs, genTwoArgs, runTool = runMigrationTool) {
|
|
82
|
+
const first = await callMigrateTool(genOneArgs, runTool);
|
|
83
|
+
if (first.status !== EXIT_CODES.SUCCESS && first.status !== EXIT_CODES.GENERAL) {
|
|
84
|
+
process.exitCode = first.status;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const second = await callMigrateTool(genTwoArgs, runTool);
|
|
88
|
+
if (second.status !== EXIT_CODES.SUCCESS && second.status !== EXIT_CODES.GENERAL) {
|
|
89
|
+
process.exitCode = second.status;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!first.plan && !second.plan) {
|
|
93
|
+
if (first.status !== EXIT_CODES.SUCCESS)
|
|
94
|
+
process.exitCode = first.status;
|
|
28
95
|
return;
|
|
29
96
|
}
|
|
97
|
+
const combined = combineMigrationPlans(first, second);
|
|
98
|
+
output(command, combined);
|
|
99
|
+
if (combined.status === "blocked")
|
|
100
|
+
process.exitCode = EXIT_CODES.GENERAL;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Merge both generations' plans into the one combined envelope the command
|
|
104
|
+
* prints. Deliberately PURE — every rule the combined plan encodes (the
|
|
105
|
+
* {@link worstStatus} rollup, the fail-closed
|
|
106
|
+
* {@link resolveGenerationStatus} contribution, and the blockers merge, which
|
|
107
|
+
* orders generation 1's own blockers after its resolution error and before
|
|
108
|
+
* generation 2's) is decided here from two plain values, so it is provable
|
|
109
|
+
* without a subprocess, a CLI dispatch, or an output-mode singleton. The
|
|
110
|
+
* caller keeps the only two effectful decisions: whether generation 2 runs at
|
|
111
|
+
* all, and the process exit code.
|
|
112
|
+
*/
|
|
113
|
+
export function combineMigrationPlans(first, second) {
|
|
114
|
+
const firstResolved = resolveGenerationStatus(first, "task-v2-to-v3");
|
|
115
|
+
const secondResolved = resolveGenerationStatus(second, "task-v3-to-task-source-v4");
|
|
116
|
+
return {
|
|
117
|
+
schemaVersion: 1,
|
|
118
|
+
status: worstStatus(firstResolved.status, secondResolved.status),
|
|
119
|
+
blockers: [
|
|
120
|
+
...(firstResolved.error ? [firstResolved.error] : []),
|
|
121
|
+
...(first.plan?.blockers ?? []),
|
|
122
|
+
...(secondResolved.error ? [secondResolved.error] : []),
|
|
123
|
+
...(second.plan?.blockers ?? []),
|
|
124
|
+
],
|
|
125
|
+
taskV3Migration: first.plan?.taskV3Migration,
|
|
126
|
+
taskV4Migration: second.plan?.taskV4Migration,
|
|
127
|
+
...(first.plan?.backupPath !== undefined ? { backupPath: first.plan.backupPath } : {}),
|
|
128
|
+
...(first.plan?.applied !== undefined ? { applied: first.plan.applied } : {}),
|
|
129
|
+
...(second.plan?.backupPath !== undefined ? { taskV4BackupPath: second.plan.backupPath } : {}),
|
|
130
|
+
...(second.plan?.applied !== undefined ? { taskV4Applied: second.plan.applied } : {}),
|
|
131
|
+
};
|
|
30
132
|
}
|
|
31
133
|
export const migrateCommand = defineGroupCommand({
|
|
32
|
-
meta: { name: "migrate", description: "Inspect or apply task-v2
|
|
134
|
+
meta: { name: "migrate", description: "Inspect or apply task-v2 and task-v3 sources to task source v4" },
|
|
33
135
|
subCommands: {
|
|
34
136
|
status: defineJsonCommand({
|
|
35
|
-
meta: { name: "status", description: "Read-only task-v2 migration check" },
|
|
137
|
+
meta: { name: "status", description: "Read-only task-v2 and task-v3 migration check" },
|
|
36
138
|
run() {
|
|
37
|
-
return runMigrateSubcommand("migrate-status", ["status"]);
|
|
139
|
+
return runMigrateSubcommand("migrate-status", ["status"], ["task-v4-status"]);
|
|
38
140
|
},
|
|
39
141
|
}),
|
|
40
142
|
apply: defineJsonCommand({
|
|
41
|
-
meta: {
|
|
143
|
+
meta: {
|
|
144
|
+
name: "apply",
|
|
145
|
+
description: "Back up and atomically convert task-v2 and task-v3 files to task source v4",
|
|
146
|
+
},
|
|
42
147
|
args: {
|
|
43
148
|
"dry-run": {
|
|
44
149
|
type: "boolean",
|
|
@@ -47,7 +152,8 @@ export const migrateCommand = defineGroupCommand({
|
|
|
47
152
|
},
|
|
48
153
|
},
|
|
49
154
|
run({ args }) {
|
|
50
|
-
|
|
155
|
+
const dryRunFlag = args.dryRun ? ["--dry-run"] : [];
|
|
156
|
+
return runMigrateSubcommand("migrate-apply", ["apply", ...dryRunFlag], ["task-v4-apply", ...dryRunFlag]);
|
|
51
157
|
},
|
|
52
158
|
}),
|
|
53
159
|
},
|
|
@@ -5,7 +5,7 @@ import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
|
|
|
5
5
|
import { parseRefInput } from "../../../core/asset/resolve-ref.js";
|
|
6
6
|
import { proposalContent } from "../../../core/file-change.js";
|
|
7
7
|
import { lintLessonContent } from "../../../core/lesson-lint.js";
|
|
8
|
-
import {
|
|
8
|
+
import { parseTaskSource } from "../../../tasks/source/parse-task-source.js";
|
|
9
9
|
import { compileWorkflowSource } from "../../../workflows/source-ir/compile.js";
|
|
10
10
|
import { defaultProposalQualityValidators } from "./proposal-quality-validators.js";
|
|
11
11
|
const genericProposalValidator = {
|
|
@@ -52,7 +52,12 @@ const canonicalProposalValidators = {
|
|
|
52
52
|
const name = ctx.parsedRef?.name;
|
|
53
53
|
if (!name)
|
|
54
54
|
return [];
|
|
55
|
-
|
|
55
|
+
// Version-routing seam (spec docs/plans/specs/p2a-task-source-v4.md
|
|
56
|
+
// §3.6): a proposal body is validated by parsing alone — neither arm's
|
|
57
|
+
// parsed document is inspected further, so routing through the union is
|
|
58
|
+
// a pure swap; any parse failure (either version) is turned into an
|
|
59
|
+
// `invalid-task-structure` finding by the try/catch this call sits in.
|
|
60
|
+
parseTaskSource({
|
|
56
61
|
yaml: proposalContent(proposal),
|
|
57
62
|
filePath: proposal.changes[0]?.path || proposal.ref,
|
|
58
63
|
});
|