akm-cli 0.9.2-alpha.3 → 0.9.2-alpha.5
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 +664 -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/plugin-staleness.js +219 -0
- package/dist/commands/health/type-directory-check.js +167 -0
- package/dist/commands/health/windows.js +8 -4
- package/dist/commands/health.js +71 -9
- package/dist/commands/improve/extract.js +22 -1
- 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/read/remember-cli.js +6 -1
- package/dist/commands/remember.js +70 -0
- package/dist/commands/tasks/explain.js +304 -0
- package/dist/commands/tasks/tasks-cli.js +185 -3
- package/dist/commands/tasks/tasks.js +233 -45
- 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/integrations/harnesses/claude/session-log.js +85 -25
- package/dist/integrations/session-logs/pre-filter.js +152 -2
- 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/registry/semver.js +4 -0
- 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 +55 -12
- 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 +19 -5
- 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 +2 -2
- package/docs/migration/v0.8-to-v0.9.md +9 -7
- package/docs/migration/v0.9.0-troubleshooting.md +14 -7
- 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 +3 -3
- package/docs/reference/supported-formats.md +9 -5
- package/docs/reference/tasks.md +338 -75
- package/docs/reference/workflow-schema.md +281 -8
- 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
|
@@ -90,12 +90,155 @@ function classifyEvent(event, akmReadOnlyOps, maxLen) {
|
|
|
90
90
|
}
|
|
91
91
|
return { keep: true, event, truncated: false };
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* A parent-side `<task-notification>` event, as Claude Code writes it into a
|
|
95
|
+
* session's own transcript: a `role: "user"` event whose text is (or wraps)
|
|
96
|
+
* `<task-notification>...<task-id>ID</task-id>...<result>TEXT</result>...</task-notification>`.
|
|
97
|
+
* Matched on the tags themselves (not a dedicated field) because
|
|
98
|
+
* {@link SessionEvent} carries no structural provenance beyond `text`/`role`/
|
|
99
|
+
* `filePath` — the same constraint #830 (subagent provenance) worked within.
|
|
100
|
+
*/
|
|
101
|
+
const TASK_NOTIFICATION_RE = /<task-notification>[\s\S]*<\/task-notification>/;
|
|
102
|
+
const TASK_ID_RE = /<task-id>([^<]+)<\/task-id>/;
|
|
103
|
+
const RESULT_RE = /<result>([\s\S]*)<\/result>/;
|
|
104
|
+
const SUMMARY_RE = /<summary>([^<]*)<\/summary>/;
|
|
105
|
+
/** Claude Code's own `<summary>` phrasing for a finished agent: `Agent "<description>" finished`. */
|
|
106
|
+
const AGENT_SUMMARY_DESCRIPTION_RE = /^Agent "(.*)" finished$/;
|
|
107
|
+
/** Provenance {@link subagentProvenance} stamps on every folded subagent event; stripped before comparison. */
|
|
108
|
+
const PROVENANCE_PREFIX_RE = /^\[subagent:[^\]]*\][^\n]*\n/;
|
|
109
|
+
/** Claude Code's own agentId file naming: `<...>/subagents/<...>agent-<agentId>.jsonl`. */
|
|
110
|
+
const SUBAGENT_FILEPATH_RE = /agent-([^/\\]+?)\.jsonl$/;
|
|
111
|
+
/** Dice (bigram) similarity at/above this counts as "the same content" (#839). */
|
|
112
|
+
const DEDUPE_SIMILARITY_THRESHOLD = 0.9;
|
|
113
|
+
/** A handful of named-entity decodes — enough for what Claude Code escapes when it wraps `<result>` text in XML. */
|
|
114
|
+
function decodeXmlEntities(text) {
|
|
115
|
+
return text
|
|
116
|
+
.replace(/</g, "<")
|
|
117
|
+
.replace(/>/g, ">")
|
|
118
|
+
.replace(/"/g, '"')
|
|
119
|
+
.replace(/'/g, "'")
|
|
120
|
+
.replace(/&/g, "&");
|
|
121
|
+
}
|
|
122
|
+
/** Sørensen–Dice coefficient over character bigrams — a cheap, symmetric textual-overlap measure. */
|
|
123
|
+
function diceSimilarity(a, b) {
|
|
124
|
+
if (a.length < 2 || b.length < 2)
|
|
125
|
+
return a === b ? 1 : 0;
|
|
126
|
+
const bigrams = (s) => {
|
|
127
|
+
const counts = new Map();
|
|
128
|
+
for (let i = 0; i < s.length - 1; i++) {
|
|
129
|
+
const bg = s.slice(i, i + 2);
|
|
130
|
+
counts.set(bg, (counts.get(bg) ?? 0) + 1);
|
|
131
|
+
}
|
|
132
|
+
return counts;
|
|
133
|
+
};
|
|
134
|
+
const bigramsA = bigrams(a);
|
|
135
|
+
const bigramsB = bigrams(b);
|
|
136
|
+
let intersection = 0;
|
|
137
|
+
let totalA = 0;
|
|
138
|
+
let totalB = 0;
|
|
139
|
+
for (const count of bigramsA.values())
|
|
140
|
+
totalA += count;
|
|
141
|
+
for (const count of bigramsB.values())
|
|
142
|
+
totalB += count;
|
|
143
|
+
for (const [bg, count] of bigramsA) {
|
|
144
|
+
const other = bigramsB.get(bg);
|
|
145
|
+
if (other)
|
|
146
|
+
intersection += Math.min(count, other);
|
|
147
|
+
}
|
|
148
|
+
return totalA + totalB === 0 ? 1 : (2 * intersection) / (totalA + totalB);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Stub out a parent's `<task-notification>` event when its `<result>` is a
|
|
152
|
+
* near-duplicate of a subagent transcript's own event that ALSO survived
|
|
153
|
+
* into this same kept set (#839).
|
|
154
|
+
*
|
|
155
|
+
* After #830 folds a session's subagent transcripts into its event stream,
|
|
156
|
+
* a completed subagent's report can appear twice: once as the subagent's own
|
|
157
|
+
* folded final event, once as the parent's `<task-notification>` record of
|
|
158
|
+
* that same call — the notification wraps the subagent's own text almost
|
|
159
|
+
* verbatim (Claude Code XML-escapes `<`/`>`/`&`/quotes in the `<result>`
|
|
160
|
+
* body, which {@link decodeXmlEntities} reverses before comparing). Direction
|
|
161
|
+
* is owner-decided (#839): drop the parent's copy, keep the subagent's
|
|
162
|
+
* original — the inverse was evaluated and rejected in #836 because some
|
|
163
|
+
* subagent transcripts consist ONLY of their terminal event, so dropping it
|
|
164
|
+
* would destroy the harvesting #830 added.
|
|
165
|
+
*
|
|
166
|
+
* **Runs on `kept` — the FINAL post-budget list — not the raw stream**, and
|
|
167
|
+
* only stubs a notification when a matching subagent event is ALSO present
|
|
168
|
+
* in that same `kept` list. This is required, not incidental: the recency-
|
|
169
|
+
* biased budget already evicts one side of most raw duplicate pairs before
|
|
170
|
+
* dedupe would matter (#840's design doc measured zero pairs where both
|
|
171
|
+
* copies reached the pre-dedupe prompt across four real sessions), and any
|
|
172
|
+
* future prompt-composition design that stops including subagent-origin
|
|
173
|
+
* events in the prompt at all (#840's recommended "harvest-without-
|
|
174
|
+
* prompting hybrid") makes the parent's `<task-notification>` the ONLY
|
|
175
|
+
* surviving trace of that delegated work. An unconditional raw-stream stub
|
|
176
|
+
* would delete that sole copy the moment the subagent's own event is absent
|
|
177
|
+
* for ANY reason — evicted by budget today, or never present by design
|
|
178
|
+
* tomorrow. Scoping to "both sides survived into the same kept set" makes
|
|
179
|
+
* this dedupe a no-op whenever there is only one copy left to dedupe
|
|
180
|
+
* against, which is exactly the case where deleting it would be a bug, not
|
|
181
|
+
* a fix.
|
|
182
|
+
*
|
|
183
|
+
* Matching is scoped by `<task-id>` (which is the subagent's agentId) to the
|
|
184
|
+
* SPECIFIC subagent transcript it names, via the `agent-<agentId>.jsonl`
|
|
185
|
+
* filename #830's folding already stamps onto every folded event's
|
|
186
|
+
* `filePath` — then requires the decoded `<result>` to be a near-duplicate
|
|
187
|
+
* (Dice similarity ≥ {@link DEDUPE_SIMILARITY_THRESHOLD}) of that subagent's
|
|
188
|
+
* text, not merely a same-agent match. This matters because a task-notification
|
|
189
|
+
* fires every time an agent stops (Claude Code's own note in the event: "the
|
|
190
|
+
* same task-id may notify more than once") — an EARLIER notification for a
|
|
191
|
+
* resumed agent can carry a genuinely different (intermediate) result that
|
|
192
|
+
* must NOT be stubbed just because the ids line up.
|
|
193
|
+
*
|
|
194
|
+
* The event is kept (not dropped) so event counts/timestamps stay stable and
|
|
195
|
+
* the parent's narrative — *why* it delegated — survives as a short stub:
|
|
196
|
+
* `[subagent <agentId> completed: <description>]`.
|
|
197
|
+
*/
|
|
198
|
+
function dedupeTaskNotifications(kept) {
|
|
199
|
+
// Index the KEPT subagent events by the agentId embedded in their
|
|
200
|
+
// transcript's filename, so a notification's <task-id> narrows the
|
|
201
|
+
// comparison to the ONE subagent it reports on — and so an agentId with no
|
|
202
|
+
// surviving event here means "nothing to dedupe against", not "assume it
|
|
203
|
+
// exists upstream".
|
|
204
|
+
const byAgentId = new Map();
|
|
205
|
+
for (const event of kept) {
|
|
206
|
+
const agentId = event.filePath?.match(SUBAGENT_FILEPATH_RE)?.[1];
|
|
207
|
+
if (!agentId)
|
|
208
|
+
continue;
|
|
209
|
+
const list = byAgentId.get(agentId);
|
|
210
|
+
if (list)
|
|
211
|
+
list.push(event);
|
|
212
|
+
else
|
|
213
|
+
byAgentId.set(agentId, [event]);
|
|
214
|
+
}
|
|
215
|
+
if (byAgentId.size === 0)
|
|
216
|
+
return kept; // no folded subagent survived the budget — nothing to dedupe
|
|
217
|
+
return kept.map((event) => {
|
|
218
|
+
if (event.role !== "user" || !TASK_NOTIFICATION_RE.test(event.text))
|
|
219
|
+
return event;
|
|
220
|
+
const taskId = event.text.match(TASK_ID_RE)?.[1];
|
|
221
|
+
const resultRaw = event.text.match(RESULT_RE)?.[1];
|
|
222
|
+
if (!taskId || !resultRaw)
|
|
223
|
+
return event; // no <result> (e.g. a background-command notification) — nothing to compare
|
|
224
|
+
const candidates = byAgentId.get(taskId);
|
|
225
|
+
if (!candidates || candidates.length === 0)
|
|
226
|
+
return event; // that subagent's own event didn't survive into this kept set
|
|
227
|
+
const decodedResult = decodeXmlEntities(resultRaw);
|
|
228
|
+
const isDuplicate = candidates.some((c) => diceSimilarity(decodedResult, c.text.replace(PROVENANCE_PREFIX_RE, "")) >= DEDUPE_SIMILARITY_THRESHOLD);
|
|
229
|
+
if (!isDuplicate)
|
|
230
|
+
return event;
|
|
231
|
+
const summary = event.text.match(SUMMARY_RE)?.[1]?.trim();
|
|
232
|
+
const description = (summary && (summary.match(AGENT_SUMMARY_DESCRIPTION_RE)?.[1] ?? summary)) || "completed";
|
|
233
|
+
return { ...event, text: `[subagent ${taskId} completed: ${description}]` };
|
|
234
|
+
});
|
|
235
|
+
}
|
|
93
236
|
export function preFilterSession(data, options = {}) {
|
|
94
237
|
const akmReadOnlyOps = options.akmReadOnlyOps ?? DEFAULT_AKM_READONLY_OPS;
|
|
95
238
|
const maxLen = options.maxEventTextLength ?? DEFAULT_MAX_EVENT_LENGTH;
|
|
96
239
|
const maxTotalChars = options.maxTotalChars ?? DEFAULT_MAX_TOTAL_CHARS;
|
|
97
240
|
const droppedByRule = {};
|
|
98
|
-
|
|
241
|
+
let kept = [];
|
|
99
242
|
let truncatedCount = 0;
|
|
100
243
|
const candidates = [];
|
|
101
244
|
for (const event of data.events) {
|
|
@@ -138,6 +281,13 @@ export function preFilterSession(data, options = {}) {
|
|
|
138
281
|
if (c.truncated)
|
|
139
282
|
truncatedCount += 1;
|
|
140
283
|
}
|
|
284
|
+
// Post-pass (#839): dedupe a task-notification against a subagent event
|
|
285
|
+
// ONLY when both survived into this exact kept set — see
|
|
286
|
+
// dedupeTaskNotifications's doc for why that scoping is required. Recompute
|
|
287
|
+
// totalChars afterward since stubbing can only shrink kept text, never move
|
|
288
|
+
// anything across the budget boundary already decided above.
|
|
289
|
+
kept = dedupeTaskNotifications(kept);
|
|
290
|
+
const finalTotalChars = kept.reduce((sum, e) => sum + e.text.length, 0);
|
|
141
291
|
return {
|
|
142
292
|
events: kept,
|
|
143
293
|
stats: {
|
|
@@ -145,7 +295,7 @@ export function preFilterSession(data, options = {}) {
|
|
|
145
295
|
outputCount: kept.length,
|
|
146
296
|
droppedByRule,
|
|
147
297
|
truncatedCount,
|
|
148
|
-
totalChars,
|
|
298
|
+
totalChars: finalTotalChars,
|
|
149
299
|
budgetDroppedCount,
|
|
150
300
|
},
|
|
151
301
|
};
|
|
@@ -53,6 +53,7 @@ const PASSTHROUGH_COMMANDS = [
|
|
|
53
53
|
"sync",
|
|
54
54
|
"task-add",
|
|
55
55
|
"task-doctor",
|
|
56
|
+
"task-explain",
|
|
56
57
|
"task-history",
|
|
57
58
|
"task-run",
|
|
58
59
|
"task-sync",
|
|
@@ -61,6 +62,7 @@ const PASSTHROUGH_COMMANDS = [
|
|
|
61
62
|
"workflow-abandon",
|
|
62
63
|
"workflow-create",
|
|
63
64
|
"workflow-list",
|
|
65
|
+
"workflow-plan",
|
|
64
66
|
"workflow-resume",
|
|
65
67
|
"workflow-run",
|
|
66
68
|
"workflow-status",
|
|
@@ -20,4 +20,4 @@ export { formatHealthPlain } from "./health-format.js";
|
|
|
20
20
|
export { formatLintPlain } from "./lint-format.js";
|
|
21
21
|
export { formatGateDecisionSummary, formatProposalAcceptPlain, formatProposalDiffPlain, formatProposalDrainPlain, formatProposalListPlain, formatProposalProducerPlain, formatProposalRejectPlain, formatProposalShowPlain, } from "./proposal-format.js";
|
|
22
22
|
export { formatShowPlain } from "./show-format.js";
|
|
23
|
-
export { formatWorkflowCreatePlain, formatWorkflowListPlain, formatWorkflowResumePlain, formatWorkflowRunPlain, formatWorkflowStatusPlain, } from "./workflow-format.js";
|
|
23
|
+
export { formatWorkflowCreatePlain, formatWorkflowListPlain, formatWorkflowPlanPlain, formatWorkflowResumePlain, formatWorkflowRunPlain, formatWorkflowStatusPlain, } from "./workflow-format.js";
|
|
@@ -20,16 +20,25 @@ export function formatMigratePlain(result) {
|
|
|
20
20
|
const lines = [`${planGlyph(plan.status)} ${plan.status}`];
|
|
21
21
|
if (plan.taskV3Migration) {
|
|
22
22
|
const tasks = plan.taskV3Migration;
|
|
23
|
-
lines.push(`
|
|
23
|
+
lines.push(` task-v2->v3: ${tasks.changed} change, ${tasks.skipped} current, ${tasks.blocked} blocked`);
|
|
24
|
+
}
|
|
25
|
+
if (plan.taskV4Migration) {
|
|
26
|
+
const tasks = plan.taskV4Migration;
|
|
27
|
+
lines.push(` task-v3->v4: ${tasks.changed} change, ${tasks.skipped} current, ${tasks.blocked} blocked`);
|
|
24
28
|
}
|
|
25
29
|
if (plan.blockers?.length) {
|
|
26
30
|
lines.push("", "blockers:", ...plan.blockers.map((blocker) => ` - ${blocker}`));
|
|
27
31
|
}
|
|
28
32
|
if (plan.backupPath) {
|
|
29
|
-
lines.push("", `backup: ${plan.backupPath}`);
|
|
33
|
+
lines.push("", `backup (v2->v3): ${plan.backupPath}`);
|
|
30
34
|
}
|
|
31
35
|
if (plan.applied !== undefined)
|
|
32
|
-
lines.push(`applied: ${plan.applied}`);
|
|
36
|
+
lines.push(`applied (v2->v3): ${plan.applied}`);
|
|
37
|
+
if (plan.taskV4BackupPath) {
|
|
38
|
+
lines.push(`backup (v3->v4): ${plan.taskV4BackupPath}`);
|
|
39
|
+
}
|
|
40
|
+
if (plan.taskV4Applied !== undefined)
|
|
41
|
+
lines.push(`applied (v3->v4): ${plan.taskV4Applied}`);
|
|
33
42
|
return lines.join("\n");
|
|
34
43
|
}
|
|
35
44
|
export const migrateFormatters = [
|
|
@@ -27,6 +27,35 @@ export function formatWorkflowListPlain(result) {
|
|
|
27
27
|
})
|
|
28
28
|
.join("\n");
|
|
29
29
|
}
|
|
30
|
+
/** §4.5's glyph table (P3b) — drawn from the repo's existing vocabulary (status-list.ts's ✗/✓, proposal-format.ts's →). */
|
|
31
|
+
const CHILD_STATUS_GLYPHS = {
|
|
32
|
+
completed: "✓",
|
|
33
|
+
active: "→",
|
|
34
|
+
blocked: "⚠",
|
|
35
|
+
failed: "✗",
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Render one `children:` tree node (P3b, spec §4.5) plus its own children,
|
|
39
|
+
* recursively, indenting two spaces per level. Order is SPAWN order (the
|
|
40
|
+
* envelope's own array order) — the tree is structural, never severity-sorted.
|
|
41
|
+
*/
|
|
42
|
+
function renderChildNode(child, depth, lines) {
|
|
43
|
+
const indent = " ".repeat(depth + 1);
|
|
44
|
+
const runId = typeof child.runId === "string" ? child.runId : "unknown";
|
|
45
|
+
const ref = typeof child.workflowRef === "string" ? child.workflowRef : "unknown";
|
|
46
|
+
const status = typeof child.status === "string" ? child.status : "unknown";
|
|
47
|
+
const glyph = CHILD_STATUS_GLYPHS[status] ?? "?";
|
|
48
|
+
const stepId = typeof child.stepId === "string" ? ` (step "${child.stepId}")` : "";
|
|
49
|
+
lines.push(`${indent}- ${glyph} ${runId} ${ref} [${status}]${stepId}`);
|
|
50
|
+
const resume = typeof child.resume === "object" && child.resume !== null ? child.resume : undefined;
|
|
51
|
+
if (resume) {
|
|
52
|
+
lines.push(`${indent} resume: ${typeof resume.command === "string" ? resume.command : ""}`);
|
|
53
|
+
lines.push(`${indent} then: ${typeof resume.then === "string" ? resume.then : ""}`);
|
|
54
|
+
}
|
|
55
|
+
const nested = Array.isArray(child.children) ? child.children : [];
|
|
56
|
+
for (const grandchild of nested)
|
|
57
|
+
renderChildNode(grandchild, depth + 1, lines);
|
|
58
|
+
}
|
|
30
59
|
export function formatWorkflowStatusPlain(result) {
|
|
31
60
|
const run = typeof result.run === "object" && result.run !== null ? result.run : undefined;
|
|
32
61
|
const workflow = typeof result.workflow === "object" && result.workflow !== null
|
|
@@ -55,6 +84,16 @@ export function formatWorkflowStatusPlain(result) {
|
|
|
55
84
|
}
|
|
56
85
|
}
|
|
57
86
|
}
|
|
87
|
+
// The parent-child status tree (P3b, spec §4.5): renders ONLY when
|
|
88
|
+
// `children` is a non-empty array, immediately after `steps:` and before
|
|
89
|
+
// `units:`. Absent for a childless run — byte-identical to pre-P3b text
|
|
90
|
+
// (row B-33, PRESERVE).
|
|
91
|
+
const children = Array.isArray(result.children) ? result.children : undefined;
|
|
92
|
+
if (children && children.length > 0) {
|
|
93
|
+
lines.push("children:");
|
|
94
|
+
for (const child of children)
|
|
95
|
+
renderChildNode(child, 0, lines);
|
|
96
|
+
}
|
|
58
97
|
// `workflow status --units` (#22): the honest per-unit diagnostic surface —
|
|
59
98
|
// failure_reason plus any journaled result/error text. Diagnostics only; the
|
|
60
99
|
// deterministic step evidence above is unaffected.
|
|
@@ -108,6 +147,27 @@ function formatWorkflowCheckinLine(result) {
|
|
|
108
147
|
return null;
|
|
109
148
|
return checkin.directive.trim();
|
|
110
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* One `! lowering[<severity>] <code> (<adapter>[; <field>]): <message>` line
|
|
152
|
+
* from a `{code, severity, adapter, field?, message}` lowering notice, or
|
|
153
|
+
* `null` when `value` does not carry that shape. Shared by
|
|
154
|
+
* `formatWorkflowRunPlain` (dispatch-time notices) and `formatWorkflowPlanPlain`
|
|
155
|
+
* (freeze-time notices, P3b §4.6, B-57) — ONE projection, rendered the same
|
|
156
|
+
* way regardless of which invocation computed the notice.
|
|
157
|
+
*/
|
|
158
|
+
function renderLoweringNoticeLine(value) {
|
|
159
|
+
if (typeof value !== "object" || value === null)
|
|
160
|
+
return null;
|
|
161
|
+
const notice = value;
|
|
162
|
+
if (typeof notice.code !== "string" ||
|
|
163
|
+
typeof notice.severity !== "string" ||
|
|
164
|
+
typeof notice.adapter !== "string" ||
|
|
165
|
+
typeof notice.message !== "string") {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const field = typeof notice.field === "string" && notice.field ? `; ${notice.field}` : "";
|
|
169
|
+
return `! lowering[${notice.severity}] ${notice.code} (${notice.adapter}${field}): ${notice.message}`;
|
|
170
|
+
}
|
|
111
171
|
export function formatWorkflowRunPlain(result) {
|
|
112
172
|
const run = typeof result.run === "object" && result.run !== null ? result.run : undefined;
|
|
113
173
|
if (!run)
|
|
@@ -123,16 +183,9 @@ export function formatWorkflowRunPlain(result) {
|
|
|
123
183
|
// default text output has the same observability as JSON without widening a
|
|
124
184
|
// durable workflow schema.
|
|
125
185
|
for (const value of Array.isArray(result.notices) ? result.notices : []) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (typeof notice.code !== "string" ||
|
|
130
|
-
typeof notice.severity !== "string" ||
|
|
131
|
-
typeof notice.adapter !== "string" ||
|
|
132
|
-
typeof notice.message !== "string")
|
|
133
|
-
continue;
|
|
134
|
-
const field = typeof notice.field === "string" && notice.field ? `; ${notice.field}` : "";
|
|
135
|
-
lines.push(`! lowering[${notice.severity}] ${notice.code} (${notice.adapter}${field}): ${notice.message}`);
|
|
186
|
+
const line = renderLoweringNoticeLine(value);
|
|
187
|
+
if (line)
|
|
188
|
+
lines.push(line);
|
|
136
189
|
}
|
|
137
190
|
const executed = Array.isArray(result.executed) ? result.executed : [];
|
|
138
191
|
if (executed.length === 0) {
|
|
@@ -180,3 +233,132 @@ export function formatWorkflowCreatePlain(r) {
|
|
|
180
233
|
export function formatWorkflowResumePlain(r) {
|
|
181
234
|
return formatWorkflowStatusPlain(r) ?? `Resumed workflow run ${String(r.id ?? r.runId ?? "?")}`;
|
|
182
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* A step's `inputBindings[]` (P3b §4.6's `with:` line) — a literal shows its
|
|
238
|
+
* value, a reference shows only its unresolved `from` (never a resolved
|
|
239
|
+
* value; B-52/B-53's closed print list explicitly allows a literal *task/
|
|
240
|
+
* child* input binding's `.value`, unlike a literal **environment** binding's).
|
|
241
|
+
*/
|
|
242
|
+
function formatInputBindingsList(bindings) {
|
|
243
|
+
return bindings
|
|
244
|
+
.map((binding) => {
|
|
245
|
+
const name = String(binding.name ?? "");
|
|
246
|
+
return binding.kind === "literal"
|
|
247
|
+
? `${name}=${JSON.stringify(binding.value)} (literal)`
|
|
248
|
+
: `${name} <- ${String(binding.from ?? "")} (reference)`;
|
|
249
|
+
})
|
|
250
|
+
.join(", ");
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* One `akm workflow plan` step line (P3b, spec §4.6), plus its child-workflow
|
|
254
|
+
* expansion's own steps, recursively, indented one level per composition
|
|
255
|
+
* boundary. A child step also carries the child's `planHash` on the step
|
|
256
|
+
* line itself, and (when present) a `with:` line for the step's own
|
|
257
|
+
* `inputBindings[]` and an `exports:` line for the child's declared output
|
|
258
|
+
* names — §4.6's worked example.
|
|
259
|
+
*/
|
|
260
|
+
function renderPlanStepLines(step, ordinal, lines, prefix = " ") {
|
|
261
|
+
const stepId = typeof step.stepId === "string" ? step.stepId : "unknown";
|
|
262
|
+
const targetKind = typeof step.targetKind === "string" ? `[${step.targetKind}]` : "";
|
|
263
|
+
const expansion = typeof step.expansion === "object" && step.expansion !== null ? step.expansion : {};
|
|
264
|
+
const via = typeof expansion.via === "string" ? expansion.via : "direct";
|
|
265
|
+
const childPlanHash = typeof expansion.childPlanHash === "string" ? expansion.childPlanHash : undefined;
|
|
266
|
+
const viaText = via === "task"
|
|
267
|
+
? `via ${String(expansion.taskRef ?? "")}`
|
|
268
|
+
: via === "child"
|
|
269
|
+
? `-> ${String(expansion.childRef ?? "")}${childPlanHash ? ` (plan ${childPlanHash})` : ""}`
|
|
270
|
+
: "direct";
|
|
271
|
+
lines.push(`${prefix}${ordinal}. ${stepId} ${targetKind} ${viaText}`.replace(/ +/g, " ").trimEnd());
|
|
272
|
+
const detailPrefix = `${prefix} `;
|
|
273
|
+
const inputBindings = Array.isArray(step.inputBindings) ? step.inputBindings : [];
|
|
274
|
+
if (inputBindings.length > 0) {
|
|
275
|
+
lines.push(`${detailPrefix}with: ${formatInputBindingsList(inputBindings)}`);
|
|
276
|
+
}
|
|
277
|
+
if (via === "child") {
|
|
278
|
+
const childOutputs = Array.isArray(expansion.childOutputs) ? expansion.childOutputs : [];
|
|
279
|
+
if (childOutputs.length > 0) {
|
|
280
|
+
lines.push(`${detailPrefix}exports: ${childOutputs.map(String).join(", ")}`);
|
|
281
|
+
}
|
|
282
|
+
const childSteps = Array.isArray(expansion.steps) ? expansion.steps : [];
|
|
283
|
+
for (const [index, childStep] of childSteps.entries()) {
|
|
284
|
+
renderPlanStepLines(childStep, index + 1, lines, `${prefix} ${ordinal}.`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* `akm workflow plan <ref>` text mode (P3b, spec §4.6): a human summary of
|
|
290
|
+
* the same compile+freeze data `--format json` returns — never a resolved
|
|
291
|
+
* env/secret value (B-52, B-53).
|
|
292
|
+
*/
|
|
293
|
+
export function formatWorkflowPlanPlain(result) {
|
|
294
|
+
if (typeof result.ref !== "string")
|
|
295
|
+
return null;
|
|
296
|
+
const sourceFormat = typeof result.sourceFormat === "string" ? result.sourceFormat : "unknown";
|
|
297
|
+
const irVersion = typeof result.irVersion === "number" ? result.irVersion : "unknown";
|
|
298
|
+
const planHash = typeof result.planHash === "string" ? result.planHash : "unknown";
|
|
299
|
+
const execution = typeof result.execution === "object" && result.execution !== null
|
|
300
|
+
? result.execution
|
|
301
|
+
: {};
|
|
302
|
+
const budget = typeof result.budget === "object" && result.budget !== null
|
|
303
|
+
? result.budget
|
|
304
|
+
: undefined;
|
|
305
|
+
const lines = [
|
|
306
|
+
`workflow: ${result.ref} (${sourceFormat})`,
|
|
307
|
+
`source: ${typeof result.sourcePath === "string" ? result.sourcePath : "unknown"}`,
|
|
308
|
+
`plan: irVersion ${irVersion}, hash ${planHash} (not published)`,
|
|
309
|
+
];
|
|
310
|
+
const limitsParts = [
|
|
311
|
+
`maxConcurrency ${typeof execution.maxConcurrency === "number" ? execution.maxConcurrency : "?"}`,
|
|
312
|
+
];
|
|
313
|
+
if (budget) {
|
|
314
|
+
const budgetParts = [];
|
|
315
|
+
if (typeof budget.maxUnits === "number")
|
|
316
|
+
budgetParts.push(`max_units ${budget.maxUnits}`);
|
|
317
|
+
if (typeof budget.maxTokens === "number")
|
|
318
|
+
budgetParts.push(`max_tokens ${budget.maxTokens}`);
|
|
319
|
+
if (budgetParts.length > 0)
|
|
320
|
+
limitsParts.push(`budget ${budgetParts.join(", ")}`);
|
|
321
|
+
}
|
|
322
|
+
lines.push(`limits: ${limitsParts.join("; ")}`);
|
|
323
|
+
const params = Array.isArray(result.params) ? result.params : undefined;
|
|
324
|
+
if (params && params.length > 0)
|
|
325
|
+
lines.push(`params: ${params.join(", ")}`);
|
|
326
|
+
const outputs = typeof result.outputs === "object" && result.outputs !== null
|
|
327
|
+
? result.outputs
|
|
328
|
+
: undefined;
|
|
329
|
+
if (outputs) {
|
|
330
|
+
for (const [name, declaration] of Object.entries(outputs)) {
|
|
331
|
+
const from = typeof declaration === "object" && declaration !== null
|
|
332
|
+
? String(declaration.from ?? "")
|
|
333
|
+
: "";
|
|
334
|
+
lines.push(`outputs: ${name} <- ${from}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const steps = Array.isArray(result.steps) ? result.steps : [];
|
|
338
|
+
lines.push("steps:");
|
|
339
|
+
for (const [index, step] of steps.entries()) {
|
|
340
|
+
renderPlanStepLines(step, index + 1, lines);
|
|
341
|
+
}
|
|
342
|
+
const sourceReadSet = Array.isArray(result.sourceReadSet) ? result.sourceReadSet : [];
|
|
343
|
+
if (sourceReadSet.length > 0) {
|
|
344
|
+
lines.push("read set:");
|
|
345
|
+
for (const entry of sourceReadSet)
|
|
346
|
+
lines.push(` ${String(entry)}`);
|
|
347
|
+
}
|
|
348
|
+
const notices = Array.isArray(result.notices) ? result.notices : [];
|
|
349
|
+
if (notices.length > 0) {
|
|
350
|
+
lines.push("notices:");
|
|
351
|
+
for (const value of notices) {
|
|
352
|
+
const line = renderLoweringNoticeLine(value);
|
|
353
|
+
if (line)
|
|
354
|
+
lines.push(` ${line}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const warnings = Array.isArray(result.warnings) ? result.warnings : [];
|
|
358
|
+
if (warnings.length > 0) {
|
|
359
|
+
lines.push("warnings:");
|
|
360
|
+
for (const warning of warnings)
|
|
361
|
+
lines.push(` ! ${String(warning)}`);
|
|
362
|
+
}
|
|
363
|
+
return lines.join("\n");
|
|
364
|
+
}
|
|
@@ -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
|
// Output text formatters for all `akm workflow *` commands.
|
|
5
|
-
import { formatWorkflowCreatePlain, formatWorkflowListPlain, formatWorkflowResumePlain, formatWorkflowRunPlain, formatWorkflowStatusPlain, } from "./helpers.js";
|
|
5
|
+
import { formatWorkflowCreatePlain, formatWorkflowListPlain, formatWorkflowPlanPlain, formatWorkflowResumePlain, formatWorkflowRunPlain, formatWorkflowStatusPlain, } from "./helpers.js";
|
|
6
6
|
export const workflowFormatters = [
|
|
7
7
|
{ command: "workflow-status", handler: (r) => formatWorkflowStatusPlain(r) },
|
|
8
8
|
{ command: "workflow-list", handler: (r) => formatWorkflowListPlain(r) },
|
|
@@ -10,4 +10,5 @@ export const workflowFormatters = [
|
|
|
10
10
|
{ command: "workflow-resume", handler: (r) => formatWorkflowResumePlain(r) },
|
|
11
11
|
{ command: "workflow-abandon", handler: (r) => formatWorkflowStatusPlain(r) },
|
|
12
12
|
{ command: "workflow-run", handler: (r) => formatWorkflowRunPlain(r) },
|
|
13
|
+
{ command: "workflow-plan", handler: (r) => formatWorkflowPlanPlain(r) },
|
|
13
14
|
];
|
package/dist/registry/semver.js
CHANGED
|
@@ -41,3 +41,7 @@ export function isSemverRange(input) {
|
|
|
41
41
|
export function maxSatisfying(versions, range) {
|
|
42
42
|
return semver.maxSatisfying(versions, range) ?? undefined;
|
|
43
43
|
}
|
|
44
|
+
/** True when `version` satisfies `range` (both real semver forms). False for an invalid version or range. */
|
|
45
|
+
export function satisfiesRange(version, range) {
|
|
46
|
+
return semver.satisfies(version, range);
|
|
47
|
+
}
|
package/dist/runtime.js
CHANGED
|
@@ -77,6 +77,7 @@ function nodeSpawnAdapter(cmd, options) {
|
|
|
77
77
|
env: options.env ?? process.env,
|
|
78
78
|
detached: options.detached,
|
|
79
79
|
stdio: [stdioFor(options.stdin), stdioFor(options.stdout), stdioFor(options.stderr)],
|
|
80
|
+
windowsVerbatimArguments: options.windowsVerbatimArguments,
|
|
80
81
|
});
|
|
81
82
|
// Node's 'exit' fires (null, signal) when the child dies from a signal.
|
|
82
83
|
// Resolving `code ?? 0` reported that as SUCCESS, so an OOM-killed or
|