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.
@@ -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 path49 from "node:path";
53299
+ import path48 from "node:path";
53253
53300
 
53254
53301
  // scripts/akm-migrate/migrate/task-files.ts
53255
- import crypto5 from "node:crypto";
53302
+ import crypto4 from "node:crypto";
53256
53303
  import fs39 from "node:fs";
53257
- import path48 from "node:path";
53304
+ import path47 from "node:path";
53258
53305
 
53259
- // src/tasks/source/task-to-v3.ts
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-v3.ts
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 parseLegacyTaskYaml(input) {
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: ${cause instanceof Error ? cause.message : String(cause)}`);
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 v2 migration source",
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 planLegacyTaskDataToV3(input, data) {
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", cause instanceof Error ? cause.message : String(cause));
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", cause instanceof Error ? cause.message : String(cause));
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 after = Buffer.from($stringify(migrated), "utf8");
54235
+ const v3Yaml = $stringify(migrated);
53973
54236
  try {
53974
54237
  parseTaskV3Yaml({
53975
- yaml: after.toString("utf8"),
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", cause instanceof Error ? cause.message : String(cause));
54243
+ return blocked(input, "generated-v3-validation-failed", causeMessage(cause));
53981
54244
  }
53982
- return Object.freeze({
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, source } = parseLegacyTaskYaml(input));
54247
+ ({ data: v3Data } = parseRawTaskYaml({ ...input, bytes: Buffer.from(v3Yaml, "utf8") }));
53995
54248
  } catch (cause) {
53996
- return blocked(input, "invalid-task-yaml", cause instanceof Error ? cause.message : String(cause));
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 (data.version === 3) {
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
- parseTaskV3Yaml({
54001
- yaml: source,
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 blocked2(input, "invalid-v3-task", causeMessage(cause));
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: afterYaml,
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 blocked2(input, "generated-v4-validation-failed", causeMessage(cause));
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 } = parseV3RawYaml(input));
54304
+ ({ data, source } = parseRawTaskYaml(input));
54305
54305
  } catch (cause) {
54306
- return blocked2(input, "invalid-task-yaml", causeMessage(cause));
54306
+ return blocked(input, "invalid-task-yaml", causeMessage(cause));
54307
54307
  }
54308
54308
  if (data.version === 4) {
54309
- const document = $parseDocument(source, { uniqueKeys: true });
54310
- const schedule = document.get("schedule", true);
54311
- let removed = false;
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
- parseTaskSourceV4({
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 blocked2(input, "invalid-v4-task", causeMessage(cause));
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 Object.freeze({
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 planV3DataToV4(input, data);
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 = crypto4.createHash("sha256");
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 && path47.resolve(previous.filePath) === path47.resolve(current.filePath)) {
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 = path48.relative(root, candidate);
54417
- return relative === "" || !relative.startsWith("..") && !path48.isAbsolute(relative);
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 = path48.join(directory, entry.name);
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 : path48.join(root.root, "tasks");
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(planTaskFileToV4));
54450
+ return taskToV4PlanFromOutcomes(inputs.map(planTaskToV4File));
54500
54451
  }
54501
54452
  function taskFileBackupPath(backupRoot, filePath) {
54502
- const digest = crypto5.createHash("sha256").update(path48.resolve(filePath)).digest("hex").slice(0, 16);
54503
- return path48.join(backupRoot, "files", `${digest}-${path48.basename(filePath)}`);
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(path48.dirname(filePath), { recursive: true });
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(path48.dirname(filePath));
54482
+ fsyncDirectory(path47.dirname(filePath));
54532
54483
  }
54533
54484
  function replaceAtomically(filePath, bytes, mode) {
54534
- const temporary = path48.join(path48.dirname(filePath), `.${path48.basename(filePath)}.migrate-${crypto5.randomUUID()}`);
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(path48.dirname(filePath));
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 path49.join(os7.homedir(), value.slice(2));
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 ? path49.resolve(resolutionBase, expandTilde(source.path)) : lockContentRootFor(bundleId, source.type);
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 = path49.resolve(configuredRoot);
54555
+ const bundleRoot = path48.resolve(configuredRoot);
54605
54556
  const component = bundleComponentConfig(bundle);
54606
- const componentRoot = path49.resolve(bundleRoot, component?.root ?? ".");
54607
- const relative = path49.relative(bundleRoot, componentRoot);
54608
- if (relative === ".." || relative.startsWith(`..${path49.sep}`) || path49.isAbsolute(relative)) {
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 = path49.resolve(envStash);
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 = path49.join(getDataDir(), "backups", "tasks", `${Date.now()}-${randomUUID3()}`);
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
  });