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.
@@ -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 path49 from "path";
53257
+ import path48 from "path";
53211
53258
 
53212
53259
  // scripts/akm-migrate/migrate/task-files.ts
53213
- import crypto5 from "crypto";
53260
+ import crypto4 from "crypto";
53214
53261
  import fs39 from "fs";
53215
- import path48 from "path";
53262
+ import path47 from "path";
53216
53263
 
53217
- // src/tasks/source/task-to-v3.ts
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-v3.ts
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 parseLegacyTaskYaml(input) {
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: ${cause instanceof Error ? cause.message : String(cause)}`);
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 v2 migration source",
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 planLegacyTaskDataToV3(input, data) {
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", cause instanceof Error ? cause.message : String(cause));
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", cause instanceof Error ? cause.message : String(cause));
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 after = Buffer.from($stringify(migrated), "utf8");
54193
+ const v3Yaml = $stringify(migrated);
53931
54194
  try {
53932
54195
  parseTaskV3Yaml({
53933
- yaml: after.toString("utf8"),
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", cause instanceof Error ? cause.message : String(cause));
54201
+ return blocked(input, "generated-v3-validation-failed", causeMessage(cause));
53939
54202
  }
53940
- return Object.freeze({
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, source } = parseLegacyTaskYaml(input));
54205
+ ({ data: v3Data } = parseRawTaskYaml({ ...input, bytes: Buffer.from(v3Yaml, "utf8") }));
53953
54206
  } catch (cause) {
53954
- return blocked(input, "invalid-task-yaml", cause instanceof Error ? cause.message : String(cause));
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 (data.version === 3) {
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
- parseTaskV3Yaml({
53959
- yaml: source,
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 blocked2(input, "invalid-v3-task", causeMessage(cause));
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: afterYaml,
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 blocked2(input, "generated-v4-validation-failed", causeMessage(cause));
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 } = parseV3RawYaml(input));
54262
+ ({ data, source } = parseRawTaskYaml(input));
54263
54263
  } catch (cause) {
54264
- return blocked2(input, "invalid-task-yaml", causeMessage(cause));
54264
+ return blocked(input, "invalid-task-yaml", causeMessage(cause));
54265
54265
  }
54266
54266
  if (data.version === 4) {
54267
- const document = $parseDocument(source, { uniqueKeys: true });
54268
- const schedule = document.get("schedule", true);
54269
- let removed = false;
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
- parseTaskSourceV4({
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 blocked2(input, "invalid-v4-task", causeMessage(cause));
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 Object.freeze({
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 planV3DataToV4(input, data);
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 = crypto4.createHash("sha256");
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 && path47.resolve(previous.filePath) === path47.resolve(current.filePath)) {
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 = path48.relative(root2, candidate);
54375
- return relative === "" || !relative.startsWith("..") && !path48.isAbsolute(relative);
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 = path48.join(directory, entry.name);
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 : path48.join(root2.root, "tasks");
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(planTaskFileToV4));
54408
+ return taskToV4PlanFromOutcomes(inputs.map(planTaskToV4File));
54458
54409
  }
54459
54410
  function taskFileBackupPath(backupRoot, filePath) {
54460
- const digest = crypto5.createHash("sha256").update(path48.resolve(filePath)).digest("hex").slice(0, 16);
54461
- return path48.join(backupRoot, "files", `${digest}-${path48.basename(filePath)}`);
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(path48.dirname(filePath), { recursive: true });
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(path48.dirname(filePath));
54440
+ fsyncDirectory(path47.dirname(filePath));
54490
54441
  }
54491
54442
  function replaceAtomically(filePath, bytes, mode) {
54492
- const temporary = path48.join(path48.dirname(filePath), `.${path48.basename(filePath)}.migrate-${crypto5.randomUUID()}`);
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(path48.dirname(filePath));
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 path49.join(os7.homedir(), value.slice(2));
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 ? path49.resolve(resolutionBase, expandTilde(source.path)) : lockContentRootFor(bundleId, source.type);
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 = path49.resolve(configuredRoot);
54513
+ const bundleRoot = path48.resolve(configuredRoot);
54563
54514
  const component = bundleComponentConfig(bundle);
54564
- const componentRoot = path49.resolve(bundleRoot, component?.root ?? ".");
54565
- const relative = path49.relative(bundleRoot, componentRoot);
54566
- if (relative === ".." || relative.startsWith(`..${path49.sep}`) || path49.isAbsolute(relative)) {
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 = path49.resolve(envStash);
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 = path49.join(getDataDir(), "backups", "tasks", `${Date.now()}-${randomUUID3()}`);
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
  });