akm-cli 0.9.17-alpha.7 → 0.9.17-alpha.8
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 +139 -0
- package/dist/akm +55 -22
- package/dist/akm-migrate +38 -19
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +5 -3
- package/dist/commands/improve/loop-stages.js +4 -3
- package/dist/commands/read/curate.js +40 -13
- package/dist/commands/read/show.js +55 -2
- package/dist/commands/sources/info.js +3 -0
- package/dist/core/adapter/adapters/akm-adapter.js +2 -0
- package/dist/core/adapter/adapters/akm-metadata.js +31 -0
- package/dist/indexer/db/graph-db.js +0 -32
- package/dist/indexer/graph/graph-extraction.js +3 -1
- package/dist/indexer/links/declared-links.js +90 -0
- package/dist/indexer/scan/doc-to-entry.js +1 -0
- package/dist/llm/graph-extract.js +26 -37
- package/dist/output/shapes/helpers.js +3 -0
- package/dist/output/text/show-format.js +16 -0
- package/dist/scripts/akm-migrate-node.js +351 -400
- package/dist/scripts/akm-migrate.js +351 -400
- package/dist/storage/repositories/index-entries-repository.js +12 -6
- package/dist/storage/repositories/index-entry-schema.js +18 -1
- package/dist/storage/repositories/index-links-repository.js +143 -0
- package/dist/storage/repositories/index-schema.js +27 -0
- package/dist/tasks/source/task-to-v4.js +462 -74
- package/docs/migration/release-notes/0.9.17.md +7 -5
- package/docs/reference/cli.md +22 -7
- package/package.json +1 -1
- package/dist/tasks/source/task-to-v3.js +0 -453
|
@@ -28125,6 +28125,7 @@ function shapeShowOutput(result, detail, shape = "human") {
|
|
|
28125
28125
|
"steps",
|
|
28126
28126
|
"keys",
|
|
28127
28127
|
"related",
|
|
28128
|
+
"links",
|
|
28128
28129
|
...FRAGMENT_PROVENANCE_FIELDS,
|
|
28129
28130
|
...FRAGMENT_CONTEXT_FIELDS
|
|
28130
28131
|
]);
|
|
@@ -28146,6 +28147,7 @@ function shapeShowOutput(result, detail, shape = "human") {
|
|
|
28146
28147
|
"origin",
|
|
28147
28148
|
"keys",
|
|
28148
28149
|
"related",
|
|
28150
|
+
"links",
|
|
28149
28151
|
...FRAGMENT_PROVENANCE_FIELDS,
|
|
28150
28152
|
...FRAGMENT_CONTEXT_FIELDS
|
|
28151
28153
|
]);
|
|
@@ -28174,6 +28176,7 @@ function shapeShowOutput(result, detail, shape = "human") {
|
|
|
28174
28176
|
"activeRun",
|
|
28175
28177
|
"keys",
|
|
28176
28178
|
"related",
|
|
28179
|
+
"links",
|
|
28177
28180
|
...FRAGMENT_PROVENANCE_FIELDS,
|
|
28178
28181
|
...FRAGMENT_CONTEXT_FIELDS,
|
|
28179
28182
|
"path",
|
|
@@ -30181,6 +30184,22 @@ function formatShowPlain(r, detail) {
|
|
|
30181
30184
|
lines.push(` relationCount: ${String(hit.relationCount ?? 0)}`);
|
|
30182
30185
|
}
|
|
30183
30186
|
}
|
|
30187
|
+
const links = typeof r.links === "object" && r.links !== null ? r.links : undefined;
|
|
30188
|
+
if (links) {
|
|
30189
|
+
lines.push("");
|
|
30190
|
+
lines.push("links:");
|
|
30191
|
+
for (const part of ["outgoing", "incoming", "unresolved"]) {
|
|
30192
|
+
const groups = links[part];
|
|
30193
|
+
if (typeof groups !== "object" || groups === null)
|
|
30194
|
+
continue;
|
|
30195
|
+
lines.push(` ${part}:`);
|
|
30196
|
+
for (const [kind, group] of Object.entries(groups)) {
|
|
30197
|
+
const refs = Array.isArray(group.refs) ? group.refs.map(String) : [];
|
|
30198
|
+
const more = (group.total ?? refs.length) > refs.length ? ` (+${(group.total ?? 0) - refs.length} more)` : "";
|
|
30199
|
+
lines.push(` ${kind}: ${refs.join(", ")}${more}`);
|
|
30200
|
+
}
|
|
30201
|
+
}
|
|
30202
|
+
}
|
|
30184
30203
|
const payloads = [r.content, r.template, r.prompt].filter((value) => value != null).map(String);
|
|
30185
30204
|
if (Array.isArray(r.steps) && r.steps.length > 0) {
|
|
30186
30205
|
if (lines.length > 0)
|
|
@@ -43625,6 +43644,22 @@ function applyFrontmatterDescriptionAndTags(fm, out) {
|
|
|
43625
43644
|
}
|
|
43626
43645
|
}
|
|
43627
43646
|
}
|
|
43647
|
+
function storedCommandRef(action) {
|
|
43648
|
+
return action?.kind === "stored" ? action.ref : undefined;
|
|
43649
|
+
}
|
|
43650
|
+
function workflowStepTarget(spec) {
|
|
43651
|
+
if (!spec?.uses)
|
|
43652
|
+
return;
|
|
43653
|
+
if (spec.uses !== "akm/command")
|
|
43654
|
+
return spec.uses;
|
|
43655
|
+
if (spec.commandMode !== "stored-ref")
|
|
43656
|
+
return;
|
|
43657
|
+
try {
|
|
43658
|
+
return storedCommandRef(parseBuiltinCommandAction(spec.with));
|
|
43659
|
+
} catch {
|
|
43660
|
+
return;
|
|
43661
|
+
}
|
|
43662
|
+
}
|
|
43628
43663
|
function finalizeHints(out, hints) {
|
|
43629
43664
|
if (hints.size > 0)
|
|
43630
43665
|
out.searchHints = Array.from(hints).filter(Boolean);
|
|
@@ -43718,6 +43753,9 @@ function foldRecognizedMetadata(rendererName, file) {
|
|
|
43718
43753
|
hints.add(`prompt:${target.command.ref}`);
|
|
43719
43754
|
else
|
|
43720
43755
|
hints.add(`uses:${target.uses.ref}`);
|
|
43756
|
+
const used = target.uses.kind !== "builtin-command" ? target.uses.ref : storedCommandRef(target.command);
|
|
43757
|
+
if (used)
|
|
43758
|
+
out.uses = [used];
|
|
43721
43759
|
} else {
|
|
43722
43760
|
hints.add(`run:${target.run}`);
|
|
43723
43761
|
}
|
|
@@ -43781,6 +43819,7 @@ function foldRecognizedMetadata(rendererName, file) {
|
|
|
43781
43819
|
return out;
|
|
43782
43820
|
const plan = result.plan;
|
|
43783
43821
|
const hints = new Set;
|
|
43822
|
+
const uses = new Set;
|
|
43784
43823
|
if (plan.preamble)
|
|
43785
43824
|
hints.add(plan.preamble);
|
|
43786
43825
|
for (const step of plan.steps) {
|
|
@@ -43788,8 +43827,13 @@ function foldRecognizedMetadata(rendererName, file) {
|
|
|
43788
43827
|
hints.add(workflowStepInstructions(step));
|
|
43789
43828
|
if (step.gate.criteria[0])
|
|
43790
43829
|
hints.add(step.gate.criteria[0]);
|
|
43830
|
+
const used = workflowStepTarget(step.spec);
|
|
43831
|
+
if (used)
|
|
43832
|
+
uses.add(used);
|
|
43791
43833
|
}
|
|
43792
43834
|
out.searchHints = Array.from(hints).filter(Boolean);
|
|
43835
|
+
if (uses.size > 0)
|
|
43836
|
+
out.uses = [...uses];
|
|
43793
43837
|
if (plan.paramSchemas) {
|
|
43794
43838
|
const parameters = Object.entries(plan.paramSchemas).map(([name, schema]) => {
|
|
43795
43839
|
const description = schema.description;
|
|
@@ -43823,6 +43867,8 @@ function applyFoldedMetadata(entry, folded) {
|
|
|
43823
43867
|
entry.toc = folded.toc;
|
|
43824
43868
|
if (folded.parameters)
|
|
43825
43869
|
entry.parameters = folded.parameters;
|
|
43870
|
+
if (folded.uses)
|
|
43871
|
+
entry.uses = folded.uses;
|
|
43826
43872
|
}
|
|
43827
43873
|
|
|
43828
43874
|
// src/core/adapter/adapters/akm-adapter.ts
|
|
@@ -43881,6 +43927,7 @@ var DOCUMENT_JSON_CARRIED_FIELDS = [
|
|
|
43881
43927
|
"wikiRole",
|
|
43882
43928
|
"sources",
|
|
43883
43929
|
"evidenceSources",
|
|
43930
|
+
"uses",
|
|
43884
43931
|
"provenance"
|
|
43885
43932
|
];
|
|
43886
43933
|
function indexDocumentFromEntry(entry, base, rendererName) {
|
|
@@ -53207,14 +53254,14 @@ function removeDeadResidue(stashDir) {
|
|
|
53207
53254
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
53208
53255
|
import fs40 from "fs";
|
|
53209
53256
|
import os7 from "os";
|
|
53210
|
-
import
|
|
53257
|
+
import path48 from "path";
|
|
53211
53258
|
|
|
53212
53259
|
// scripts/akm-migrate/migrate/task-files.ts
|
|
53213
|
-
import
|
|
53260
|
+
import crypto4 from "crypto";
|
|
53214
53261
|
import fs39 from "fs";
|
|
53215
|
-
import
|
|
53262
|
+
import path47 from "path";
|
|
53216
53263
|
|
|
53217
|
-
// src/tasks/source/task-to-
|
|
53264
|
+
// src/tasks/source/task-to-v4.ts
|
|
53218
53265
|
import crypto3 from "crypto";
|
|
53219
53266
|
import path46 from "path";
|
|
53220
53267
|
|
|
@@ -53575,7 +53622,51 @@ function parseTaskV3Yaml(input) {
|
|
|
53575
53622
|
});
|
|
53576
53623
|
}
|
|
53577
53624
|
|
|
53578
|
-
// src/tasks/source/task-to-
|
|
53625
|
+
// src/tasks/source/task-to-v4.ts
|
|
53626
|
+
var V3_TOP_LEVEL_KEYS = new Set([
|
|
53627
|
+
"version",
|
|
53628
|
+
"name",
|
|
53629
|
+
"uses",
|
|
53630
|
+
"run",
|
|
53631
|
+
"with",
|
|
53632
|
+
"env",
|
|
53633
|
+
"shell",
|
|
53634
|
+
"working-directory",
|
|
53635
|
+
"akm",
|
|
53636
|
+
"on"
|
|
53637
|
+
]);
|
|
53638
|
+
var V3_AKM_KEYS = new Set([
|
|
53639
|
+
"schedule",
|
|
53640
|
+
"enabled",
|
|
53641
|
+
"description",
|
|
53642
|
+
"when_to_use",
|
|
53643
|
+
"tags",
|
|
53644
|
+
"agent",
|
|
53645
|
+
"engine",
|
|
53646
|
+
"model",
|
|
53647
|
+
"inference",
|
|
53648
|
+
"outputSchema",
|
|
53649
|
+
"tools",
|
|
53650
|
+
"timeout",
|
|
53651
|
+
"redact",
|
|
53652
|
+
"maxSteps",
|
|
53653
|
+
"maxRetries"
|
|
53654
|
+
]);
|
|
53655
|
+
var V3_ON_KEYS = new Set(["schedule", "workflow_dispatch"]);
|
|
53656
|
+
var AKM_HOIST_KEYS = [
|
|
53657
|
+
"description",
|
|
53658
|
+
"when_to_use",
|
|
53659
|
+
"tags",
|
|
53660
|
+
"agent",
|
|
53661
|
+
"engine",
|
|
53662
|
+
"model",
|
|
53663
|
+
"inference",
|
|
53664
|
+
"tools",
|
|
53665
|
+
"timeout",
|
|
53666
|
+
"redact",
|
|
53667
|
+
"maxSteps",
|
|
53668
|
+
"maxRetries"
|
|
53669
|
+
];
|
|
53579
53670
|
var V2_KEYS = new Set([
|
|
53580
53671
|
"version",
|
|
53581
53672
|
"name",
|
|
@@ -53646,9 +53737,13 @@ var KNOWN_PROMPT_REF_FAMILIES = new Set([
|
|
|
53646
53737
|
"tasks",
|
|
53647
53738
|
"workflows"
|
|
53648
53739
|
]);
|
|
53740
|
+
var ARGV_ARRAY_BLOCK_DETAIL = "Manual conversion required: an array `command:` has no safe v3 `run:` string. Rewrite it by hand as " + "`run:` (string) plus `shell:` \u2014 see docs/migration/v0.9.1-to-v0.9.2.md for the full v2 to v4 field mapping.";
|
|
53649
53741
|
function hash(bytes) {
|
|
53650
53742
|
return crypto3.createHash("sha256").update(bytes).digest("hex");
|
|
53651
53743
|
}
|
|
53744
|
+
function causeMessage(cause) {
|
|
53745
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
53746
|
+
}
|
|
53652
53747
|
function base(input) {
|
|
53653
53748
|
return {
|
|
53654
53749
|
filePath: input.filePath,
|
|
@@ -53660,16 +53755,17 @@ function base(input) {
|
|
|
53660
53755
|
...input.containmentRoot ? { containmentRoot: input.containmentRoot } : {}
|
|
53661
53756
|
};
|
|
53662
53757
|
}
|
|
53663
|
-
var ARGV_ARRAY_BLOCK_DETAIL = "Manual conversion required: an array `command:` has no safe v3 `run:` string. Rewrite it by hand as " + "`run:` (string) plus `shell:` \u2014 see docs/migration/v0.9.1-to-v0.9.2.md for the full v2 to v4 field mapping.";
|
|
53664
53758
|
function blocked(input, reason, detail) {
|
|
53665
53759
|
return Object.freeze({ status: "blocked", ...base(input), reason, ...detail ? { detail } : {} });
|
|
53666
53760
|
}
|
|
53667
53761
|
function plainRecord(value, label) {
|
|
53668
|
-
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
53762
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
53669
53763
|
throw new Error(`${label} must be a mapping`);
|
|
53764
|
+
}
|
|
53670
53765
|
const prototype = Object.getPrototypeOf(value);
|
|
53671
|
-
if (prototype !== Object.prototype && prototype !== null)
|
|
53766
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
53672
53767
|
throw new Error(`${label} must use a plain or null prototype`);
|
|
53768
|
+
}
|
|
53673
53769
|
return value;
|
|
53674
53770
|
}
|
|
53675
53771
|
function exactString(value, label, nonempty = false) {
|
|
@@ -53683,7 +53779,7 @@ function optionalString(value, label) {
|
|
|
53683
53779
|
return;
|
|
53684
53780
|
return exactString(value, label);
|
|
53685
53781
|
}
|
|
53686
|
-
function
|
|
53782
|
+
function parseRawTaskYaml(input) {
|
|
53687
53783
|
let source;
|
|
53688
53784
|
try {
|
|
53689
53785
|
source = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(input.bytes);
|
|
@@ -53695,7 +53791,7 @@ function parseLegacyTaskYaml(input) {
|
|
|
53695
53791
|
try {
|
|
53696
53792
|
document = $parseDocument(source, { lineCounter: lineCounter2, uniqueKeys: true });
|
|
53697
53793
|
} catch (cause) {
|
|
53698
|
-
throw new Error(`invalid YAML: ${
|
|
53794
|
+
throw new Error(`invalid YAML: ${causeMessage(cause)}`);
|
|
53699
53795
|
}
|
|
53700
53796
|
const [parseError] = document.errors;
|
|
53701
53797
|
if (parseError)
|
|
@@ -53706,11 +53802,181 @@ function parseLegacyTaskYaml(input) {
|
|
|
53706
53802
|
throw new Error(`unsupported YAML construct: ${parseWarning.message}`);
|
|
53707
53803
|
assertBoundedTaskYamlDocument(document, {
|
|
53708
53804
|
filePath: input.filePath,
|
|
53709
|
-
sourceLabel: "task
|
|
53805
|
+
sourceLabel: "task migration source",
|
|
53710
53806
|
lineCounter: lineCounter2
|
|
53711
53807
|
});
|
|
53712
53808
|
return { data: plainRecord(document.toJS({ maxAliasCount: 0 }), "task YAML"), source };
|
|
53713
53809
|
}
|
|
53810
|
+
function planV3DataToV4(input, data) {
|
|
53811
|
+
const unknownTop = Object.keys(data).filter((key) => !V3_TOP_LEVEL_KEYS.has(key));
|
|
53812
|
+
if (unknownTop.length > 0) {
|
|
53813
|
+
return blocked(input, "invalid-v3-task", `unknown v3 field(s): ${unknownTop.join(", ")}`);
|
|
53814
|
+
}
|
|
53815
|
+
const hasUses = Object.hasOwn(data, "uses");
|
|
53816
|
+
const hasRun = Object.hasOwn(data, "run");
|
|
53817
|
+
if (hasUses === hasRun) {
|
|
53818
|
+
return blocked(input, "invalid-v3-task", "requires exactly one executable selector: uses or run");
|
|
53819
|
+
}
|
|
53820
|
+
let akm;
|
|
53821
|
+
if (Object.hasOwn(data, "akm")) {
|
|
53822
|
+
try {
|
|
53823
|
+
akm = plainRecord(data.akm, "akm");
|
|
53824
|
+
} catch (cause) {
|
|
53825
|
+
return blocked(input, "invalid-v3-task", causeMessage(cause));
|
|
53826
|
+
}
|
|
53827
|
+
const unknownAkm = Object.keys(akm).filter((key) => !V3_AKM_KEYS.has(key));
|
|
53828
|
+
if (unknownAkm.length > 0) {
|
|
53829
|
+
return blocked(input, "unrecognized-akm-member", `akm has unknown field(s): ${unknownAkm.join(", ")}`);
|
|
53830
|
+
}
|
|
53831
|
+
if (Object.hasOwn(akm, "enabled") && typeof akm.enabled !== "boolean") {
|
|
53832
|
+
return blocked(input, "invalid-v3-task", "akm.enabled must be a boolean");
|
|
53833
|
+
}
|
|
53834
|
+
if (Object.hasOwn(akm, "schedule") && typeof akm.schedule !== "string") {
|
|
53835
|
+
return blocked(input, "invalid-v3-task", "akm.schedule must be a string");
|
|
53836
|
+
}
|
|
53837
|
+
}
|
|
53838
|
+
const hasOn = Object.hasOwn(data, "on");
|
|
53839
|
+
let onRecord;
|
|
53840
|
+
if (hasOn) {
|
|
53841
|
+
try {
|
|
53842
|
+
onRecord = plainRecord(data.on, "on");
|
|
53843
|
+
} catch (cause) {
|
|
53844
|
+
return blocked(input, "invalid-v3-task", causeMessage(cause));
|
|
53845
|
+
}
|
|
53846
|
+
if (Object.keys(onRecord).length === 0) {
|
|
53847
|
+
return blocked(input, "invalid-v3-task", "on must declare schedule and/or workflow_dispatch.");
|
|
53848
|
+
}
|
|
53849
|
+
const unknownOn = Object.keys(onRecord).filter((key) => !V3_ON_KEYS.has(key));
|
|
53850
|
+
if (unknownOn.length > 0) {
|
|
53851
|
+
return blocked(input, "invalid-v3-task", `on has unknown field(s): ${unknownOn.join(", ")}`);
|
|
53852
|
+
}
|
|
53853
|
+
if (Object.hasOwn(onRecord, "workflow_dispatch") && onRecord.workflow_dispatch !== null) {
|
|
53854
|
+
let dispatchMapping;
|
|
53855
|
+
try {
|
|
53856
|
+
dispatchMapping = plainRecord(onRecord.workflow_dispatch, "on.workflow_dispatch");
|
|
53857
|
+
} catch (cause) {
|
|
53858
|
+
return blocked(input, "invalid-v3-task", causeMessage(cause));
|
|
53859
|
+
}
|
|
53860
|
+
if (Object.keys(dispatchMapping).length > 0) {
|
|
53861
|
+
return blocked(input, "invalid-v3-task", "on.workflow_dispatch must be null or an empty mapping; inputs are unsupported.");
|
|
53862
|
+
}
|
|
53863
|
+
}
|
|
53864
|
+
}
|
|
53865
|
+
const hasAkmSchedule = akm !== undefined && Object.hasOwn(akm, "schedule");
|
|
53866
|
+
if (hasAkmSchedule && hasOn) {
|
|
53867
|
+
return blocked(input, "ambiguous-scheduling-source", "declares both akm.schedule and on:; task v3 requires exactly one scheduling source and the migrator will not guess which one wins.");
|
|
53868
|
+
}
|
|
53869
|
+
if (!hasAkmSchedule && !hasOn) {
|
|
53870
|
+
return blocked(input, "invalid-v3-task", "requires exactly one scheduling source: akm.schedule or on.");
|
|
53871
|
+
}
|
|
53872
|
+
const hasWith = Object.hasOwn(data, "with");
|
|
53873
|
+
let usesTarget;
|
|
53874
|
+
if (hasUses) {
|
|
53875
|
+
let usesValue;
|
|
53876
|
+
try {
|
|
53877
|
+
usesValue = exactString(data.uses, "uses", true);
|
|
53878
|
+
} catch (cause) {
|
|
53879
|
+
return blocked(input, "invalid-v3-task", causeMessage(cause));
|
|
53880
|
+
}
|
|
53881
|
+
try {
|
|
53882
|
+
usesTarget = classifyTaskV3Uses(usesValue);
|
|
53883
|
+
} catch (cause) {
|
|
53884
|
+
return blocked(input, "invalid-v3-task", causeMessage(cause));
|
|
53885
|
+
}
|
|
53886
|
+
if (usesTarget.kind === "github-action") {
|
|
53887
|
+
return blocked(input, "github-action-target-removed", `"${usesValue}" is a github-action target; the github-action uses: variant was removed in task source v4. Use commands/, scripts/, workflows/, or akm/command instead.`);
|
|
53888
|
+
}
|
|
53889
|
+
if (hasWith && usesTarget.kind !== "builtin-command") {
|
|
53890
|
+
return blocked(input, "with-on-non-command-target", `a with: block on "${usesValue}" (a non-akm/command target) has no task source v4 equivalent; task-call inputs are declared and bound separately.`);
|
|
53891
|
+
}
|
|
53892
|
+
} else if (hasWith) {
|
|
53893
|
+
return blocked(input, "invalid-v3-task", "with is legal only with uses");
|
|
53894
|
+
}
|
|
53895
|
+
if (!input.writable || input.onDiskWritable === false) {
|
|
53896
|
+
return blocked(input, "read-only-source", !input.writable ? "the owning source is not writable" : "the source file or publication directory is read-only");
|
|
53897
|
+
}
|
|
53898
|
+
let scheduleField;
|
|
53899
|
+
const notices = [];
|
|
53900
|
+
if (hasAkmSchedule) {
|
|
53901
|
+
const cron = akm.schedule;
|
|
53902
|
+
scheduleField = cron;
|
|
53903
|
+
} else {
|
|
53904
|
+
const rawSchedule = onRecord !== undefined && Object.hasOwn(onRecord, "schedule") ? onRecord.schedule : undefined;
|
|
53905
|
+
if (rawSchedule !== undefined) {
|
|
53906
|
+
if (!Array.isArray(rawSchedule) || rawSchedule.length === 0) {
|
|
53907
|
+
return blocked(input, "invalid-v3-task", "on.schedule must be a non-empty list of {cron} records");
|
|
53908
|
+
}
|
|
53909
|
+
const crons = [];
|
|
53910
|
+
for (const entry of rawSchedule) {
|
|
53911
|
+
let record;
|
|
53912
|
+
try {
|
|
53913
|
+
record = plainRecord(entry, "on.schedule[]");
|
|
53914
|
+
} catch (cause) {
|
|
53915
|
+
return blocked(input, "invalid-v3-task", causeMessage(cause));
|
|
53916
|
+
}
|
|
53917
|
+
const keys = Object.keys(record);
|
|
53918
|
+
if (keys.length !== 1 || keys[0] !== "cron" || typeof record.cron !== "string" || record.cron.length === 0) {
|
|
53919
|
+
return blocked(input, "invalid-v3-task", "each on.schedule entry must be exactly {cron: <non-empty string>}");
|
|
53920
|
+
}
|
|
53921
|
+
crons.push(record.cron);
|
|
53922
|
+
}
|
|
53923
|
+
scheduleField = crons.map((cron) => ({ cron }));
|
|
53924
|
+
} else {
|
|
53925
|
+
notices.push("schedule: is absent from the migrated document \u2014 the source's only trigger was on.workflow_dispatch (manual dispatch); task source v4 tasks are always runnable manually via `akm task run`, so no schedule: entry was emitted.");
|
|
53926
|
+
}
|
|
53927
|
+
}
|
|
53928
|
+
const out = { version: 4 };
|
|
53929
|
+
if (Object.hasOwn(data, "name"))
|
|
53930
|
+
out.name = data.name;
|
|
53931
|
+
if (hasUses)
|
|
53932
|
+
out.uses = data.uses;
|
|
53933
|
+
else
|
|
53934
|
+
out.run = data.run;
|
|
53935
|
+
if (Object.hasOwn(data, "shell"))
|
|
53936
|
+
out.shell = data.shell;
|
|
53937
|
+
if (hasWith)
|
|
53938
|
+
out.with = data.with;
|
|
53939
|
+
if (Object.hasOwn(data, "env"))
|
|
53940
|
+
out.env = data.env;
|
|
53941
|
+
if (Object.hasOwn(data, "working-directory"))
|
|
53942
|
+
out["working-directory"] = data["working-directory"];
|
|
53943
|
+
if (scheduleField !== undefined)
|
|
53944
|
+
out.schedule = scheduleField;
|
|
53945
|
+
if (akm) {
|
|
53946
|
+
for (const key of AKM_HOIST_KEYS) {
|
|
53947
|
+
if (Object.hasOwn(akm, key))
|
|
53948
|
+
out[key] = akm[key];
|
|
53949
|
+
}
|
|
53950
|
+
if (Object.hasOwn(akm, "outputSchema") && akm.outputSchema !== null) {
|
|
53951
|
+
if (usesTarget !== undefined && (usesTarget.kind === "command" || usesTarget.kind === "builtin-command")) {
|
|
53952
|
+
out.output = akm.outputSchema;
|
|
53953
|
+
} else {
|
|
53954
|
+
const targetLabel = usesTarget === undefined ? "a run: target" : `the "${usesTarget.ref}" target`;
|
|
53955
|
+
notices.push(`akm.outputSchema was dropped rather than hoisted to output: \u2014 task source v4 accepts output: only with a command target (uses: commands/<ref> or uses: akm/command), and ${targetLabel} never consumed the schema in v3 either, so nothing enforceable was lost.`);
|
|
53956
|
+
}
|
|
53957
|
+
}
|
|
53958
|
+
}
|
|
53959
|
+
const afterYaml = $stringify(out);
|
|
53960
|
+
const after = Buffer.from(afterYaml, "utf8");
|
|
53961
|
+
try {
|
|
53962
|
+
parseTaskSourceV4({
|
|
53963
|
+
yaml: afterYaml,
|
|
53964
|
+
filePath: input.filePath,
|
|
53965
|
+
...input.containmentRoot ? { workspaceRoot: input.containmentRoot } : {}
|
|
53966
|
+
});
|
|
53967
|
+
} catch (cause) {
|
|
53968
|
+
return blocked(input, "generated-v4-validation-failed", causeMessage(cause));
|
|
53969
|
+
}
|
|
53970
|
+
const notice = notices.join(" ");
|
|
53971
|
+
return Object.freeze({
|
|
53972
|
+
status: "changed",
|
|
53973
|
+
...base(input),
|
|
53974
|
+
reason: "task-converted",
|
|
53975
|
+
after,
|
|
53976
|
+
afterHash: hash(after),
|
|
53977
|
+
...notice ? { notice } : {}
|
|
53978
|
+
});
|
|
53979
|
+
}
|
|
53714
53980
|
function validateCommonV2(data) {
|
|
53715
53981
|
const unknown = Object.keys(data).filter((key) => !V2_KEYS.has(key));
|
|
53716
53982
|
if (unknown.length > 0)
|
|
@@ -53905,424 +54171,120 @@ function migratedObject(data) {
|
|
|
53905
54171
|
function isReason(value) {
|
|
53906
54172
|
return typeof value === "string";
|
|
53907
54173
|
}
|
|
53908
|
-
function
|
|
53909
|
-
if (data.version !== 2) {
|
|
53910
|
-
return blocked(input, "unsupported-task-version", `expected normalized legacy version 2, got ${String(data.version)}`);
|
|
53911
|
-
}
|
|
54174
|
+
function planV2DataToV4(input, data) {
|
|
53912
54175
|
if (!input.writable || input.onDiskWritable === false) {
|
|
53913
54176
|
return blocked(input, "read-only-source", !input.writable ? "the owning source is not writable" : "the source file or publication directory is read-only");
|
|
53914
54177
|
}
|
|
53915
54178
|
try {
|
|
53916
54179
|
validateTaskId(path46.basename(input.filePath, ".yml"));
|
|
53917
54180
|
} catch (cause) {
|
|
53918
|
-
return blocked(input, "invalid-v2-task",
|
|
54181
|
+
return blocked(input, "invalid-v2-task", causeMessage(cause));
|
|
53919
54182
|
}
|
|
53920
54183
|
let migrated;
|
|
53921
54184
|
try {
|
|
53922
54185
|
migrated = migratedObject(data);
|
|
53923
54186
|
} catch (cause) {
|
|
53924
|
-
return blocked(input, "invalid-v2-task",
|
|
54187
|
+
return blocked(input, "invalid-v2-task", causeMessage(cause));
|
|
53925
54188
|
}
|
|
53926
54189
|
if (isReason(migrated)) {
|
|
53927
54190
|
const detail = migrated === "argv-array-has-no-portable-shell-string" ? ARGV_ARRAY_BLOCK_DETAIL : undefined;
|
|
53928
54191
|
return blocked(input, migrated, detail);
|
|
53929
54192
|
}
|
|
53930
|
-
const
|
|
54193
|
+
const v3Yaml = $stringify(migrated);
|
|
53931
54194
|
try {
|
|
53932
54195
|
parseTaskV3Yaml({
|
|
53933
|
-
yaml:
|
|
54196
|
+
yaml: v3Yaml,
|
|
53934
54197
|
filePath: input.filePath,
|
|
53935
54198
|
...input.containmentRoot ? { workspaceRoot: input.containmentRoot } : {}
|
|
53936
54199
|
});
|
|
53937
54200
|
} catch (cause) {
|
|
53938
|
-
return blocked(input, "generated-v3-validation-failed",
|
|
54201
|
+
return blocked(input, "generated-v3-validation-failed", causeMessage(cause));
|
|
53939
54202
|
}
|
|
53940
|
-
|
|
53941
|
-
status: "changed",
|
|
53942
|
-
...base(input),
|
|
53943
|
-
reason: "task-converted",
|
|
53944
|
-
after,
|
|
53945
|
-
afterHash: hash(after)
|
|
53946
|
-
});
|
|
53947
|
-
}
|
|
53948
|
-
function planTaskToV3File(input) {
|
|
53949
|
-
let data;
|
|
53950
|
-
let source;
|
|
54203
|
+
let v3Data;
|
|
53951
54204
|
try {
|
|
53952
|
-
({ data
|
|
54205
|
+
({ data: v3Data } = parseRawTaskYaml({ ...input, bytes: Buffer.from(v3Yaml, "utf8") }));
|
|
53953
54206
|
} catch (cause) {
|
|
53954
|
-
return blocked(input, "invalid-task-yaml",
|
|
54207
|
+
return blocked(input, "invalid-task-yaml", causeMessage(cause));
|
|
54208
|
+
}
|
|
54209
|
+
const v4Outcome = planV3DataToV4(input, v3Data);
|
|
54210
|
+
return v4Outcome.status === "changed" ? { ...v4Outcome, reason: "task-converted" } : v4Outcome;
|
|
54211
|
+
}
|
|
54212
|
+
function planV4Cleanup(input, source) {
|
|
54213
|
+
const document = $parseDocument(source, { uniqueKeys: true });
|
|
54214
|
+
const schedule = document.get("schedule", true);
|
|
54215
|
+
let removed = false;
|
|
54216
|
+
if ($isSeq(schedule)) {
|
|
54217
|
+
for (const entry of schedule.items) {
|
|
54218
|
+
if (!$isMap(entry) || !entry.has("enabled"))
|
|
54219
|
+
continue;
|
|
54220
|
+
entry.delete("enabled");
|
|
54221
|
+
removed = true;
|
|
54222
|
+
}
|
|
53955
54223
|
}
|
|
53956
|
-
if (
|
|
54224
|
+
if (removed) {
|
|
54225
|
+
if (!input.writable || input.onDiskWritable === false) {
|
|
54226
|
+
return blocked(input, "read-only-source", !input.writable ? "the owning source is not writable" : "the source file or publication directory is read-only");
|
|
54227
|
+
}
|
|
54228
|
+
const after = Buffer.from(document.toString(), "utf8");
|
|
53957
54229
|
try {
|
|
53958
|
-
|
|
53959
|
-
yaml:
|
|
54230
|
+
parseTaskSourceV4({
|
|
54231
|
+
yaml: after.toString("utf8"),
|
|
53960
54232
|
filePath: input.filePath,
|
|
53961
54233
|
...input.containmentRoot ? { workspaceRoot: input.containmentRoot } : {}
|
|
53962
54234
|
});
|
|
53963
|
-
return Object.freeze({ status: "skipped", ...base(input), reason: "already-v3" });
|
|
53964
|
-
} catch (cause) {
|
|
53965
|
-
return blocked(input, "invalid-v3-task", cause instanceof Error ? cause.message : String(cause));
|
|
53966
|
-
}
|
|
53967
|
-
}
|
|
53968
|
-
if (data.version === 4) {
|
|
53969
|
-
return Object.freeze({ status: "skipped", ...base(input), reason: "already-v4" });
|
|
53970
|
-
}
|
|
53971
|
-
if (data.version !== 2) {
|
|
53972
|
-
return blocked(input, "unsupported-task-version", `expected version 2, 3, or 4, got ${String(data.version)}`);
|
|
53973
|
-
}
|
|
53974
|
-
return planLegacyTaskDataToV3(input, data);
|
|
53975
|
-
}
|
|
53976
|
-
|
|
53977
|
-
// src/tasks/source/task-to-v4.ts
|
|
53978
|
-
import crypto4 from "crypto";
|
|
53979
|
-
import path47 from "path";
|
|
53980
|
-
var V3_TOP_LEVEL_KEYS = new Set([
|
|
53981
|
-
"version",
|
|
53982
|
-
"name",
|
|
53983
|
-
"uses",
|
|
53984
|
-
"run",
|
|
53985
|
-
"with",
|
|
53986
|
-
"env",
|
|
53987
|
-
"shell",
|
|
53988
|
-
"working-directory",
|
|
53989
|
-
"akm",
|
|
53990
|
-
"on"
|
|
53991
|
-
]);
|
|
53992
|
-
var V3_AKM_KEYS = new Set([
|
|
53993
|
-
"schedule",
|
|
53994
|
-
"enabled",
|
|
53995
|
-
"description",
|
|
53996
|
-
"when_to_use",
|
|
53997
|
-
"tags",
|
|
53998
|
-
"agent",
|
|
53999
|
-
"engine",
|
|
54000
|
-
"model",
|
|
54001
|
-
"inference",
|
|
54002
|
-
"outputSchema",
|
|
54003
|
-
"tools",
|
|
54004
|
-
"timeout",
|
|
54005
|
-
"redact",
|
|
54006
|
-
"maxSteps",
|
|
54007
|
-
"maxRetries"
|
|
54008
|
-
]);
|
|
54009
|
-
var V3_ON_KEYS = new Set(["schedule", "workflow_dispatch"]);
|
|
54010
|
-
var AKM_HOIST_KEYS = [
|
|
54011
|
-
"description",
|
|
54012
|
-
"when_to_use",
|
|
54013
|
-
"tags",
|
|
54014
|
-
"agent",
|
|
54015
|
-
"engine",
|
|
54016
|
-
"model",
|
|
54017
|
-
"inference",
|
|
54018
|
-
"tools",
|
|
54019
|
-
"timeout",
|
|
54020
|
-
"redact",
|
|
54021
|
-
"maxSteps",
|
|
54022
|
-
"maxRetries"
|
|
54023
|
-
];
|
|
54024
|
-
function hash2(bytes) {
|
|
54025
|
-
return crypto4.createHash("sha256").update(bytes).digest("hex");
|
|
54026
|
-
}
|
|
54027
|
-
function causeMessage(cause) {
|
|
54028
|
-
return cause instanceof Error ? cause.message : String(cause);
|
|
54029
|
-
}
|
|
54030
|
-
function base2(input) {
|
|
54031
|
-
return {
|
|
54032
|
-
filePath: input.filePath,
|
|
54033
|
-
before: Buffer.from(input.bytes),
|
|
54034
|
-
beforeHash: hash2(input.bytes),
|
|
54035
|
-
mode: input.mode,
|
|
54036
|
-
writable: input.writable,
|
|
54037
|
-
...input.onDiskWritable !== undefined ? { onDiskWritable: input.onDiskWritable } : {},
|
|
54038
|
-
...input.containmentRoot ? { containmentRoot: input.containmentRoot } : {}
|
|
54039
|
-
};
|
|
54040
|
-
}
|
|
54041
|
-
function blocked2(input, reason, detail) {
|
|
54042
|
-
return Object.freeze({ status: "blocked", ...base2(input), reason, ...detail ? { detail } : {} });
|
|
54043
|
-
}
|
|
54044
|
-
function plainRecord2(value, label) {
|
|
54045
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
54046
|
-
throw new Error(`${label} must be a mapping`);
|
|
54047
|
-
}
|
|
54048
|
-
const prototype = Object.getPrototypeOf(value);
|
|
54049
|
-
if (prototype !== Object.prototype && prototype !== null) {
|
|
54050
|
-
throw new Error(`${label} must use a plain or null prototype`);
|
|
54051
|
-
}
|
|
54052
|
-
return value;
|
|
54053
|
-
}
|
|
54054
|
-
function exactString2(value, label, nonempty = false) {
|
|
54055
|
-
if (typeof value !== "string" || nonempty && value.trim().length === 0) {
|
|
54056
|
-
throw new Error(`${label} must be ${nonempty ? "a non-empty " : "a "}string`);
|
|
54057
|
-
}
|
|
54058
|
-
return value;
|
|
54059
|
-
}
|
|
54060
|
-
function parseV3RawYaml(input) {
|
|
54061
|
-
let source;
|
|
54062
|
-
try {
|
|
54063
|
-
source = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(input.bytes);
|
|
54064
|
-
} catch {
|
|
54065
|
-
throw new Error("task YAML contains invalid UTF-8 bytes");
|
|
54066
|
-
}
|
|
54067
|
-
const lineCounter2 = new $LineCounter;
|
|
54068
|
-
let document;
|
|
54069
|
-
try {
|
|
54070
|
-
document = $parseDocument(source, { lineCounter: lineCounter2, uniqueKeys: true });
|
|
54071
|
-
} catch (cause) {
|
|
54072
|
-
throw new Error(`invalid YAML: ${causeMessage(cause)}`);
|
|
54073
|
-
}
|
|
54074
|
-
const [parseError] = document.errors;
|
|
54075
|
-
if (parseError)
|
|
54076
|
-
throw new Error(`invalid YAML: ${parseError.message.split(`
|
|
54077
|
-
`)[0]}`);
|
|
54078
|
-
const [parseWarning] = document.warnings;
|
|
54079
|
-
if (parseWarning)
|
|
54080
|
-
throw new Error(`unsupported YAML construct: ${parseWarning.message}`);
|
|
54081
|
-
assertBoundedTaskYamlDocument(document, {
|
|
54082
|
-
filePath: input.filePath,
|
|
54083
|
-
sourceLabel: "task v3 migration source",
|
|
54084
|
-
lineCounter: lineCounter2
|
|
54085
|
-
});
|
|
54086
|
-
return { data: plainRecord2(document.toJS({ maxAliasCount: 0 }), "task YAML"), source };
|
|
54087
|
-
}
|
|
54088
|
-
function planV3DataToV4(input, data) {
|
|
54089
|
-
const unknownTop = Object.keys(data).filter((key) => !V3_TOP_LEVEL_KEYS.has(key));
|
|
54090
|
-
if (unknownTop.length > 0) {
|
|
54091
|
-
return blocked2(input, "invalid-v3-task", `unknown v3 field(s): ${unknownTop.join(", ")}`);
|
|
54092
|
-
}
|
|
54093
|
-
const hasUses = Object.hasOwn(data, "uses");
|
|
54094
|
-
const hasRun = Object.hasOwn(data, "run");
|
|
54095
|
-
if (hasUses === hasRun) {
|
|
54096
|
-
return blocked2(input, "invalid-v3-task", "requires exactly one executable selector: uses or run");
|
|
54097
|
-
}
|
|
54098
|
-
let akm;
|
|
54099
|
-
if (Object.hasOwn(data, "akm")) {
|
|
54100
|
-
try {
|
|
54101
|
-
akm = plainRecord2(data.akm, "akm");
|
|
54102
|
-
} catch (cause) {
|
|
54103
|
-
return blocked2(input, "invalid-v3-task", causeMessage(cause));
|
|
54104
|
-
}
|
|
54105
|
-
const unknownAkm = Object.keys(akm).filter((key) => !V3_AKM_KEYS.has(key));
|
|
54106
|
-
if (unknownAkm.length > 0) {
|
|
54107
|
-
return blocked2(input, "unrecognized-akm-member", `akm has unknown field(s): ${unknownAkm.join(", ")}`);
|
|
54108
|
-
}
|
|
54109
|
-
if (Object.hasOwn(akm, "enabled") && typeof akm.enabled !== "boolean") {
|
|
54110
|
-
return blocked2(input, "invalid-v3-task", "akm.enabled must be a boolean");
|
|
54111
|
-
}
|
|
54112
|
-
if (Object.hasOwn(akm, "schedule") && typeof akm.schedule !== "string") {
|
|
54113
|
-
return blocked2(input, "invalid-v3-task", "akm.schedule must be a string");
|
|
54114
|
-
}
|
|
54115
|
-
}
|
|
54116
|
-
const hasOn = Object.hasOwn(data, "on");
|
|
54117
|
-
let onRecord;
|
|
54118
|
-
if (hasOn) {
|
|
54119
|
-
try {
|
|
54120
|
-
onRecord = plainRecord2(data.on, "on");
|
|
54121
|
-
} catch (cause) {
|
|
54122
|
-
return blocked2(input, "invalid-v3-task", causeMessage(cause));
|
|
54123
|
-
}
|
|
54124
|
-
if (Object.keys(onRecord).length === 0) {
|
|
54125
|
-
return blocked2(input, "invalid-v3-task", "on must declare schedule and/or workflow_dispatch.");
|
|
54126
|
-
}
|
|
54127
|
-
const unknownOn = Object.keys(onRecord).filter((key) => !V3_ON_KEYS.has(key));
|
|
54128
|
-
if (unknownOn.length > 0) {
|
|
54129
|
-
return blocked2(input, "invalid-v3-task", `on has unknown field(s): ${unknownOn.join(", ")}`);
|
|
54130
|
-
}
|
|
54131
|
-
if (Object.hasOwn(onRecord, "workflow_dispatch") && onRecord.workflow_dispatch !== null) {
|
|
54132
|
-
let dispatchMapping;
|
|
54133
|
-
try {
|
|
54134
|
-
dispatchMapping = plainRecord2(onRecord.workflow_dispatch, "on.workflow_dispatch");
|
|
54135
|
-
} catch (cause) {
|
|
54136
|
-
return blocked2(input, "invalid-v3-task", causeMessage(cause));
|
|
54137
|
-
}
|
|
54138
|
-
if (Object.keys(dispatchMapping).length > 0) {
|
|
54139
|
-
return blocked2(input, "invalid-v3-task", "on.workflow_dispatch must be null or an empty mapping; inputs are unsupported.");
|
|
54140
|
-
}
|
|
54141
|
-
}
|
|
54142
|
-
}
|
|
54143
|
-
const hasAkmSchedule = akm !== undefined && Object.hasOwn(akm, "schedule");
|
|
54144
|
-
if (hasAkmSchedule && hasOn) {
|
|
54145
|
-
return blocked2(input, "ambiguous-scheduling-source", "declares both akm.schedule and on:; task v3 requires exactly one scheduling source and the migrator will not guess which one wins.");
|
|
54146
|
-
}
|
|
54147
|
-
if (!hasAkmSchedule && !hasOn) {
|
|
54148
|
-
return blocked2(input, "invalid-v3-task", "requires exactly one scheduling source: akm.schedule or on.");
|
|
54149
|
-
}
|
|
54150
|
-
const hasWith = Object.hasOwn(data, "with");
|
|
54151
|
-
let usesTarget;
|
|
54152
|
-
if (hasUses) {
|
|
54153
|
-
let usesValue;
|
|
54154
|
-
try {
|
|
54155
|
-
usesValue = exactString2(data.uses, "uses", true);
|
|
54156
54235
|
} catch (cause) {
|
|
54157
|
-
return
|
|
54158
|
-
}
|
|
54159
|
-
try {
|
|
54160
|
-
usesTarget = classifyTaskV3Uses(usesValue);
|
|
54161
|
-
} catch (cause) {
|
|
54162
|
-
return blocked2(input, "invalid-v3-task", causeMessage(cause));
|
|
54163
|
-
}
|
|
54164
|
-
if (usesTarget.kind === "github-action") {
|
|
54165
|
-
return blocked2(input, "github-action-target-removed", `"${usesValue}" is a github-action target; the github-action uses: variant was removed in task source v4. Use commands/, scripts/, workflows/, or akm/command instead.`);
|
|
54166
|
-
}
|
|
54167
|
-
if (hasWith && usesTarget.kind !== "builtin-command") {
|
|
54168
|
-
return blocked2(input, "with-on-non-command-target", `a with: block on "${usesValue}" (a non-akm/command target) has no task source v4 equivalent; task-call inputs are declared and bound separately.`);
|
|
54169
|
-
}
|
|
54170
|
-
} else if (hasWith) {
|
|
54171
|
-
return blocked2(input, "invalid-v3-task", "with is legal only with uses");
|
|
54172
|
-
}
|
|
54173
|
-
if (!input.writable || input.onDiskWritable === false) {
|
|
54174
|
-
return blocked2(input, "read-only-source", !input.writable ? "the owning source is not writable" : "the source file or publication directory is read-only");
|
|
54175
|
-
}
|
|
54176
|
-
let scheduleField;
|
|
54177
|
-
const notices = [];
|
|
54178
|
-
if (hasAkmSchedule) {
|
|
54179
|
-
const cron = akm.schedule;
|
|
54180
|
-
scheduleField = cron;
|
|
54181
|
-
} else {
|
|
54182
|
-
const rawSchedule = onRecord !== undefined && Object.hasOwn(onRecord, "schedule") ? onRecord.schedule : undefined;
|
|
54183
|
-
if (rawSchedule !== undefined) {
|
|
54184
|
-
if (!Array.isArray(rawSchedule) || rawSchedule.length === 0) {
|
|
54185
|
-
return blocked2(input, "invalid-v3-task", "on.schedule must be a non-empty list of {cron} records");
|
|
54186
|
-
}
|
|
54187
|
-
const crons = [];
|
|
54188
|
-
for (const entry of rawSchedule) {
|
|
54189
|
-
let record;
|
|
54190
|
-
try {
|
|
54191
|
-
record = plainRecord2(entry, "on.schedule[]");
|
|
54192
|
-
} catch (cause) {
|
|
54193
|
-
return blocked2(input, "invalid-v3-task", causeMessage(cause));
|
|
54194
|
-
}
|
|
54195
|
-
const keys = Object.keys(record);
|
|
54196
|
-
if (keys.length !== 1 || keys[0] !== "cron" || typeof record.cron !== "string" || record.cron.length === 0) {
|
|
54197
|
-
return blocked2(input, "invalid-v3-task", "each on.schedule entry must be exactly {cron: <non-empty string>}");
|
|
54198
|
-
}
|
|
54199
|
-
crons.push(record.cron);
|
|
54200
|
-
}
|
|
54201
|
-
scheduleField = crons.map((cron) => ({ cron }));
|
|
54202
|
-
} else {
|
|
54203
|
-
notices.push("schedule: is absent from the migrated document \u2014 the source's only trigger was on.workflow_dispatch (manual dispatch); task source v4 tasks are always runnable manually via `akm task run`, so no schedule: entry was emitted.");
|
|
54204
|
-
}
|
|
54205
|
-
}
|
|
54206
|
-
const out = { version: 4 };
|
|
54207
|
-
if (Object.hasOwn(data, "name"))
|
|
54208
|
-
out.name = data.name;
|
|
54209
|
-
if (hasUses)
|
|
54210
|
-
out.uses = data.uses;
|
|
54211
|
-
else
|
|
54212
|
-
out.run = data.run;
|
|
54213
|
-
if (Object.hasOwn(data, "shell"))
|
|
54214
|
-
out.shell = data.shell;
|
|
54215
|
-
if (hasWith)
|
|
54216
|
-
out.with = data.with;
|
|
54217
|
-
if (Object.hasOwn(data, "env"))
|
|
54218
|
-
out.env = data.env;
|
|
54219
|
-
if (Object.hasOwn(data, "working-directory"))
|
|
54220
|
-
out["working-directory"] = data["working-directory"];
|
|
54221
|
-
if (scheduleField !== undefined)
|
|
54222
|
-
out.schedule = scheduleField;
|
|
54223
|
-
if (akm) {
|
|
54224
|
-
for (const key of AKM_HOIST_KEYS) {
|
|
54225
|
-
if (Object.hasOwn(akm, key))
|
|
54226
|
-
out[key] = akm[key];
|
|
54227
|
-
}
|
|
54228
|
-
if (Object.hasOwn(akm, "outputSchema") && akm.outputSchema !== null) {
|
|
54229
|
-
if (usesTarget !== undefined && (usesTarget.kind === "command" || usesTarget.kind === "builtin-command")) {
|
|
54230
|
-
out.output = akm.outputSchema;
|
|
54231
|
-
} else {
|
|
54232
|
-
const targetLabel = usesTarget === undefined ? "a run: target" : `the "${usesTarget.ref}" target`;
|
|
54233
|
-
notices.push(`akm.outputSchema was dropped rather than hoisted to output: \u2014 task source v4 accepts output: only with a command target (uses: commands/<ref> or uses: akm/command), and ${targetLabel} never consumed the schema in v3 either, so nothing enforceable was lost.`);
|
|
54234
|
-
}
|
|
54236
|
+
return blocked(input, "generated-v4-validation-failed", causeMessage(cause));
|
|
54235
54237
|
}
|
|
54238
|
+
return Object.freeze({
|
|
54239
|
+
status: "changed",
|
|
54240
|
+
...base(input),
|
|
54241
|
+
reason: "source-enablement-removed",
|
|
54242
|
+
after,
|
|
54243
|
+
afterHash: hash(after),
|
|
54244
|
+
notice: "Removed source-owned schedule enablement; scheduler activation is now host-local config."
|
|
54245
|
+
});
|
|
54236
54246
|
}
|
|
54237
|
-
const afterYaml = $stringify(out);
|
|
54238
|
-
const after = Buffer.from(afterYaml, "utf8");
|
|
54239
54247
|
try {
|
|
54240
54248
|
parseTaskSourceV4({
|
|
54241
|
-
yaml:
|
|
54249
|
+
yaml: source,
|
|
54242
54250
|
filePath: input.filePath,
|
|
54243
54251
|
...input.containmentRoot ? { workspaceRoot: input.containmentRoot } : {}
|
|
54244
54252
|
});
|
|
54253
|
+
return Object.freeze({ status: "skipped", ...base(input), reason: "already-v4" });
|
|
54245
54254
|
} catch (cause) {
|
|
54246
|
-
return
|
|
54255
|
+
return blocked(input, "invalid-v4-task", causeMessage(cause));
|
|
54247
54256
|
}
|
|
54248
|
-
const notice = notices.join(" ");
|
|
54249
|
-
return Object.freeze({
|
|
54250
|
-
status: "changed",
|
|
54251
|
-
...base2(input),
|
|
54252
|
-
reason: "task-converted",
|
|
54253
|
-
after,
|
|
54254
|
-
afterHash: hash2(after),
|
|
54255
|
-
...notice ? { notice } : {}
|
|
54256
|
-
});
|
|
54257
54257
|
}
|
|
54258
54258
|
function planTaskToV4File(input) {
|
|
54259
54259
|
let data;
|
|
54260
54260
|
let source;
|
|
54261
54261
|
try {
|
|
54262
|
-
({ data, source } =
|
|
54262
|
+
({ data, source } = parseRawTaskYaml(input));
|
|
54263
54263
|
} catch (cause) {
|
|
54264
|
-
return
|
|
54264
|
+
return blocked(input, "invalid-task-yaml", causeMessage(cause));
|
|
54265
54265
|
}
|
|
54266
54266
|
if (data.version === 4) {
|
|
54267
|
-
|
|
54268
|
-
|
|
54269
|
-
|
|
54270
|
-
if ($isSeq(schedule)) {
|
|
54271
|
-
for (const entry of schedule.items) {
|
|
54272
|
-
if (!$isMap(entry) || !entry.has("enabled"))
|
|
54273
|
-
continue;
|
|
54274
|
-
entry.delete("enabled");
|
|
54275
|
-
removed = true;
|
|
54276
|
-
}
|
|
54277
|
-
}
|
|
54278
|
-
if (removed) {
|
|
54279
|
-
if (!input.writable || input.onDiskWritable === false) {
|
|
54280
|
-
return blocked2(input, "read-only-source", !input.writable ? "the owning source is not writable" : "the source file or publication directory is read-only");
|
|
54281
|
-
}
|
|
54282
|
-
const after = Buffer.from(document.toString(), "utf8");
|
|
54283
|
-
try {
|
|
54284
|
-
parseTaskSourceV4({
|
|
54285
|
-
yaml: after.toString("utf8"),
|
|
54286
|
-
filePath: input.filePath,
|
|
54287
|
-
...input.containmentRoot ? { workspaceRoot: input.containmentRoot } : {}
|
|
54288
|
-
});
|
|
54289
|
-
} catch (cause) {
|
|
54290
|
-
return blocked2(input, "generated-v4-validation-failed", causeMessage(cause));
|
|
54291
|
-
}
|
|
54292
|
-
return Object.freeze({
|
|
54293
|
-
status: "changed",
|
|
54294
|
-
...base2(input),
|
|
54295
|
-
reason: "source-enablement-removed",
|
|
54296
|
-
after,
|
|
54297
|
-
afterHash: hash2(after),
|
|
54298
|
-
notice: "Removed source-owned schedule enablement; scheduler activation is now host-local config."
|
|
54299
|
-
});
|
|
54300
|
-
}
|
|
54267
|
+
return planV4Cleanup(input, source);
|
|
54268
|
+
}
|
|
54269
|
+
if (data.version === 3) {
|
|
54301
54270
|
try {
|
|
54302
|
-
|
|
54271
|
+
parseTaskV3Yaml({
|
|
54303
54272
|
yaml: source,
|
|
54304
54273
|
filePath: input.filePath,
|
|
54305
54274
|
...input.containmentRoot ? { workspaceRoot: input.containmentRoot } : {}
|
|
54306
54275
|
});
|
|
54307
|
-
return Object.freeze({ status: "skipped", ...base2(input), reason: "already-v4" });
|
|
54308
54276
|
} catch (cause) {
|
|
54309
|
-
return
|
|
54277
|
+
return blocked(input, "invalid-v3-task", causeMessage(cause));
|
|
54310
54278
|
}
|
|
54279
|
+
return planV3DataToV4(input, data);
|
|
54311
54280
|
}
|
|
54312
54281
|
if (data.version === 2) {
|
|
54313
|
-
return
|
|
54314
|
-
status: "skipped",
|
|
54315
|
-
...base2(input),
|
|
54316
|
-
reason: "pending-v2-to-v3-migration"
|
|
54317
|
-
});
|
|
54318
|
-
}
|
|
54319
|
-
if (data.version !== 3) {
|
|
54320
|
-
return blocked2(input, "unsupported-task-version", `expected version 2, 3, or 4, got ${String(data.version)}`);
|
|
54282
|
+
return planV2DataToV4(input, data);
|
|
54321
54283
|
}
|
|
54322
|
-
return
|
|
54284
|
+
return blocked(input, "unsupported-task-version", `expected version 2, 3, or 4, got ${String(data.version)}`);
|
|
54323
54285
|
}
|
|
54324
54286
|
function generationFor(files) {
|
|
54325
|
-
const digest =
|
|
54287
|
+
const digest = crypto3.createHash("sha256");
|
|
54326
54288
|
digest.update("akm-task-to-v4-plan-v1\x00");
|
|
54327
54289
|
for (const file of files) {
|
|
54328
54290
|
digest.update(file.filePath);
|
|
@@ -54359,7 +54321,7 @@ function taskToV4PlanFromOutcomes(outcomes) {
|
|
|
54359
54321
|
for (let index = 1;index < files.length; index += 1) {
|
|
54360
54322
|
const previous = files[index - 1];
|
|
54361
54323
|
const current = files[index];
|
|
54362
|
-
if (previous && current &&
|
|
54324
|
+
if (previous && current && path46.resolve(previous.filePath) === path46.resolve(current.filePath)) {
|
|
54363
54325
|
throw new Error(`duplicate task migration file path: ${current.filePath}`);
|
|
54364
54326
|
}
|
|
54365
54327
|
}
|
|
@@ -54371,8 +54333,8 @@ function migrationError(detail) {
|
|
|
54371
54333
|
return new ConfigError(`Task migration failed: ${detail}`, "INVALID_CONFIG_FILE");
|
|
54372
54334
|
}
|
|
54373
54335
|
function contained2(root2, candidate) {
|
|
54374
|
-
const relative =
|
|
54375
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
54336
|
+
const relative = path47.relative(root2, candidate);
|
|
54337
|
+
return relative === "" || !relative.startsWith("..") && !path47.isAbsolute(relative);
|
|
54376
54338
|
}
|
|
54377
54339
|
function realDirectory(filePath) {
|
|
54378
54340
|
const stat = fs39.lstatSync(filePath);
|
|
@@ -54400,7 +54362,7 @@ function walkTasks(root2, tasksDir, out) {
|
|
|
54400
54362
|
throw migrationError(`${directory} resolves outside bundle ${root2.bundleId}.`);
|
|
54401
54363
|
}
|
|
54402
54364
|
for (const entry of fs39.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
54403
|
-
const candidate =
|
|
54365
|
+
const candidate = path47.join(directory, entry.name);
|
|
54404
54366
|
if (entry.isSymbolicLink())
|
|
54405
54367
|
throw migrationError(`task migration does not follow symbolic link ${candidate}.`);
|
|
54406
54368
|
if (entry.isDirectory()) {
|
|
@@ -54426,7 +54388,7 @@ function walkTasks(root2, tasksDir, out) {
|
|
|
54426
54388
|
function inspectTaskFiles(roots) {
|
|
54427
54389
|
const files = [];
|
|
54428
54390
|
for (const root2 of [...roots].sort((a, b) => a.bundleId.localeCompare(b.bundleId))) {
|
|
54429
|
-
const tasksDir = root2.layout === "akm-task" ? root2.root :
|
|
54391
|
+
const tasksDir = root2.layout === "akm-task" ? root2.root : path47.join(root2.root, "tasks");
|
|
54430
54392
|
try {
|
|
54431
54393
|
const stat = fs39.lstatSync(tasksDir);
|
|
54432
54394
|
if (stat.isSymbolicLink())
|
|
@@ -54442,23 +54404,12 @@ function inspectTaskFiles(roots) {
|
|
|
54442
54404
|
}
|
|
54443
54405
|
return files.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
54444
54406
|
}
|
|
54445
|
-
function planTaskFileToV4(input) {
|
|
54446
|
-
const v3 = planTaskToV3File(input);
|
|
54447
|
-
if (v3.status === "blocked")
|
|
54448
|
-
return v3;
|
|
54449
|
-
if (v3.status === "skipped")
|
|
54450
|
-
return planTaskToV4File(input);
|
|
54451
|
-
const v4 = planTaskToV4File({ ...input, bytes: v3.after });
|
|
54452
|
-
if (v4.status !== "changed")
|
|
54453
|
-
return { ...v4, before: v3.before, beforeHash: v3.beforeHash };
|
|
54454
|
-
return { ...v4, reason: "task-converted", before: v3.before, beforeHash: v3.beforeHash };
|
|
54455
|
-
}
|
|
54456
54407
|
function planTaskFilesMigration(inputs) {
|
|
54457
|
-
return taskToV4PlanFromOutcomes(inputs.map(
|
|
54408
|
+
return taskToV4PlanFromOutcomes(inputs.map(planTaskToV4File));
|
|
54458
54409
|
}
|
|
54459
54410
|
function taskFileBackupPath(backupRoot, filePath) {
|
|
54460
|
-
const digest =
|
|
54461
|
-
return
|
|
54411
|
+
const digest = crypto4.createHash("sha256").update(path47.resolve(filePath)).digest("hex").slice(0, 16);
|
|
54412
|
+
return path47.join(backupRoot, "files", `${digest}-${path47.basename(filePath)}`);
|
|
54462
54413
|
}
|
|
54463
54414
|
function fsyncDirectory(directory) {
|
|
54464
54415
|
if (process.platform === "win32")
|
|
@@ -54477,7 +54428,7 @@ function fsyncDirectory(directory) {
|
|
|
54477
54428
|
}
|
|
54478
54429
|
}
|
|
54479
54430
|
function writeDurable(filePath, bytes, mode, exclusive = false) {
|
|
54480
|
-
fs39.mkdirSync(
|
|
54431
|
+
fs39.mkdirSync(path47.dirname(filePath), { recursive: true });
|
|
54481
54432
|
const fd = fs39.openSync(filePath, exclusive ? "wx" : "w", mode);
|
|
54482
54433
|
try {
|
|
54483
54434
|
fs39.writeFileSync(fd, bytes);
|
|
@@ -54486,14 +54437,14 @@ function writeDurable(filePath, bytes, mode, exclusive = false) {
|
|
|
54486
54437
|
fs39.closeSync(fd);
|
|
54487
54438
|
}
|
|
54488
54439
|
fs39.chmodSync(filePath, mode);
|
|
54489
|
-
fsyncDirectory(
|
|
54440
|
+
fsyncDirectory(path47.dirname(filePath));
|
|
54490
54441
|
}
|
|
54491
54442
|
function replaceAtomically(filePath, bytes, mode) {
|
|
54492
|
-
const temporary =
|
|
54443
|
+
const temporary = path47.join(path47.dirname(filePath), `.${path47.basename(filePath)}.migrate-${crypto4.randomUUID()}`);
|
|
54493
54444
|
try {
|
|
54494
54445
|
writeDurable(temporary, bytes, mode, true);
|
|
54495
54446
|
fs39.renameSync(temporary, filePath);
|
|
54496
|
-
fsyncDirectory(
|
|
54447
|
+
fsyncDirectory(path47.dirname(filePath));
|
|
54497
54448
|
} finally {
|
|
54498
54449
|
fs39.rmSync(temporary, { force: true });
|
|
54499
54450
|
}
|
|
@@ -54530,7 +54481,7 @@ function expandTilde(value) {
|
|
|
54530
54481
|
if (value === "~")
|
|
54531
54482
|
return os7.homedir();
|
|
54532
54483
|
if (value.startsWith("~/") || value.startsWith("~\\"))
|
|
54533
|
-
return
|
|
54484
|
+
return path48.join(os7.homedir(), value.slice(2));
|
|
54534
54485
|
return value;
|
|
54535
54486
|
}
|
|
54536
54487
|
function existingDirectory(target) {
|
|
@@ -54556,14 +54507,14 @@ function taskRoots(config, resolutionBase = process.cwd()) {
|
|
|
54556
54507
|
const source = sources.get(bundleId);
|
|
54557
54508
|
if (!source)
|
|
54558
54509
|
continue;
|
|
54559
|
-
const configuredRoot = source.type === "filesystem" && source.path ?
|
|
54510
|
+
const configuredRoot = source.type === "filesystem" && source.path ? path48.resolve(resolutionBase, expandTilde(source.path)) : lockContentRootFor(bundleId, source.type);
|
|
54560
54511
|
if (!configuredRoot || !existingDirectory(configuredRoot))
|
|
54561
54512
|
continue;
|
|
54562
|
-
const bundleRoot =
|
|
54513
|
+
const bundleRoot = path48.resolve(configuredRoot);
|
|
54563
54514
|
const component = bundleComponentConfig(bundle);
|
|
54564
|
-
const componentRoot =
|
|
54565
|
-
const relative =
|
|
54566
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
54515
|
+
const componentRoot = path48.resolve(bundleRoot, component?.root ?? ".");
|
|
54516
|
+
const relative = path48.relative(bundleRoot, componentRoot);
|
|
54517
|
+
if (relative === ".." || relative.startsWith(`..${path48.sep}`) || path48.isAbsolute(relative)) {
|
|
54567
54518
|
throw new ConfigError(`Task migration component root ${componentRoot} escapes bundle ${bundleId} at ${bundleRoot}.`, "INVALID_CONFIG_FILE");
|
|
54568
54519
|
}
|
|
54569
54520
|
if (!existingDirectory(componentRoot))
|
|
@@ -54580,7 +54531,7 @@ function taskRoots(config, resolutionBase = process.cwd()) {
|
|
|
54580
54531
|
}
|
|
54581
54532
|
const envStash = process.env.AKM_BUNDLE_DIR?.trim();
|
|
54582
54533
|
if (envStash && existingDirectory(envStash)) {
|
|
54583
|
-
const stashRoot =
|
|
54534
|
+
const stashRoot = path48.resolve(envStash);
|
|
54584
54535
|
if (!rootsByPath.has(stashRoot)) {
|
|
54585
54536
|
const bundleId = deriveBundleId(undefined, stashRoot, new Set(Object.keys(config.bundles ?? {})));
|
|
54586
54537
|
const candidate = taskRoot(bundleId, stashRoot, stashRoot, detectAdapterId(stashRoot), () => true);
|
|
@@ -54649,7 +54600,7 @@ function applyTaskFilesMigration() {
|
|
|
54649
54600
|
const before = inspectCurrent();
|
|
54650
54601
|
if (before.result.taskFiles.changed === 0)
|
|
54651
54602
|
return before.result;
|
|
54652
|
-
const backupPath =
|
|
54603
|
+
const backupPath = path48.join(getDataDir(), "backups", "tasks", `${Date.now()}-${randomUUID3()}`);
|
|
54653
54604
|
const applied = applyTaskFilesPlan(before.plan, { backupRoot: backupPath });
|
|
54654
54605
|
return { ...inspectCurrent().result, backupPath, applied: applied.changed.length };
|
|
54655
54606
|
});
|