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
|
@@ -28797,6 +28797,7 @@ function shapeShowOutput(result, detail, shape = "human") {
|
|
|
28797
28797
|
"steps",
|
|
28798
28798
|
"keys",
|
|
28799
28799
|
"related",
|
|
28800
|
+
"links",
|
|
28800
28801
|
...FRAGMENT_PROVENANCE_FIELDS,
|
|
28801
28802
|
...FRAGMENT_CONTEXT_FIELDS
|
|
28802
28803
|
]);
|
|
@@ -28818,6 +28819,7 @@ function shapeShowOutput(result, detail, shape = "human") {
|
|
|
28818
28819
|
"origin",
|
|
28819
28820
|
"keys",
|
|
28820
28821
|
"related",
|
|
28822
|
+
"links",
|
|
28821
28823
|
...FRAGMENT_PROVENANCE_FIELDS,
|
|
28822
28824
|
...FRAGMENT_CONTEXT_FIELDS
|
|
28823
28825
|
]);
|
|
@@ -28846,6 +28848,7 @@ function shapeShowOutput(result, detail, shape = "human") {
|
|
|
28846
28848
|
"activeRun",
|
|
28847
28849
|
"keys",
|
|
28848
28850
|
"related",
|
|
28851
|
+
"links",
|
|
28849
28852
|
...FRAGMENT_PROVENANCE_FIELDS,
|
|
28850
28853
|
...FRAGMENT_CONTEXT_FIELDS,
|
|
28851
28854
|
"path",
|
|
@@ -30853,6 +30856,22 @@ function formatShowPlain(r, detail) {
|
|
|
30853
30856
|
lines.push(` relationCount: ${String(hit.relationCount ?? 0)}`);
|
|
30854
30857
|
}
|
|
30855
30858
|
}
|
|
30859
|
+
const links = typeof r.links === "object" && r.links !== null ? r.links : undefined;
|
|
30860
|
+
if (links) {
|
|
30861
|
+
lines.push("");
|
|
30862
|
+
lines.push("links:");
|
|
30863
|
+
for (const part of ["outgoing", "incoming", "unresolved"]) {
|
|
30864
|
+
const groups = links[part];
|
|
30865
|
+
if (typeof groups !== "object" || groups === null)
|
|
30866
|
+
continue;
|
|
30867
|
+
lines.push(` ${part}:`);
|
|
30868
|
+
for (const [kind, group] of Object.entries(groups)) {
|
|
30869
|
+
const refs = Array.isArray(group.refs) ? group.refs.map(String) : [];
|
|
30870
|
+
const more = (group.total ?? refs.length) > refs.length ? ` (+${(group.total ?? 0) - refs.length} more)` : "";
|
|
30871
|
+
lines.push(` ${kind}: ${refs.join(", ")}${more}`);
|
|
30872
|
+
}
|
|
30873
|
+
}
|
|
30874
|
+
}
|
|
30856
30875
|
const payloads = [r.content, r.template, r.prompt].filter((value) => value != null).map(String);
|
|
30857
30876
|
if (Array.isArray(r.steps) && r.steps.length > 0) {
|
|
30858
30877
|
if (lines.length > 0)
|
|
@@ -44297,6 +44316,22 @@ function applyFrontmatterDescriptionAndTags(fm, out) {
|
|
|
44297
44316
|
}
|
|
44298
44317
|
}
|
|
44299
44318
|
}
|
|
44319
|
+
function storedCommandRef(action) {
|
|
44320
|
+
return action?.kind === "stored" ? action.ref : undefined;
|
|
44321
|
+
}
|
|
44322
|
+
function workflowStepTarget(spec) {
|
|
44323
|
+
if (!spec?.uses)
|
|
44324
|
+
return;
|
|
44325
|
+
if (spec.uses !== "akm/command")
|
|
44326
|
+
return spec.uses;
|
|
44327
|
+
if (spec.commandMode !== "stored-ref")
|
|
44328
|
+
return;
|
|
44329
|
+
try {
|
|
44330
|
+
return storedCommandRef(parseBuiltinCommandAction(spec.with));
|
|
44331
|
+
} catch {
|
|
44332
|
+
return;
|
|
44333
|
+
}
|
|
44334
|
+
}
|
|
44300
44335
|
function finalizeHints(out, hints) {
|
|
44301
44336
|
if (hints.size > 0)
|
|
44302
44337
|
out.searchHints = Array.from(hints).filter(Boolean);
|
|
@@ -44390,6 +44425,9 @@ function foldRecognizedMetadata(rendererName, file) {
|
|
|
44390
44425
|
hints.add(`prompt:${target.command.ref}`);
|
|
44391
44426
|
else
|
|
44392
44427
|
hints.add(`uses:${target.uses.ref}`);
|
|
44428
|
+
const used = target.uses.kind !== "builtin-command" ? target.uses.ref : storedCommandRef(target.command);
|
|
44429
|
+
if (used)
|
|
44430
|
+
out.uses = [used];
|
|
44393
44431
|
} else {
|
|
44394
44432
|
hints.add(`run:${target.run}`);
|
|
44395
44433
|
}
|
|
@@ -44453,6 +44491,7 @@ function foldRecognizedMetadata(rendererName, file) {
|
|
|
44453
44491
|
return out;
|
|
44454
44492
|
const plan = result.plan;
|
|
44455
44493
|
const hints = new Set;
|
|
44494
|
+
const uses = new Set;
|
|
44456
44495
|
if (plan.preamble)
|
|
44457
44496
|
hints.add(plan.preamble);
|
|
44458
44497
|
for (const step of plan.steps) {
|
|
@@ -44460,8 +44499,13 @@ function foldRecognizedMetadata(rendererName, file) {
|
|
|
44460
44499
|
hints.add(workflowStepInstructions(step));
|
|
44461
44500
|
if (step.gate.criteria[0])
|
|
44462
44501
|
hints.add(step.gate.criteria[0]);
|
|
44502
|
+
const used = workflowStepTarget(step.spec);
|
|
44503
|
+
if (used)
|
|
44504
|
+
uses.add(used);
|
|
44463
44505
|
}
|
|
44464
44506
|
out.searchHints = Array.from(hints).filter(Boolean);
|
|
44507
|
+
if (uses.size > 0)
|
|
44508
|
+
out.uses = [...uses];
|
|
44465
44509
|
if (plan.paramSchemas) {
|
|
44466
44510
|
const parameters = Object.entries(plan.paramSchemas).map(([name, schema]) => {
|
|
44467
44511
|
const description = schema.description;
|
|
@@ -44495,6 +44539,8 @@ function applyFoldedMetadata(entry, folded) {
|
|
|
44495
44539
|
entry.toc = folded.toc;
|
|
44496
44540
|
if (folded.parameters)
|
|
44497
44541
|
entry.parameters = folded.parameters;
|
|
44542
|
+
if (folded.uses)
|
|
44543
|
+
entry.uses = folded.uses;
|
|
44498
44544
|
}
|
|
44499
44545
|
|
|
44500
44546
|
// src/core/adapter/adapters/akm-adapter.ts
|
|
@@ -44553,6 +44599,7 @@ var DOCUMENT_JSON_CARRIED_FIELDS = [
|
|
|
44553
44599
|
"wikiRole",
|
|
44554
44600
|
"sources",
|
|
44555
44601
|
"evidenceSources",
|
|
44602
|
+
"uses",
|
|
44556
44603
|
"provenance"
|
|
44557
44604
|
];
|
|
44558
44605
|
function indexDocumentFromEntry(entry, base, rendererName) {
|
|
@@ -53249,14 +53296,14 @@ function removeDeadResidue(stashDir) {
|
|
|
53249
53296
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
53250
53297
|
import fs40 from "node:fs";
|
|
53251
53298
|
import os7 from "node:os";
|
|
53252
|
-
import
|
|
53299
|
+
import path48 from "node:path";
|
|
53253
53300
|
|
|
53254
53301
|
// scripts/akm-migrate/migrate/task-files.ts
|
|
53255
|
-
import
|
|
53302
|
+
import crypto4 from "node:crypto";
|
|
53256
53303
|
import fs39 from "node:fs";
|
|
53257
|
-
import
|
|
53304
|
+
import path47 from "node:path";
|
|
53258
53305
|
|
|
53259
|
-
// src/tasks/source/task-to-
|
|
53306
|
+
// src/tasks/source/task-to-v4.ts
|
|
53260
53307
|
import crypto3 from "node:crypto";
|
|
53261
53308
|
import path46 from "node:path";
|
|
53262
53309
|
|
|
@@ -53617,7 +53664,51 @@ function parseTaskV3Yaml(input) {
|
|
|
53617
53664
|
});
|
|
53618
53665
|
}
|
|
53619
53666
|
|
|
53620
|
-
// src/tasks/source/task-to-
|
|
53667
|
+
// src/tasks/source/task-to-v4.ts
|
|
53668
|
+
var V3_TOP_LEVEL_KEYS = new Set([
|
|
53669
|
+
"version",
|
|
53670
|
+
"name",
|
|
53671
|
+
"uses",
|
|
53672
|
+
"run",
|
|
53673
|
+
"with",
|
|
53674
|
+
"env",
|
|
53675
|
+
"shell",
|
|
53676
|
+
"working-directory",
|
|
53677
|
+
"akm",
|
|
53678
|
+
"on"
|
|
53679
|
+
]);
|
|
53680
|
+
var V3_AKM_KEYS = new Set([
|
|
53681
|
+
"schedule",
|
|
53682
|
+
"enabled",
|
|
53683
|
+
"description",
|
|
53684
|
+
"when_to_use",
|
|
53685
|
+
"tags",
|
|
53686
|
+
"agent",
|
|
53687
|
+
"engine",
|
|
53688
|
+
"model",
|
|
53689
|
+
"inference",
|
|
53690
|
+
"outputSchema",
|
|
53691
|
+
"tools",
|
|
53692
|
+
"timeout",
|
|
53693
|
+
"redact",
|
|
53694
|
+
"maxSteps",
|
|
53695
|
+
"maxRetries"
|
|
53696
|
+
]);
|
|
53697
|
+
var V3_ON_KEYS = new Set(["schedule", "workflow_dispatch"]);
|
|
53698
|
+
var AKM_HOIST_KEYS = [
|
|
53699
|
+
"description",
|
|
53700
|
+
"when_to_use",
|
|
53701
|
+
"tags",
|
|
53702
|
+
"agent",
|
|
53703
|
+
"engine",
|
|
53704
|
+
"model",
|
|
53705
|
+
"inference",
|
|
53706
|
+
"tools",
|
|
53707
|
+
"timeout",
|
|
53708
|
+
"redact",
|
|
53709
|
+
"maxSteps",
|
|
53710
|
+
"maxRetries"
|
|
53711
|
+
];
|
|
53621
53712
|
var V2_KEYS = new Set([
|
|
53622
53713
|
"version",
|
|
53623
53714
|
"name",
|
|
@@ -53688,9 +53779,13 @@ var KNOWN_PROMPT_REF_FAMILIES = new Set([
|
|
|
53688
53779
|
"tasks",
|
|
53689
53780
|
"workflows"
|
|
53690
53781
|
]);
|
|
53782
|
+
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:` — see docs/migration/v0.9.1-to-v0.9.2.md for the full v2 to v4 field mapping.";
|
|
53691
53783
|
function hash(bytes) {
|
|
53692
53784
|
return crypto3.createHash("sha256").update(bytes).digest("hex");
|
|
53693
53785
|
}
|
|
53786
|
+
function causeMessage(cause) {
|
|
53787
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
53788
|
+
}
|
|
53694
53789
|
function base(input) {
|
|
53695
53790
|
return {
|
|
53696
53791
|
filePath: input.filePath,
|
|
@@ -53702,16 +53797,17 @@ function base(input) {
|
|
|
53702
53797
|
...input.containmentRoot ? { containmentRoot: input.containmentRoot } : {}
|
|
53703
53798
|
};
|
|
53704
53799
|
}
|
|
53705
|
-
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:` — see docs/migration/v0.9.1-to-v0.9.2.md for the full v2 to v4 field mapping.";
|
|
53706
53800
|
function blocked(input, reason, detail) {
|
|
53707
53801
|
return Object.freeze({ status: "blocked", ...base(input), reason, ...detail ? { detail } : {} });
|
|
53708
53802
|
}
|
|
53709
53803
|
function plainRecord(value, label) {
|
|
53710
|
-
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
53804
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
53711
53805
|
throw new Error(`${label} must be a mapping`);
|
|
53806
|
+
}
|
|
53712
53807
|
const prototype = Object.getPrototypeOf(value);
|
|
53713
|
-
if (prototype !== Object.prototype && prototype !== null)
|
|
53808
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
53714
53809
|
throw new Error(`${label} must use a plain or null prototype`);
|
|
53810
|
+
}
|
|
53715
53811
|
return value;
|
|
53716
53812
|
}
|
|
53717
53813
|
function exactString(value, label, nonempty = false) {
|
|
@@ -53725,7 +53821,7 @@ function optionalString(value, label) {
|
|
|
53725
53821
|
return;
|
|
53726
53822
|
return exactString(value, label);
|
|
53727
53823
|
}
|
|
53728
|
-
function
|
|
53824
|
+
function parseRawTaskYaml(input) {
|
|
53729
53825
|
let source;
|
|
53730
53826
|
try {
|
|
53731
53827
|
source = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(input.bytes);
|
|
@@ -53737,7 +53833,7 @@ function parseLegacyTaskYaml(input) {
|
|
|
53737
53833
|
try {
|
|
53738
53834
|
document = $parseDocument(source, { lineCounter: lineCounter2, uniqueKeys: true });
|
|
53739
53835
|
} catch (cause) {
|
|
53740
|
-
throw new Error(`invalid YAML: ${
|
|
53836
|
+
throw new Error(`invalid YAML: ${causeMessage(cause)}`);
|
|
53741
53837
|
}
|
|
53742
53838
|
const [parseError] = document.errors;
|
|
53743
53839
|
if (parseError)
|
|
@@ -53748,11 +53844,181 @@ function parseLegacyTaskYaml(input) {
|
|
|
53748
53844
|
throw new Error(`unsupported YAML construct: ${parseWarning.message}`);
|
|
53749
53845
|
assertBoundedTaskYamlDocument(document, {
|
|
53750
53846
|
filePath: input.filePath,
|
|
53751
|
-
sourceLabel: "task
|
|
53847
|
+
sourceLabel: "task migration source",
|
|
53752
53848
|
lineCounter: lineCounter2
|
|
53753
53849
|
});
|
|
53754
53850
|
return { data: plainRecord(document.toJS({ maxAliasCount: 0 }), "task YAML"), source };
|
|
53755
53851
|
}
|
|
53852
|
+
function planV3DataToV4(input, data) {
|
|
53853
|
+
const unknownTop = Object.keys(data).filter((key) => !V3_TOP_LEVEL_KEYS.has(key));
|
|
53854
|
+
if (unknownTop.length > 0) {
|
|
53855
|
+
return blocked(input, "invalid-v3-task", `unknown v3 field(s): ${unknownTop.join(", ")}`);
|
|
53856
|
+
}
|
|
53857
|
+
const hasUses = Object.hasOwn(data, "uses");
|
|
53858
|
+
const hasRun = Object.hasOwn(data, "run");
|
|
53859
|
+
if (hasUses === hasRun) {
|
|
53860
|
+
return blocked(input, "invalid-v3-task", "requires exactly one executable selector: uses or run");
|
|
53861
|
+
}
|
|
53862
|
+
let akm;
|
|
53863
|
+
if (Object.hasOwn(data, "akm")) {
|
|
53864
|
+
try {
|
|
53865
|
+
akm = plainRecord(data.akm, "akm");
|
|
53866
|
+
} catch (cause) {
|
|
53867
|
+
return blocked(input, "invalid-v3-task", causeMessage(cause));
|
|
53868
|
+
}
|
|
53869
|
+
const unknownAkm = Object.keys(akm).filter((key) => !V3_AKM_KEYS.has(key));
|
|
53870
|
+
if (unknownAkm.length > 0) {
|
|
53871
|
+
return blocked(input, "unrecognized-akm-member", `akm has unknown field(s): ${unknownAkm.join(", ")}`);
|
|
53872
|
+
}
|
|
53873
|
+
if (Object.hasOwn(akm, "enabled") && typeof akm.enabled !== "boolean") {
|
|
53874
|
+
return blocked(input, "invalid-v3-task", "akm.enabled must be a boolean");
|
|
53875
|
+
}
|
|
53876
|
+
if (Object.hasOwn(akm, "schedule") && typeof akm.schedule !== "string") {
|
|
53877
|
+
return blocked(input, "invalid-v3-task", "akm.schedule must be a string");
|
|
53878
|
+
}
|
|
53879
|
+
}
|
|
53880
|
+
const hasOn = Object.hasOwn(data, "on");
|
|
53881
|
+
let onRecord;
|
|
53882
|
+
if (hasOn) {
|
|
53883
|
+
try {
|
|
53884
|
+
onRecord = plainRecord(data.on, "on");
|
|
53885
|
+
} catch (cause) {
|
|
53886
|
+
return blocked(input, "invalid-v3-task", causeMessage(cause));
|
|
53887
|
+
}
|
|
53888
|
+
if (Object.keys(onRecord).length === 0) {
|
|
53889
|
+
return blocked(input, "invalid-v3-task", "on must declare schedule and/or workflow_dispatch.");
|
|
53890
|
+
}
|
|
53891
|
+
const unknownOn = Object.keys(onRecord).filter((key) => !V3_ON_KEYS.has(key));
|
|
53892
|
+
if (unknownOn.length > 0) {
|
|
53893
|
+
return blocked(input, "invalid-v3-task", `on has unknown field(s): ${unknownOn.join(", ")}`);
|
|
53894
|
+
}
|
|
53895
|
+
if (Object.hasOwn(onRecord, "workflow_dispatch") && onRecord.workflow_dispatch !== null) {
|
|
53896
|
+
let dispatchMapping;
|
|
53897
|
+
try {
|
|
53898
|
+
dispatchMapping = plainRecord(onRecord.workflow_dispatch, "on.workflow_dispatch");
|
|
53899
|
+
} catch (cause) {
|
|
53900
|
+
return blocked(input, "invalid-v3-task", causeMessage(cause));
|
|
53901
|
+
}
|
|
53902
|
+
if (Object.keys(dispatchMapping).length > 0) {
|
|
53903
|
+
return blocked(input, "invalid-v3-task", "on.workflow_dispatch must be null or an empty mapping; inputs are unsupported.");
|
|
53904
|
+
}
|
|
53905
|
+
}
|
|
53906
|
+
}
|
|
53907
|
+
const hasAkmSchedule = akm !== undefined && Object.hasOwn(akm, "schedule");
|
|
53908
|
+
if (hasAkmSchedule && hasOn) {
|
|
53909
|
+
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.");
|
|
53910
|
+
}
|
|
53911
|
+
if (!hasAkmSchedule && !hasOn) {
|
|
53912
|
+
return blocked(input, "invalid-v3-task", "requires exactly one scheduling source: akm.schedule or on.");
|
|
53913
|
+
}
|
|
53914
|
+
const hasWith = Object.hasOwn(data, "with");
|
|
53915
|
+
let usesTarget;
|
|
53916
|
+
if (hasUses) {
|
|
53917
|
+
let usesValue;
|
|
53918
|
+
try {
|
|
53919
|
+
usesValue = exactString(data.uses, "uses", true);
|
|
53920
|
+
} catch (cause) {
|
|
53921
|
+
return blocked(input, "invalid-v3-task", causeMessage(cause));
|
|
53922
|
+
}
|
|
53923
|
+
try {
|
|
53924
|
+
usesTarget = classifyTaskV3Uses(usesValue);
|
|
53925
|
+
} catch (cause) {
|
|
53926
|
+
return blocked(input, "invalid-v3-task", causeMessage(cause));
|
|
53927
|
+
}
|
|
53928
|
+
if (usesTarget.kind === "github-action") {
|
|
53929
|
+
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.`);
|
|
53930
|
+
}
|
|
53931
|
+
if (hasWith && usesTarget.kind !== "builtin-command") {
|
|
53932
|
+
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.`);
|
|
53933
|
+
}
|
|
53934
|
+
} else if (hasWith) {
|
|
53935
|
+
return blocked(input, "invalid-v3-task", "with is legal only with uses");
|
|
53936
|
+
}
|
|
53937
|
+
if (!input.writable || input.onDiskWritable === false) {
|
|
53938
|
+
return blocked(input, "read-only-source", !input.writable ? "the owning source is not writable" : "the source file or publication directory is read-only");
|
|
53939
|
+
}
|
|
53940
|
+
let scheduleField;
|
|
53941
|
+
const notices = [];
|
|
53942
|
+
if (hasAkmSchedule) {
|
|
53943
|
+
const cron = akm.schedule;
|
|
53944
|
+
scheduleField = cron;
|
|
53945
|
+
} else {
|
|
53946
|
+
const rawSchedule = onRecord !== undefined && Object.hasOwn(onRecord, "schedule") ? onRecord.schedule : undefined;
|
|
53947
|
+
if (rawSchedule !== undefined) {
|
|
53948
|
+
if (!Array.isArray(rawSchedule) || rawSchedule.length === 0) {
|
|
53949
|
+
return blocked(input, "invalid-v3-task", "on.schedule must be a non-empty list of {cron} records");
|
|
53950
|
+
}
|
|
53951
|
+
const crons = [];
|
|
53952
|
+
for (const entry of rawSchedule) {
|
|
53953
|
+
let record;
|
|
53954
|
+
try {
|
|
53955
|
+
record = plainRecord(entry, "on.schedule[]");
|
|
53956
|
+
} catch (cause) {
|
|
53957
|
+
return blocked(input, "invalid-v3-task", causeMessage(cause));
|
|
53958
|
+
}
|
|
53959
|
+
const keys = Object.keys(record);
|
|
53960
|
+
if (keys.length !== 1 || keys[0] !== "cron" || typeof record.cron !== "string" || record.cron.length === 0) {
|
|
53961
|
+
return blocked(input, "invalid-v3-task", "each on.schedule entry must be exactly {cron: <non-empty string>}");
|
|
53962
|
+
}
|
|
53963
|
+
crons.push(record.cron);
|
|
53964
|
+
}
|
|
53965
|
+
scheduleField = crons.map((cron) => ({ cron }));
|
|
53966
|
+
} else {
|
|
53967
|
+
notices.push("schedule: is absent from the migrated document — 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.");
|
|
53968
|
+
}
|
|
53969
|
+
}
|
|
53970
|
+
const out = { version: 4 };
|
|
53971
|
+
if (Object.hasOwn(data, "name"))
|
|
53972
|
+
out.name = data.name;
|
|
53973
|
+
if (hasUses)
|
|
53974
|
+
out.uses = data.uses;
|
|
53975
|
+
else
|
|
53976
|
+
out.run = data.run;
|
|
53977
|
+
if (Object.hasOwn(data, "shell"))
|
|
53978
|
+
out.shell = data.shell;
|
|
53979
|
+
if (hasWith)
|
|
53980
|
+
out.with = data.with;
|
|
53981
|
+
if (Object.hasOwn(data, "env"))
|
|
53982
|
+
out.env = data.env;
|
|
53983
|
+
if (Object.hasOwn(data, "working-directory"))
|
|
53984
|
+
out["working-directory"] = data["working-directory"];
|
|
53985
|
+
if (scheduleField !== undefined)
|
|
53986
|
+
out.schedule = scheduleField;
|
|
53987
|
+
if (akm) {
|
|
53988
|
+
for (const key of AKM_HOIST_KEYS) {
|
|
53989
|
+
if (Object.hasOwn(akm, key))
|
|
53990
|
+
out[key] = akm[key];
|
|
53991
|
+
}
|
|
53992
|
+
if (Object.hasOwn(akm, "outputSchema") && akm.outputSchema !== null) {
|
|
53993
|
+
if (usesTarget !== undefined && (usesTarget.kind === "command" || usesTarget.kind === "builtin-command")) {
|
|
53994
|
+
out.output = akm.outputSchema;
|
|
53995
|
+
} else {
|
|
53996
|
+
const targetLabel = usesTarget === undefined ? "a run: target" : `the "${usesTarget.ref}" target`;
|
|
53997
|
+
notices.push(`akm.outputSchema was dropped rather than hoisted to output: — 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.`);
|
|
53998
|
+
}
|
|
53999
|
+
}
|
|
54000
|
+
}
|
|
54001
|
+
const afterYaml = $stringify(out);
|
|
54002
|
+
const after = Buffer.from(afterYaml, "utf8");
|
|
54003
|
+
try {
|
|
54004
|
+
parseTaskSourceV4({
|
|
54005
|
+
yaml: afterYaml,
|
|
54006
|
+
filePath: input.filePath,
|
|
54007
|
+
...input.containmentRoot ? { workspaceRoot: input.containmentRoot } : {}
|
|
54008
|
+
});
|
|
54009
|
+
} catch (cause) {
|
|
54010
|
+
return blocked(input, "generated-v4-validation-failed", causeMessage(cause));
|
|
54011
|
+
}
|
|
54012
|
+
const notice = notices.join(" ");
|
|
54013
|
+
return Object.freeze({
|
|
54014
|
+
status: "changed",
|
|
54015
|
+
...base(input),
|
|
54016
|
+
reason: "task-converted",
|
|
54017
|
+
after,
|
|
54018
|
+
afterHash: hash(after),
|
|
54019
|
+
...notice ? { notice } : {}
|
|
54020
|
+
});
|
|
54021
|
+
}
|
|
53756
54022
|
function validateCommonV2(data) {
|
|
53757
54023
|
const unknown = Object.keys(data).filter((key) => !V2_KEYS.has(key));
|
|
53758
54024
|
if (unknown.length > 0)
|
|
@@ -53947,424 +54213,120 @@ function migratedObject(data) {
|
|
|
53947
54213
|
function isReason(value) {
|
|
53948
54214
|
return typeof value === "string";
|
|
53949
54215
|
}
|
|
53950
|
-
function
|
|
53951
|
-
if (data.version !== 2) {
|
|
53952
|
-
return blocked(input, "unsupported-task-version", `expected normalized legacy version 2, got ${String(data.version)}`);
|
|
53953
|
-
}
|
|
54216
|
+
function planV2DataToV4(input, data) {
|
|
53954
54217
|
if (!input.writable || input.onDiskWritable === false) {
|
|
53955
54218
|
return blocked(input, "read-only-source", !input.writable ? "the owning source is not writable" : "the source file or publication directory is read-only");
|
|
53956
54219
|
}
|
|
53957
54220
|
try {
|
|
53958
54221
|
validateTaskId(path46.basename(input.filePath, ".yml"));
|
|
53959
54222
|
} catch (cause) {
|
|
53960
|
-
return blocked(input, "invalid-v2-task",
|
|
54223
|
+
return blocked(input, "invalid-v2-task", causeMessage(cause));
|
|
53961
54224
|
}
|
|
53962
54225
|
let migrated;
|
|
53963
54226
|
try {
|
|
53964
54227
|
migrated = migratedObject(data);
|
|
53965
54228
|
} catch (cause) {
|
|
53966
|
-
return blocked(input, "invalid-v2-task",
|
|
54229
|
+
return blocked(input, "invalid-v2-task", causeMessage(cause));
|
|
53967
54230
|
}
|
|
53968
54231
|
if (isReason(migrated)) {
|
|
53969
54232
|
const detail = migrated === "argv-array-has-no-portable-shell-string" ? ARGV_ARRAY_BLOCK_DETAIL : undefined;
|
|
53970
54233
|
return blocked(input, migrated, detail);
|
|
53971
54234
|
}
|
|
53972
|
-
const
|
|
54235
|
+
const v3Yaml = $stringify(migrated);
|
|
53973
54236
|
try {
|
|
53974
54237
|
parseTaskV3Yaml({
|
|
53975
|
-
yaml:
|
|
54238
|
+
yaml: v3Yaml,
|
|
53976
54239
|
filePath: input.filePath,
|
|
53977
54240
|
...input.containmentRoot ? { workspaceRoot: input.containmentRoot } : {}
|
|
53978
54241
|
});
|
|
53979
54242
|
} catch (cause) {
|
|
53980
|
-
return blocked(input, "generated-v3-validation-failed",
|
|
54243
|
+
return blocked(input, "generated-v3-validation-failed", causeMessage(cause));
|
|
53981
54244
|
}
|
|
53982
|
-
|
|
53983
|
-
status: "changed",
|
|
53984
|
-
...base(input),
|
|
53985
|
-
reason: "task-converted",
|
|
53986
|
-
after,
|
|
53987
|
-
afterHash: hash(after)
|
|
53988
|
-
});
|
|
53989
|
-
}
|
|
53990
|
-
function planTaskToV3File(input) {
|
|
53991
|
-
let data;
|
|
53992
|
-
let source;
|
|
54245
|
+
let v3Data;
|
|
53993
54246
|
try {
|
|
53994
|
-
({ data
|
|
54247
|
+
({ data: v3Data } = parseRawTaskYaml({ ...input, bytes: Buffer.from(v3Yaml, "utf8") }));
|
|
53995
54248
|
} catch (cause) {
|
|
53996
|
-
return blocked(input, "invalid-task-yaml",
|
|
54249
|
+
return blocked(input, "invalid-task-yaml", causeMessage(cause));
|
|
54250
|
+
}
|
|
54251
|
+
const v4Outcome = planV3DataToV4(input, v3Data);
|
|
54252
|
+
return v4Outcome.status === "changed" ? { ...v4Outcome, reason: "task-converted" } : v4Outcome;
|
|
54253
|
+
}
|
|
54254
|
+
function planV4Cleanup(input, source) {
|
|
54255
|
+
const document = $parseDocument(source, { uniqueKeys: true });
|
|
54256
|
+
const schedule = document.get("schedule", true);
|
|
54257
|
+
let removed = false;
|
|
54258
|
+
if ($isSeq(schedule)) {
|
|
54259
|
+
for (const entry of schedule.items) {
|
|
54260
|
+
if (!$isMap(entry) || !entry.has("enabled"))
|
|
54261
|
+
continue;
|
|
54262
|
+
entry.delete("enabled");
|
|
54263
|
+
removed = true;
|
|
54264
|
+
}
|
|
53997
54265
|
}
|
|
53998
|
-
if (
|
|
54266
|
+
if (removed) {
|
|
54267
|
+
if (!input.writable || input.onDiskWritable === false) {
|
|
54268
|
+
return blocked(input, "read-only-source", !input.writable ? "the owning source is not writable" : "the source file or publication directory is read-only");
|
|
54269
|
+
}
|
|
54270
|
+
const after = Buffer.from(document.toString(), "utf8");
|
|
53999
54271
|
try {
|
|
54000
|
-
|
|
54001
|
-
yaml:
|
|
54272
|
+
parseTaskSourceV4({
|
|
54273
|
+
yaml: after.toString("utf8"),
|
|
54002
54274
|
filePath: input.filePath,
|
|
54003
54275
|
...input.containmentRoot ? { workspaceRoot: input.containmentRoot } : {}
|
|
54004
54276
|
});
|
|
54005
|
-
return Object.freeze({ status: "skipped", ...base(input), reason: "already-v3" });
|
|
54006
|
-
} catch (cause) {
|
|
54007
|
-
return blocked(input, "invalid-v3-task", cause instanceof Error ? cause.message : String(cause));
|
|
54008
|
-
}
|
|
54009
|
-
}
|
|
54010
|
-
if (data.version === 4) {
|
|
54011
|
-
return Object.freeze({ status: "skipped", ...base(input), reason: "already-v4" });
|
|
54012
|
-
}
|
|
54013
|
-
if (data.version !== 2) {
|
|
54014
|
-
return blocked(input, "unsupported-task-version", `expected version 2, 3, or 4, got ${String(data.version)}`);
|
|
54015
|
-
}
|
|
54016
|
-
return planLegacyTaskDataToV3(input, data);
|
|
54017
|
-
}
|
|
54018
|
-
|
|
54019
|
-
// src/tasks/source/task-to-v4.ts
|
|
54020
|
-
import crypto4 from "node:crypto";
|
|
54021
|
-
import path47 from "node:path";
|
|
54022
|
-
var V3_TOP_LEVEL_KEYS = new Set([
|
|
54023
|
-
"version",
|
|
54024
|
-
"name",
|
|
54025
|
-
"uses",
|
|
54026
|
-
"run",
|
|
54027
|
-
"with",
|
|
54028
|
-
"env",
|
|
54029
|
-
"shell",
|
|
54030
|
-
"working-directory",
|
|
54031
|
-
"akm",
|
|
54032
|
-
"on"
|
|
54033
|
-
]);
|
|
54034
|
-
var V3_AKM_KEYS = new Set([
|
|
54035
|
-
"schedule",
|
|
54036
|
-
"enabled",
|
|
54037
|
-
"description",
|
|
54038
|
-
"when_to_use",
|
|
54039
|
-
"tags",
|
|
54040
|
-
"agent",
|
|
54041
|
-
"engine",
|
|
54042
|
-
"model",
|
|
54043
|
-
"inference",
|
|
54044
|
-
"outputSchema",
|
|
54045
|
-
"tools",
|
|
54046
|
-
"timeout",
|
|
54047
|
-
"redact",
|
|
54048
|
-
"maxSteps",
|
|
54049
|
-
"maxRetries"
|
|
54050
|
-
]);
|
|
54051
|
-
var V3_ON_KEYS = new Set(["schedule", "workflow_dispatch"]);
|
|
54052
|
-
var AKM_HOIST_KEYS = [
|
|
54053
|
-
"description",
|
|
54054
|
-
"when_to_use",
|
|
54055
|
-
"tags",
|
|
54056
|
-
"agent",
|
|
54057
|
-
"engine",
|
|
54058
|
-
"model",
|
|
54059
|
-
"inference",
|
|
54060
|
-
"tools",
|
|
54061
|
-
"timeout",
|
|
54062
|
-
"redact",
|
|
54063
|
-
"maxSteps",
|
|
54064
|
-
"maxRetries"
|
|
54065
|
-
];
|
|
54066
|
-
function hash2(bytes) {
|
|
54067
|
-
return crypto4.createHash("sha256").update(bytes).digest("hex");
|
|
54068
|
-
}
|
|
54069
|
-
function causeMessage(cause) {
|
|
54070
|
-
return cause instanceof Error ? cause.message : String(cause);
|
|
54071
|
-
}
|
|
54072
|
-
function base2(input) {
|
|
54073
|
-
return {
|
|
54074
|
-
filePath: input.filePath,
|
|
54075
|
-
before: Buffer.from(input.bytes),
|
|
54076
|
-
beforeHash: hash2(input.bytes),
|
|
54077
|
-
mode: input.mode,
|
|
54078
|
-
writable: input.writable,
|
|
54079
|
-
...input.onDiskWritable !== undefined ? { onDiskWritable: input.onDiskWritable } : {},
|
|
54080
|
-
...input.containmentRoot ? { containmentRoot: input.containmentRoot } : {}
|
|
54081
|
-
};
|
|
54082
|
-
}
|
|
54083
|
-
function blocked2(input, reason, detail) {
|
|
54084
|
-
return Object.freeze({ status: "blocked", ...base2(input), reason, ...detail ? { detail } : {} });
|
|
54085
|
-
}
|
|
54086
|
-
function plainRecord2(value, label) {
|
|
54087
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
54088
|
-
throw new Error(`${label} must be a mapping`);
|
|
54089
|
-
}
|
|
54090
|
-
const prototype = Object.getPrototypeOf(value);
|
|
54091
|
-
if (prototype !== Object.prototype && prototype !== null) {
|
|
54092
|
-
throw new Error(`${label} must use a plain or null prototype`);
|
|
54093
|
-
}
|
|
54094
|
-
return value;
|
|
54095
|
-
}
|
|
54096
|
-
function exactString2(value, label, nonempty = false) {
|
|
54097
|
-
if (typeof value !== "string" || nonempty && value.trim().length === 0) {
|
|
54098
|
-
throw new Error(`${label} must be ${nonempty ? "a non-empty " : "a "}string`);
|
|
54099
|
-
}
|
|
54100
|
-
return value;
|
|
54101
|
-
}
|
|
54102
|
-
function parseV3RawYaml(input) {
|
|
54103
|
-
let source;
|
|
54104
|
-
try {
|
|
54105
|
-
source = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(input.bytes);
|
|
54106
|
-
} catch {
|
|
54107
|
-
throw new Error("task YAML contains invalid UTF-8 bytes");
|
|
54108
|
-
}
|
|
54109
|
-
const lineCounter2 = new $LineCounter;
|
|
54110
|
-
let document;
|
|
54111
|
-
try {
|
|
54112
|
-
document = $parseDocument(source, { lineCounter: lineCounter2, uniqueKeys: true });
|
|
54113
|
-
} catch (cause) {
|
|
54114
|
-
throw new Error(`invalid YAML: ${causeMessage(cause)}`);
|
|
54115
|
-
}
|
|
54116
|
-
const [parseError] = document.errors;
|
|
54117
|
-
if (parseError)
|
|
54118
|
-
throw new Error(`invalid YAML: ${parseError.message.split(`
|
|
54119
|
-
`)[0]}`);
|
|
54120
|
-
const [parseWarning] = document.warnings;
|
|
54121
|
-
if (parseWarning)
|
|
54122
|
-
throw new Error(`unsupported YAML construct: ${parseWarning.message}`);
|
|
54123
|
-
assertBoundedTaskYamlDocument(document, {
|
|
54124
|
-
filePath: input.filePath,
|
|
54125
|
-
sourceLabel: "task v3 migration source",
|
|
54126
|
-
lineCounter: lineCounter2
|
|
54127
|
-
});
|
|
54128
|
-
return { data: plainRecord2(document.toJS({ maxAliasCount: 0 }), "task YAML"), source };
|
|
54129
|
-
}
|
|
54130
|
-
function planV3DataToV4(input, data) {
|
|
54131
|
-
const unknownTop = Object.keys(data).filter((key) => !V3_TOP_LEVEL_KEYS.has(key));
|
|
54132
|
-
if (unknownTop.length > 0) {
|
|
54133
|
-
return blocked2(input, "invalid-v3-task", `unknown v3 field(s): ${unknownTop.join(", ")}`);
|
|
54134
|
-
}
|
|
54135
|
-
const hasUses = Object.hasOwn(data, "uses");
|
|
54136
|
-
const hasRun = Object.hasOwn(data, "run");
|
|
54137
|
-
if (hasUses === hasRun) {
|
|
54138
|
-
return blocked2(input, "invalid-v3-task", "requires exactly one executable selector: uses or run");
|
|
54139
|
-
}
|
|
54140
|
-
let akm;
|
|
54141
|
-
if (Object.hasOwn(data, "akm")) {
|
|
54142
|
-
try {
|
|
54143
|
-
akm = plainRecord2(data.akm, "akm");
|
|
54144
|
-
} catch (cause) {
|
|
54145
|
-
return blocked2(input, "invalid-v3-task", causeMessage(cause));
|
|
54146
|
-
}
|
|
54147
|
-
const unknownAkm = Object.keys(akm).filter((key) => !V3_AKM_KEYS.has(key));
|
|
54148
|
-
if (unknownAkm.length > 0) {
|
|
54149
|
-
return blocked2(input, "unrecognized-akm-member", `akm has unknown field(s): ${unknownAkm.join(", ")}`);
|
|
54150
|
-
}
|
|
54151
|
-
if (Object.hasOwn(akm, "enabled") && typeof akm.enabled !== "boolean") {
|
|
54152
|
-
return blocked2(input, "invalid-v3-task", "akm.enabled must be a boolean");
|
|
54153
|
-
}
|
|
54154
|
-
if (Object.hasOwn(akm, "schedule") && typeof akm.schedule !== "string") {
|
|
54155
|
-
return blocked2(input, "invalid-v3-task", "akm.schedule must be a string");
|
|
54156
|
-
}
|
|
54157
|
-
}
|
|
54158
|
-
const hasOn = Object.hasOwn(data, "on");
|
|
54159
|
-
let onRecord;
|
|
54160
|
-
if (hasOn) {
|
|
54161
|
-
try {
|
|
54162
|
-
onRecord = plainRecord2(data.on, "on");
|
|
54163
|
-
} catch (cause) {
|
|
54164
|
-
return blocked2(input, "invalid-v3-task", causeMessage(cause));
|
|
54165
|
-
}
|
|
54166
|
-
if (Object.keys(onRecord).length === 0) {
|
|
54167
|
-
return blocked2(input, "invalid-v3-task", "on must declare schedule and/or workflow_dispatch.");
|
|
54168
|
-
}
|
|
54169
|
-
const unknownOn = Object.keys(onRecord).filter((key) => !V3_ON_KEYS.has(key));
|
|
54170
|
-
if (unknownOn.length > 0) {
|
|
54171
|
-
return blocked2(input, "invalid-v3-task", `on has unknown field(s): ${unknownOn.join(", ")}`);
|
|
54172
|
-
}
|
|
54173
|
-
if (Object.hasOwn(onRecord, "workflow_dispatch") && onRecord.workflow_dispatch !== null) {
|
|
54174
|
-
let dispatchMapping;
|
|
54175
|
-
try {
|
|
54176
|
-
dispatchMapping = plainRecord2(onRecord.workflow_dispatch, "on.workflow_dispatch");
|
|
54177
|
-
} catch (cause) {
|
|
54178
|
-
return blocked2(input, "invalid-v3-task", causeMessage(cause));
|
|
54179
|
-
}
|
|
54180
|
-
if (Object.keys(dispatchMapping).length > 0) {
|
|
54181
|
-
return blocked2(input, "invalid-v3-task", "on.workflow_dispatch must be null or an empty mapping; inputs are unsupported.");
|
|
54182
|
-
}
|
|
54183
|
-
}
|
|
54184
|
-
}
|
|
54185
|
-
const hasAkmSchedule = akm !== undefined && Object.hasOwn(akm, "schedule");
|
|
54186
|
-
if (hasAkmSchedule && hasOn) {
|
|
54187
|
-
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.");
|
|
54188
|
-
}
|
|
54189
|
-
if (!hasAkmSchedule && !hasOn) {
|
|
54190
|
-
return blocked2(input, "invalid-v3-task", "requires exactly one scheduling source: akm.schedule or on.");
|
|
54191
|
-
}
|
|
54192
|
-
const hasWith = Object.hasOwn(data, "with");
|
|
54193
|
-
let usesTarget;
|
|
54194
|
-
if (hasUses) {
|
|
54195
|
-
let usesValue;
|
|
54196
|
-
try {
|
|
54197
|
-
usesValue = exactString2(data.uses, "uses", true);
|
|
54198
54277
|
} catch (cause) {
|
|
54199
|
-
return
|
|
54200
|
-
}
|
|
54201
|
-
try {
|
|
54202
|
-
usesTarget = classifyTaskV3Uses(usesValue);
|
|
54203
|
-
} catch (cause) {
|
|
54204
|
-
return blocked2(input, "invalid-v3-task", causeMessage(cause));
|
|
54205
|
-
}
|
|
54206
|
-
if (usesTarget.kind === "github-action") {
|
|
54207
|
-
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.`);
|
|
54208
|
-
}
|
|
54209
|
-
if (hasWith && usesTarget.kind !== "builtin-command") {
|
|
54210
|
-
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.`);
|
|
54211
|
-
}
|
|
54212
|
-
} else if (hasWith) {
|
|
54213
|
-
return blocked2(input, "invalid-v3-task", "with is legal only with uses");
|
|
54214
|
-
}
|
|
54215
|
-
if (!input.writable || input.onDiskWritable === false) {
|
|
54216
|
-
return blocked2(input, "read-only-source", !input.writable ? "the owning source is not writable" : "the source file or publication directory is read-only");
|
|
54217
|
-
}
|
|
54218
|
-
let scheduleField;
|
|
54219
|
-
const notices = [];
|
|
54220
|
-
if (hasAkmSchedule) {
|
|
54221
|
-
const cron = akm.schedule;
|
|
54222
|
-
scheduleField = cron;
|
|
54223
|
-
} else {
|
|
54224
|
-
const rawSchedule = onRecord !== undefined && Object.hasOwn(onRecord, "schedule") ? onRecord.schedule : undefined;
|
|
54225
|
-
if (rawSchedule !== undefined) {
|
|
54226
|
-
if (!Array.isArray(rawSchedule) || rawSchedule.length === 0) {
|
|
54227
|
-
return blocked2(input, "invalid-v3-task", "on.schedule must be a non-empty list of {cron} records");
|
|
54228
|
-
}
|
|
54229
|
-
const crons = [];
|
|
54230
|
-
for (const entry of rawSchedule) {
|
|
54231
|
-
let record;
|
|
54232
|
-
try {
|
|
54233
|
-
record = plainRecord2(entry, "on.schedule[]");
|
|
54234
|
-
} catch (cause) {
|
|
54235
|
-
return blocked2(input, "invalid-v3-task", causeMessage(cause));
|
|
54236
|
-
}
|
|
54237
|
-
const keys = Object.keys(record);
|
|
54238
|
-
if (keys.length !== 1 || keys[0] !== "cron" || typeof record.cron !== "string" || record.cron.length === 0) {
|
|
54239
|
-
return blocked2(input, "invalid-v3-task", "each on.schedule entry must be exactly {cron: <non-empty string>}");
|
|
54240
|
-
}
|
|
54241
|
-
crons.push(record.cron);
|
|
54242
|
-
}
|
|
54243
|
-
scheduleField = crons.map((cron) => ({ cron }));
|
|
54244
|
-
} else {
|
|
54245
|
-
notices.push("schedule: is absent from the migrated document — 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.");
|
|
54246
|
-
}
|
|
54247
|
-
}
|
|
54248
|
-
const out = { version: 4 };
|
|
54249
|
-
if (Object.hasOwn(data, "name"))
|
|
54250
|
-
out.name = data.name;
|
|
54251
|
-
if (hasUses)
|
|
54252
|
-
out.uses = data.uses;
|
|
54253
|
-
else
|
|
54254
|
-
out.run = data.run;
|
|
54255
|
-
if (Object.hasOwn(data, "shell"))
|
|
54256
|
-
out.shell = data.shell;
|
|
54257
|
-
if (hasWith)
|
|
54258
|
-
out.with = data.with;
|
|
54259
|
-
if (Object.hasOwn(data, "env"))
|
|
54260
|
-
out.env = data.env;
|
|
54261
|
-
if (Object.hasOwn(data, "working-directory"))
|
|
54262
|
-
out["working-directory"] = data["working-directory"];
|
|
54263
|
-
if (scheduleField !== undefined)
|
|
54264
|
-
out.schedule = scheduleField;
|
|
54265
|
-
if (akm) {
|
|
54266
|
-
for (const key of AKM_HOIST_KEYS) {
|
|
54267
|
-
if (Object.hasOwn(akm, key))
|
|
54268
|
-
out[key] = akm[key];
|
|
54269
|
-
}
|
|
54270
|
-
if (Object.hasOwn(akm, "outputSchema") && akm.outputSchema !== null) {
|
|
54271
|
-
if (usesTarget !== undefined && (usesTarget.kind === "command" || usesTarget.kind === "builtin-command")) {
|
|
54272
|
-
out.output = akm.outputSchema;
|
|
54273
|
-
} else {
|
|
54274
|
-
const targetLabel = usesTarget === undefined ? "a run: target" : `the "${usesTarget.ref}" target`;
|
|
54275
|
-
notices.push(`akm.outputSchema was dropped rather than hoisted to output: — 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.`);
|
|
54276
|
-
}
|
|
54278
|
+
return blocked(input, "generated-v4-validation-failed", causeMessage(cause));
|
|
54277
54279
|
}
|
|
54280
|
+
return Object.freeze({
|
|
54281
|
+
status: "changed",
|
|
54282
|
+
...base(input),
|
|
54283
|
+
reason: "source-enablement-removed",
|
|
54284
|
+
after,
|
|
54285
|
+
afterHash: hash(after),
|
|
54286
|
+
notice: "Removed source-owned schedule enablement; scheduler activation is now host-local config."
|
|
54287
|
+
});
|
|
54278
54288
|
}
|
|
54279
|
-
const afterYaml = $stringify(out);
|
|
54280
|
-
const after = Buffer.from(afterYaml, "utf8");
|
|
54281
54289
|
try {
|
|
54282
54290
|
parseTaskSourceV4({
|
|
54283
|
-
yaml:
|
|
54291
|
+
yaml: source,
|
|
54284
54292
|
filePath: input.filePath,
|
|
54285
54293
|
...input.containmentRoot ? { workspaceRoot: input.containmentRoot } : {}
|
|
54286
54294
|
});
|
|
54295
|
+
return Object.freeze({ status: "skipped", ...base(input), reason: "already-v4" });
|
|
54287
54296
|
} catch (cause) {
|
|
54288
|
-
return
|
|
54297
|
+
return blocked(input, "invalid-v4-task", causeMessage(cause));
|
|
54289
54298
|
}
|
|
54290
|
-
const notice = notices.join(" ");
|
|
54291
|
-
return Object.freeze({
|
|
54292
|
-
status: "changed",
|
|
54293
|
-
...base2(input),
|
|
54294
|
-
reason: "task-converted",
|
|
54295
|
-
after,
|
|
54296
|
-
afterHash: hash2(after),
|
|
54297
|
-
...notice ? { notice } : {}
|
|
54298
|
-
});
|
|
54299
54299
|
}
|
|
54300
54300
|
function planTaskToV4File(input) {
|
|
54301
54301
|
let data;
|
|
54302
54302
|
let source;
|
|
54303
54303
|
try {
|
|
54304
|
-
({ data, source } =
|
|
54304
|
+
({ data, source } = parseRawTaskYaml(input));
|
|
54305
54305
|
} catch (cause) {
|
|
54306
|
-
return
|
|
54306
|
+
return blocked(input, "invalid-task-yaml", causeMessage(cause));
|
|
54307
54307
|
}
|
|
54308
54308
|
if (data.version === 4) {
|
|
54309
|
-
|
|
54310
|
-
|
|
54311
|
-
|
|
54312
|
-
if ($isSeq(schedule)) {
|
|
54313
|
-
for (const entry of schedule.items) {
|
|
54314
|
-
if (!$isMap(entry) || !entry.has("enabled"))
|
|
54315
|
-
continue;
|
|
54316
|
-
entry.delete("enabled");
|
|
54317
|
-
removed = true;
|
|
54318
|
-
}
|
|
54319
|
-
}
|
|
54320
|
-
if (removed) {
|
|
54321
|
-
if (!input.writable || input.onDiskWritable === false) {
|
|
54322
|
-
return blocked2(input, "read-only-source", !input.writable ? "the owning source is not writable" : "the source file or publication directory is read-only");
|
|
54323
|
-
}
|
|
54324
|
-
const after = Buffer.from(document.toString(), "utf8");
|
|
54325
|
-
try {
|
|
54326
|
-
parseTaskSourceV4({
|
|
54327
|
-
yaml: after.toString("utf8"),
|
|
54328
|
-
filePath: input.filePath,
|
|
54329
|
-
...input.containmentRoot ? { workspaceRoot: input.containmentRoot } : {}
|
|
54330
|
-
});
|
|
54331
|
-
} catch (cause) {
|
|
54332
|
-
return blocked2(input, "generated-v4-validation-failed", causeMessage(cause));
|
|
54333
|
-
}
|
|
54334
|
-
return Object.freeze({
|
|
54335
|
-
status: "changed",
|
|
54336
|
-
...base2(input),
|
|
54337
|
-
reason: "source-enablement-removed",
|
|
54338
|
-
after,
|
|
54339
|
-
afterHash: hash2(after),
|
|
54340
|
-
notice: "Removed source-owned schedule enablement; scheduler activation is now host-local config."
|
|
54341
|
-
});
|
|
54342
|
-
}
|
|
54309
|
+
return planV4Cleanup(input, source);
|
|
54310
|
+
}
|
|
54311
|
+
if (data.version === 3) {
|
|
54343
54312
|
try {
|
|
54344
|
-
|
|
54313
|
+
parseTaskV3Yaml({
|
|
54345
54314
|
yaml: source,
|
|
54346
54315
|
filePath: input.filePath,
|
|
54347
54316
|
...input.containmentRoot ? { workspaceRoot: input.containmentRoot } : {}
|
|
54348
54317
|
});
|
|
54349
|
-
return Object.freeze({ status: "skipped", ...base2(input), reason: "already-v4" });
|
|
54350
54318
|
} catch (cause) {
|
|
54351
|
-
return
|
|
54319
|
+
return blocked(input, "invalid-v3-task", causeMessage(cause));
|
|
54352
54320
|
}
|
|
54321
|
+
return planV3DataToV4(input, data);
|
|
54353
54322
|
}
|
|
54354
54323
|
if (data.version === 2) {
|
|
54355
|
-
return
|
|
54356
|
-
status: "skipped",
|
|
54357
|
-
...base2(input),
|
|
54358
|
-
reason: "pending-v2-to-v3-migration"
|
|
54359
|
-
});
|
|
54360
|
-
}
|
|
54361
|
-
if (data.version !== 3) {
|
|
54362
|
-
return blocked2(input, "unsupported-task-version", `expected version 2, 3, or 4, got ${String(data.version)}`);
|
|
54324
|
+
return planV2DataToV4(input, data);
|
|
54363
54325
|
}
|
|
54364
|
-
return
|
|
54326
|
+
return blocked(input, "unsupported-task-version", `expected version 2, 3, or 4, got ${String(data.version)}`);
|
|
54365
54327
|
}
|
|
54366
54328
|
function generationFor(files) {
|
|
54367
|
-
const digest =
|
|
54329
|
+
const digest = crypto3.createHash("sha256");
|
|
54368
54330
|
digest.update("akm-task-to-v4-plan-v1\x00");
|
|
54369
54331
|
for (const file of files) {
|
|
54370
54332
|
digest.update(file.filePath);
|
|
@@ -54401,7 +54363,7 @@ function taskToV4PlanFromOutcomes(outcomes) {
|
|
|
54401
54363
|
for (let index = 1;index < files.length; index += 1) {
|
|
54402
54364
|
const previous = files[index - 1];
|
|
54403
54365
|
const current = files[index];
|
|
54404
|
-
if (previous && current &&
|
|
54366
|
+
if (previous && current && path46.resolve(previous.filePath) === path46.resolve(current.filePath)) {
|
|
54405
54367
|
throw new Error(`duplicate task migration file path: ${current.filePath}`);
|
|
54406
54368
|
}
|
|
54407
54369
|
}
|
|
@@ -54413,8 +54375,8 @@ function migrationError(detail) {
|
|
|
54413
54375
|
return new ConfigError(`Task migration failed: ${detail}`, "INVALID_CONFIG_FILE");
|
|
54414
54376
|
}
|
|
54415
54377
|
function contained2(root, candidate) {
|
|
54416
|
-
const relative =
|
|
54417
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
54378
|
+
const relative = path47.relative(root, candidate);
|
|
54379
|
+
return relative === "" || !relative.startsWith("..") && !path47.isAbsolute(relative);
|
|
54418
54380
|
}
|
|
54419
54381
|
function realDirectory(filePath) {
|
|
54420
54382
|
const stat = fs39.lstatSync(filePath);
|
|
@@ -54442,7 +54404,7 @@ function walkTasks(root, tasksDir, out) {
|
|
|
54442
54404
|
throw migrationError(`${directory} resolves outside bundle ${root.bundleId}.`);
|
|
54443
54405
|
}
|
|
54444
54406
|
for (const entry of fs39.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
54445
|
-
const candidate =
|
|
54407
|
+
const candidate = path47.join(directory, entry.name);
|
|
54446
54408
|
if (entry.isSymbolicLink())
|
|
54447
54409
|
throw migrationError(`task migration does not follow symbolic link ${candidate}.`);
|
|
54448
54410
|
if (entry.isDirectory()) {
|
|
@@ -54468,7 +54430,7 @@ function walkTasks(root, tasksDir, out) {
|
|
|
54468
54430
|
function inspectTaskFiles(roots) {
|
|
54469
54431
|
const files = [];
|
|
54470
54432
|
for (const root of [...roots].sort((a, b) => a.bundleId.localeCompare(b.bundleId))) {
|
|
54471
|
-
const tasksDir = root.layout === "akm-task" ? root.root :
|
|
54433
|
+
const tasksDir = root.layout === "akm-task" ? root.root : path47.join(root.root, "tasks");
|
|
54472
54434
|
try {
|
|
54473
54435
|
const stat = fs39.lstatSync(tasksDir);
|
|
54474
54436
|
if (stat.isSymbolicLink())
|
|
@@ -54484,23 +54446,12 @@ function inspectTaskFiles(roots) {
|
|
|
54484
54446
|
}
|
|
54485
54447
|
return files.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
54486
54448
|
}
|
|
54487
|
-
function planTaskFileToV4(input) {
|
|
54488
|
-
const v3 = planTaskToV3File(input);
|
|
54489
|
-
if (v3.status === "blocked")
|
|
54490
|
-
return v3;
|
|
54491
|
-
if (v3.status === "skipped")
|
|
54492
|
-
return planTaskToV4File(input);
|
|
54493
|
-
const v4 = planTaskToV4File({ ...input, bytes: v3.after });
|
|
54494
|
-
if (v4.status !== "changed")
|
|
54495
|
-
return { ...v4, before: v3.before, beforeHash: v3.beforeHash };
|
|
54496
|
-
return { ...v4, reason: "task-converted", before: v3.before, beforeHash: v3.beforeHash };
|
|
54497
|
-
}
|
|
54498
54449
|
function planTaskFilesMigration(inputs) {
|
|
54499
|
-
return taskToV4PlanFromOutcomes(inputs.map(
|
|
54450
|
+
return taskToV4PlanFromOutcomes(inputs.map(planTaskToV4File));
|
|
54500
54451
|
}
|
|
54501
54452
|
function taskFileBackupPath(backupRoot, filePath) {
|
|
54502
|
-
const digest =
|
|
54503
|
-
return
|
|
54453
|
+
const digest = crypto4.createHash("sha256").update(path47.resolve(filePath)).digest("hex").slice(0, 16);
|
|
54454
|
+
return path47.join(backupRoot, "files", `${digest}-${path47.basename(filePath)}`);
|
|
54504
54455
|
}
|
|
54505
54456
|
function fsyncDirectory(directory) {
|
|
54506
54457
|
if (process.platform === "win32")
|
|
@@ -54519,7 +54470,7 @@ function fsyncDirectory(directory) {
|
|
|
54519
54470
|
}
|
|
54520
54471
|
}
|
|
54521
54472
|
function writeDurable(filePath, bytes, mode, exclusive = false) {
|
|
54522
|
-
fs39.mkdirSync(
|
|
54473
|
+
fs39.mkdirSync(path47.dirname(filePath), { recursive: true });
|
|
54523
54474
|
const fd = fs39.openSync(filePath, exclusive ? "wx" : "w", mode);
|
|
54524
54475
|
try {
|
|
54525
54476
|
fs39.writeFileSync(fd, bytes);
|
|
@@ -54528,14 +54479,14 @@ function writeDurable(filePath, bytes, mode, exclusive = false) {
|
|
|
54528
54479
|
fs39.closeSync(fd);
|
|
54529
54480
|
}
|
|
54530
54481
|
fs39.chmodSync(filePath, mode);
|
|
54531
|
-
fsyncDirectory(
|
|
54482
|
+
fsyncDirectory(path47.dirname(filePath));
|
|
54532
54483
|
}
|
|
54533
54484
|
function replaceAtomically(filePath, bytes, mode) {
|
|
54534
|
-
const temporary =
|
|
54485
|
+
const temporary = path47.join(path47.dirname(filePath), `.${path47.basename(filePath)}.migrate-${crypto4.randomUUID()}`);
|
|
54535
54486
|
try {
|
|
54536
54487
|
writeDurable(temporary, bytes, mode, true);
|
|
54537
54488
|
fs39.renameSync(temporary, filePath);
|
|
54538
|
-
fsyncDirectory(
|
|
54489
|
+
fsyncDirectory(path47.dirname(filePath));
|
|
54539
54490
|
} finally {
|
|
54540
54491
|
fs39.rmSync(temporary, { force: true });
|
|
54541
54492
|
}
|
|
@@ -54572,7 +54523,7 @@ function expandTilde(value) {
|
|
|
54572
54523
|
if (value === "~")
|
|
54573
54524
|
return os7.homedir();
|
|
54574
54525
|
if (value.startsWith("~/") || value.startsWith("~\\"))
|
|
54575
|
-
return
|
|
54526
|
+
return path48.join(os7.homedir(), value.slice(2));
|
|
54576
54527
|
return value;
|
|
54577
54528
|
}
|
|
54578
54529
|
function existingDirectory(target) {
|
|
@@ -54598,14 +54549,14 @@ function taskRoots(config, resolutionBase = process.cwd()) {
|
|
|
54598
54549
|
const source = sources.get(bundleId);
|
|
54599
54550
|
if (!source)
|
|
54600
54551
|
continue;
|
|
54601
|
-
const configuredRoot = source.type === "filesystem" && source.path ?
|
|
54552
|
+
const configuredRoot = source.type === "filesystem" && source.path ? path48.resolve(resolutionBase, expandTilde(source.path)) : lockContentRootFor(bundleId, source.type);
|
|
54602
54553
|
if (!configuredRoot || !existingDirectory(configuredRoot))
|
|
54603
54554
|
continue;
|
|
54604
|
-
const bundleRoot =
|
|
54555
|
+
const bundleRoot = path48.resolve(configuredRoot);
|
|
54605
54556
|
const component = bundleComponentConfig(bundle);
|
|
54606
|
-
const componentRoot =
|
|
54607
|
-
const relative =
|
|
54608
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
54557
|
+
const componentRoot = path48.resolve(bundleRoot, component?.root ?? ".");
|
|
54558
|
+
const relative = path48.relative(bundleRoot, componentRoot);
|
|
54559
|
+
if (relative === ".." || relative.startsWith(`..${path48.sep}`) || path48.isAbsolute(relative)) {
|
|
54609
54560
|
throw new ConfigError(`Task migration component root ${componentRoot} escapes bundle ${bundleId} at ${bundleRoot}.`, "INVALID_CONFIG_FILE");
|
|
54610
54561
|
}
|
|
54611
54562
|
if (!existingDirectory(componentRoot))
|
|
@@ -54622,7 +54573,7 @@ function taskRoots(config, resolutionBase = process.cwd()) {
|
|
|
54622
54573
|
}
|
|
54623
54574
|
const envStash = process.env.AKM_BUNDLE_DIR?.trim();
|
|
54624
54575
|
if (envStash && existingDirectory(envStash)) {
|
|
54625
|
-
const stashRoot =
|
|
54576
|
+
const stashRoot = path48.resolve(envStash);
|
|
54626
54577
|
if (!rootsByPath.has(stashRoot)) {
|
|
54627
54578
|
const bundleId = deriveBundleId(undefined, stashRoot, new Set(Object.keys(config.bundles ?? {})));
|
|
54628
54579
|
const candidate = taskRoot(bundleId, stashRoot, stashRoot, detectAdapterId(stashRoot), () => true);
|
|
@@ -54691,7 +54642,7 @@ function applyTaskFilesMigration() {
|
|
|
54691
54642
|
const before = inspectCurrent();
|
|
54692
54643
|
if (before.result.taskFiles.changed === 0)
|
|
54693
54644
|
return before.result;
|
|
54694
|
-
const backupPath =
|
|
54645
|
+
const backupPath = path48.join(getDataDir(), "backups", "tasks", `${Date.now()}-${randomUUID3()}`);
|
|
54695
54646
|
const applied = applyTaskFilesPlan(before.plan, { backupRoot: backupPath });
|
|
54696
54647
|
return { ...inspectCurrent().result, backupPath, applied: applied.changed.length };
|
|
54697
54648
|
});
|