akm-cli 0.9.4 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10067,11 +10067,13 @@ function makeStampHandler(command) {
10067
10067
  if (typeof result !== "object" || Array.isArray(result))
10068
10068
  return result;
10069
10069
  const obj = result;
10070
- if (obj.shape === undefined)
10071
- obj.shape = command;
10072
- if (obj.schemaVersion === undefined)
10073
- obj.schemaVersion = 1;
10074
- return obj;
10070
+ if (obj.shape !== undefined && obj.schemaVersion !== undefined)
10071
+ return obj;
10072
+ return {
10073
+ ...obj,
10074
+ shape: obj.shape ?? command,
10075
+ schemaVersion: obj.schemaVersion ?? 1
10076
+ };
10075
10077
  };
10076
10078
  }
10077
10079
  var PASSTHROUGH_COMMANDS = [
@@ -10111,6 +10113,7 @@ var PASSTHROUGH_COMMANDS = [
10111
10113
  "task-doctor",
10112
10114
  "task-explain",
10113
10115
  "task-history",
10116
+ "task-prune",
10114
10117
  "task-run",
10115
10118
  "task-sync",
10116
10119
  "task-sync-dry-run",
@@ -13024,8 +13027,17 @@ import path8 from "node:path";
13024
13027
  // src/core/warn.ts
13025
13028
  import fs4 from "node:fs";
13026
13029
  var quiet = false;
13030
+ var verbose = false;
13027
13031
  var logFilePath;
13028
13032
  var sinkOverride;
13033
+ function isVerbose() {
13034
+ const env = process.env.AKM_VERBOSE?.trim().toLowerCase();
13035
+ if (env === "1" || env === "true" || env === "yes" || env === "on")
13036
+ return true;
13037
+ if (env === "0" || env === "false" || env === "no" || env === "off")
13038
+ return false;
13039
+ return verbose;
13040
+ }
13029
13041
  function appendToLogFile(level, args) {
13030
13042
  if (!logFilePath)
13031
13043
  return;
@@ -13051,6 +13063,15 @@ function warn(...args) {
13051
13063
  console.warn(...args);
13052
13064
  }
13053
13065
  }
13066
+ function warnVerbose(...args) {
13067
+ if (sinkOverride) {
13068
+ sinkOverride("warnVerbose", args);
13069
+ return;
13070
+ }
13071
+ if (isVerbose()) {
13072
+ warn(...args);
13073
+ }
13074
+ }
13054
13075
 
13055
13076
  // src/indexer/passes/metadata.ts
13056
13077
  var SCOPE_KEYS = ["user", "agent", "run", "channel"];
@@ -13766,7 +13787,7 @@ function stripMarkdownLinkDestinations(text, nesting = 0) {
13766
13787
  }
13767
13788
  return visible;
13768
13789
  }
13769
- function projectMarkdownContent(body) {
13790
+ function projectMarkdownContent(body, truncationInfo) {
13770
13791
  const lines = body.split(/\r?\n/);
13771
13792
  const innerBlock = findInnerFrontmatterBlock(lines);
13772
13793
  const start = innerBlock && isFrontmatterShaped(lines, innerBlock) ? innerBlock.close + 1 : 0;
@@ -13812,6 +13833,8 @@ function projectMarkdownContent(body) {
13812
13833
  const text = projected.join(" ").replace(/\s+/g, " ").trim();
13813
13834
  if (!text)
13814
13835
  return;
13836
+ if (truncationInfo)
13837
+ truncationInfo.truncated = text.length > MARKDOWN_CONTENT_MAX_CHARS;
13815
13838
  return truncateUnicodeSafe(text, MARKDOWN_CONTENT_MAX_CHARS);
13816
13839
  }
13817
13840
  function applyPreContributorFields(entry, file, ctx, pkgMeta) {
@@ -13835,9 +13858,15 @@ function applyPreContributorFields(entry, file, ctx, pkgMeta) {
13835
13858
  applyWikiFrontmatter(entry, parsed.data);
13836
13859
  applyProvenanceFrontmatter(entry, parsed.data);
13837
13860
  if (entry.type !== "env" && entry.type !== "session" && !hasSessionMemoryMarker(parsed.data, parsed.content)) {
13838
- const contentProjection = projectMarkdownContent(parsed.content);
13839
- if (contentProjection)
13861
+ const truncationInfo = { truncated: false };
13862
+ const contentProjection = projectMarkdownContent(parsed.content, truncationInfo);
13863
+ if (contentProjection) {
13840
13864
  entry.content = contentProjection;
13865
+ if (truncationInfo.truncated) {
13866
+ entry.contentTruncated = true;
13867
+ warnVerbose(`${file}: indexed content truncated to ${MARKDOWN_CONTENT_MAX_CHARS} chars`);
13868
+ }
13869
+ }
13841
13870
  }
13842
13871
  if (entry.type === "command") {
13843
13872
  const cmdParams = extractCommandParameters(parsed.content);
@@ -16800,6 +16829,16 @@ var SHELL_ASSIGNMENT_WORD = /^[A-Za-z_][A-Za-z0-9_]*=/;
16800
16829
  function shellStableV2Executable(executable) {
16801
16830
  return executable === "akm" || executable.includes("/");
16802
16831
  }
16832
+ function skipEnvAssignmentPrefix(tokens) {
16833
+ if (tokens[0] !== "env")
16834
+ return { tokens, envWrapped: false };
16835
+ let index = 1;
16836
+ while (index < tokens.length && SHELL_ASSIGNMENT_WORD.test(tokens[index]))
16837
+ index += 1;
16838
+ if (index >= tokens.length)
16839
+ return { tokens, envWrapped: false };
16840
+ return { tokens: tokens.slice(index), envWrapped: true };
16841
+ }
16803
16842
  var KNOWN_PROMPT_REF_FAMILIES = new Set([
16804
16843
  "agents",
16805
16844
  "commands",
@@ -16820,11 +16859,6 @@ function hash(bytes) {
16820
16859
  return crypto.createHash("sha256").update(bytes).digest("hex");
16821
16860
  }
16822
16861
  function base(input) {
16823
- const inspectionIdentity = input.inspectionIdentity ? Object.freeze({
16824
- file: Object.freeze({ ...input.inspectionIdentity.file }),
16825
- root: Object.freeze({ ...input.inspectionIdentity.root }),
16826
- ...input.inspectionIdentity.bundleRoot ? { bundleRoot: Object.freeze({ ...input.inspectionIdentity.bundleRoot }) } : {}
16827
- }) : undefined;
16828
16862
  return {
16829
16863
  filePath: input.filePath,
16830
16864
  before: Buffer.from(input.bytes),
@@ -16832,8 +16866,7 @@ function base(input) {
16832
16866
  mode: input.mode,
16833
16867
  writable: input.writable,
16834
16868
  ...input.onDiskWritable !== undefined ? { onDiskWritable: input.onDiskWritable } : {},
16835
- ...input.containmentRoot ? { containmentRoot: input.containmentRoot } : {},
16836
- ...inspectionIdentity ? { inspectionIdentity } : {}
16869
+ ...input.containmentRoot ? { containmentRoot: input.containmentRoot } : {}
16837
16870
  };
16838
16871
  }
16839
16872
  function blocked(input, reason, detail) {
@@ -17069,8 +17102,9 @@ function migratedObject(data) {
17069
17102
  if (tokens.length === 0 || tokens.some((token) => !SAFE_V2_COMMAND_TOKEN.test(token))) {
17070
17103
  return "shell-operators-change-v2-literal-argv-semantics";
17071
17104
  }
17072
- const executable = tokens[0];
17073
- if (SHELL_ASSIGNMENT_WORD.test(executable) || !shellStableV2Executable(executable)) {
17105
+ const { tokens: targetTokens, envWrapped } = skipEnvAssignmentPrefix(tokens);
17106
+ const executable = targetTokens[0];
17107
+ if (SHELL_ASSIGNMENT_WORD.test(executable) || !envWrapped && !shellStableV2Executable(executable)) {
17074
17108
  return "shell-command-resolution-changes-v2-literal-argv-semantics";
17075
17109
  }
17076
17110
  output.run = tokens.join(" ");
@@ -17264,11 +17298,6 @@ function causeMessage(cause) {
17264
17298
  return cause instanceof Error ? cause.message : String(cause);
17265
17299
  }
17266
17300
  function base2(input) {
17267
- const inspectionIdentity = input.inspectionIdentity ? Object.freeze({
17268
- file: Object.freeze({ ...input.inspectionIdentity.file }),
17269
- root: Object.freeze({ ...input.inspectionIdentity.root }),
17270
- ...input.inspectionIdentity.bundleRoot ? { bundleRoot: Object.freeze({ ...input.inspectionIdentity.bundleRoot }) } : {}
17271
- }) : undefined;
17272
17301
  return {
17273
17302
  filePath: input.filePath,
17274
17303
  before: Buffer.from(input.bytes),
@@ -17276,8 +17305,7 @@ function base2(input) {
17276
17305
  mode: input.mode,
17277
17306
  writable: input.writable,
17278
17307
  ...input.onDiskWritable !== undefined ? { onDiskWritable: input.onDiskWritable } : {},
17279
- ...input.containmentRoot ? { containmentRoot: input.containmentRoot } : {},
17280
- ...inspectionIdentity ? { inspectionIdentity } : {}
17308
+ ...input.containmentRoot ? { containmentRoot: input.containmentRoot } : {}
17281
17309
  };
17282
17310
  }
17283
17311
  function blocked2(input, reason, detail) {
@@ -21842,6 +21870,8 @@ function indexDocumentFromEntry(entry, base3, rendererName) {
21842
21870
  doc.tags = entry.tags;
21843
21871
  if (entry.content !== undefined)
21844
21872
  doc.content = entry.content;
21873
+ if (entry.contentTruncated !== undefined)
21874
+ doc.contentTruncated = entry.contentTruncated;
21845
21875
  if (entry.aliases !== undefined)
21846
21876
  doc.aliases = entry.aliases;
21847
21877
  if (entry.searchHints !== undefined)
@@ -28892,6 +28922,43 @@ var AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) => {
28892
28922
  }
28893
28923
  });
28894
28924
 
28925
+ // src/core/config/config-version-shim.ts
28926
+ var KNOWN_OLD_CONFIG_VERSIONS = ["0.0.1"];
28927
+ function isKnownOldConfigVersion(value) {
28928
+ return typeof value === "string" && KNOWN_OLD_CONFIG_VERSIONS.includes(value);
28929
+ }
28930
+ function upgradeFrom080(raw) {
28931
+ const { defaultEngine, defaults, ...rest } = raw;
28932
+ if (typeof defaultEngine !== "string" || defaultEngine.length === 0) {
28933
+ return { ...rest, ...defaults !== undefined ? { defaults } : {}, configVersion: CURRENT_CONFIG_VERSION };
28934
+ }
28935
+ const existingDefaults = defaults !== null && typeof defaults === "object" ? defaults : {};
28936
+ return {
28937
+ ...rest,
28938
+ configVersion: CURRENT_CONFIG_VERSION,
28939
+ defaults: { llmEngine: defaultEngine, ...existingDefaults }
28940
+ };
28941
+ }
28942
+ var CONFIG_VERSION_UPGRADES = {
28943
+ "0.0.1": upgradeFrom080
28944
+ };
28945
+ function unsupportedConfigVersionError(rawVersion, sourcePath) {
28946
+ const where = sourcePath ? ` at ${sourcePath}` : "";
28947
+ const supported = [CURRENT_CONFIG_VERSION, ...KNOWN_OLD_CONFIG_VERSIONS].map((v) => `"${v}"`).join(", ");
28948
+ return new ConfigError(`Unsupported configVersion${where}: got ${JSON.stringify(rawVersion)}, expected one of ${supported}.`, "UNSUPPORTED_CONFIG_VERSION", "Recreate engines and improve.strategies manually for AKM 0.9.0; profile-based configuration is not translated automatically.");
28949
+ }
28950
+ function upgradeConfigVersion(raw, sourcePath) {
28951
+ const version = raw.configVersion;
28952
+ if (version === CURRENT_CONFIG_VERSION)
28953
+ return raw;
28954
+ if (isKnownOldConfigVersion(version)) {
28955
+ const upgraded = CONFIG_VERSION_UPGRADES[version](raw);
28956
+ warn(`Config${sourcePath ? ` at ${sourcePath}` : ""} uses configVersion "${version}" — auto-upgraded to ${CURRENT_CONFIG_VERSION} in memory; the next config write (e.g. \`akm config set\`) persists this and silences the warning.`);
28957
+ return upgraded;
28958
+ }
28959
+ throw unsupportedConfigVersionError(version, sourcePath);
28960
+ }
28961
+
28895
28962
  // src/core/config/deep-merge.ts
28896
28963
  var UNSAFE_KEYS3 = new Set(["__proto__", "constructor", "prototype"]);
28897
28964
  function isPlainObject4(value) {
@@ -28982,10 +29049,7 @@ function loadUserConfig() {
28982
29049
  return finalConfig;
28983
29050
  }
28984
29051
  function parseAndValidateConfigText(text, sourcePath) {
28985
- const parsedRaw = parseConfigText(text, sourcePath);
28986
- if (parsedRaw.configVersion !== CURRENT_CONFIG_VERSION) {
28987
- throw new ConfigError(`Unsupported configVersion${sourcePath ? ` at ${sourcePath}` : ""}: expected "${CURRENT_CONFIG_VERSION}".`, "UNSUPPORTED_CONFIG_VERSION", "Recreate engines and improve.strategies manually for AKM 0.9.0; profile-based configuration is not translated automatically.");
28988
- }
29052
+ const parsedRaw = upgradeConfigVersion(parseConfigText(text, sourcePath), sourcePath);
28989
29053
  const { config: raw, lifted, conflicts } = liftLegacyEngineExtraParams(parsedRaw);
28990
29054
  const where = sourcePath ? ` at ${sourcePath}` : "";
28991
29055
  if (conflicts.length > 0) {
@@ -29538,32 +29602,18 @@ function contained2(root, candidate) {
29538
29602
  const relative = path31.relative(root, candidate);
29539
29603
  return relative === "" || !relative.startsWith("..") && !path31.isAbsolute(relative);
29540
29604
  }
29541
- function identity3(filePath, kind) {
29542
- const stat = fs26.lstatSync(filePath, { bigint: true });
29543
- const valid = kind === "file" ? stat.isFile() : stat.isDirectory();
29544
- if (stat.isSymbolicLink() || !valid)
29545
- throw migrationError(`${filePath} must be a real ${kind}.`);
29546
- if (kind === "file" && stat.nlink !== 1n)
29547
- throw migrationError(`${filePath} must not be hard-linked.`);
29548
- return Object.freeze({
29549
- realPath: fs26.realpathSync(filePath),
29550
- device: stat.dev.toString(),
29551
- inode: stat.ino.toString(),
29552
- linkCount: stat.nlink.toString(),
29553
- changeTimeNs: stat.ctimeNs.toString()
29554
- });
29555
- }
29556
- function sameIdentity2(left, right) {
29557
- return left.realPath === right.realPath && left.device === right.device && left.inode === right.inode && left.linkCount === right.linkCount && left.changeTimeNs === right.changeTimeNs;
29605
+ function realDirectory(filePath) {
29606
+ const stat = fs26.lstatSync(filePath);
29607
+ if (stat.isSymbolicLink() || !stat.isDirectory())
29608
+ throw migrationError(`${filePath} must be a real directory.`);
29609
+ return fs26.realpathSync(filePath);
29558
29610
  }
29559
29611
  function snapshot(filePath) {
29560
- const before = identity3(filePath, "file");
29561
29612
  const stat = fs26.lstatSync(filePath);
29613
+ if (stat.isSymbolicLink() || !stat.isFile())
29614
+ throw migrationError(`${filePath} must be a real file.`);
29562
29615
  const bytes = fs26.readFileSync(filePath);
29563
- const after = identity3(filePath, "file");
29564
- if (!sameIdentity2(before, after))
29565
- throw migrationError(`${filePath} changed while it was read.`);
29566
- return Object.freeze({ bytes, mode: stat.mode & 511, identity: after });
29616
+ return Object.freeze({ bytes, mode: stat.mode & 511 });
29567
29617
  }
29568
29618
  function writable(filePath) {
29569
29619
  try {
@@ -29574,10 +29624,9 @@ function writable(filePath) {
29574
29624
  }
29575
29625
  }
29576
29626
  function walkTasks(root, tasksDir, out) {
29577
- const rootIdentity = identity3(root.root, "directory");
29578
- const bundleIdentity = identity3(root.bundleRoot ?? root.root, "directory");
29579
- const physicalRoot = rootIdentity.realPath;
29580
- if (!contained2(bundleIdentity.realPath, physicalRoot)) {
29627
+ const physicalRoot = realDirectory(root.root);
29628
+ const physicalBundleRoot = realDirectory(root.bundleRoot ?? root.root);
29629
+ if (!contained2(physicalBundleRoot, physicalRoot)) {
29581
29630
  throw migrationError(`${root.root} resolves outside bundle ${root.bundleId}.`);
29582
29631
  }
29583
29632
  const visit2 = (directory) => {
@@ -29603,12 +29652,7 @@ function walkTasks(root, tasksDir, out) {
29603
29652
  mode: current.mode,
29604
29653
  writable: root.writable,
29605
29654
  onDiskWritable: writable(candidate) && writable(parent) && (current.mode & 146) !== 0,
29606
- containmentRoot: physicalRoot,
29607
- inspectionIdentity: Object.freeze({
29608
- file: current.identity,
29609
- root: rootIdentity,
29610
- bundleRoot: bundleIdentity
29611
- })
29655
+ containmentRoot: physicalRoot
29612
29656
  });
29613
29657
  }
29614
29658
  };
@@ -29672,17 +29716,9 @@ function assertUnchanged(change) {
29672
29716
  if (!current.bytes.equals(change.before) || current.mode !== change.mode) {
29673
29717
  throw migrationError(`${change.filePath} changed after preview; no task files were replaced.`);
29674
29718
  }
29675
- const planned = change.inspectionIdentity?.file;
29676
- if (planned && !sameIdentity2(planned, current.identity)) {
29677
- throw migrationError(`${change.filePath} identity changed after preview; no task files were replaced.`);
29678
- }
29679
29719
  return current;
29680
29720
  }
29681
29721
  function applyTaskToV3MigrationPlan(plan, options) {
29682
- const blocked3 = plan.files.filter((file) => file.status === "blocked");
29683
- if (blocked3.length > 0) {
29684
- throw migrationError(`plan is blocked: ${blocked3.map((file) => `${file.filePath} (${file.reason})`).join(", ")}. No files were written.`);
29685
- }
29686
29722
  const changes = plan.files.filter((file) => file.status === "changed");
29687
29723
  for (const change of changes) {
29688
29724
  assertUnchanged(change);
@@ -29725,32 +29761,18 @@ function contained3(root, candidate) {
29725
29761
  const relative = path32.relative(root, candidate);
29726
29762
  return relative === "" || !relative.startsWith("..") && !path32.isAbsolute(relative);
29727
29763
  }
29728
- function identity4(filePath, kind) {
29729
- const stat = fs27.lstatSync(filePath, { bigint: true });
29730
- const valid = kind === "file" ? stat.isFile() : stat.isDirectory();
29731
- if (stat.isSymbolicLink() || !valid)
29732
- throw migrationError2(`${filePath} must be a real ${kind}.`);
29733
- if (kind === "file" && stat.nlink !== 1n)
29734
- throw migrationError2(`${filePath} must not be hard-linked.`);
29735
- return Object.freeze({
29736
- realPath: fs27.realpathSync(filePath),
29737
- device: stat.dev.toString(),
29738
- inode: stat.ino.toString(),
29739
- linkCount: stat.nlink.toString(),
29740
- changeTimeNs: stat.ctimeNs.toString()
29741
- });
29742
- }
29743
- function sameIdentity3(left, right) {
29744
- return left.realPath === right.realPath && left.device === right.device && left.inode === right.inode && left.linkCount === right.linkCount && left.changeTimeNs === right.changeTimeNs;
29764
+ function realDirectory2(filePath) {
29765
+ const stat = fs27.lstatSync(filePath);
29766
+ if (stat.isSymbolicLink() || !stat.isDirectory())
29767
+ throw migrationError2(`${filePath} must be a real directory.`);
29768
+ return fs27.realpathSync(filePath);
29745
29769
  }
29746
29770
  function snapshot2(filePath) {
29747
- const before = identity4(filePath, "file");
29748
29771
  const stat = fs27.lstatSync(filePath);
29772
+ if (stat.isSymbolicLink() || !stat.isFile())
29773
+ throw migrationError2(`${filePath} must be a real file.`);
29749
29774
  const bytes = fs27.readFileSync(filePath);
29750
- const after = identity4(filePath, "file");
29751
- if (!sameIdentity3(before, after))
29752
- throw migrationError2(`${filePath} changed while it was read.`);
29753
- return Object.freeze({ bytes, mode: stat.mode & 511, identity: after });
29775
+ return Object.freeze({ bytes, mode: stat.mode & 511 });
29754
29776
  }
29755
29777
  function writable2(filePath) {
29756
29778
  try {
@@ -29761,10 +29783,9 @@ function writable2(filePath) {
29761
29783
  }
29762
29784
  }
29763
29785
  function walkTasks2(root, tasksDir, out) {
29764
- const rootIdentity = identity4(root.root, "directory");
29765
- const bundleIdentity = identity4(root.bundleRoot ?? root.root, "directory");
29766
- const physicalRoot = rootIdentity.realPath;
29767
- if (!contained3(bundleIdentity.realPath, physicalRoot)) {
29786
+ const physicalRoot = realDirectory2(root.root);
29787
+ const physicalBundleRoot = realDirectory2(root.bundleRoot ?? root.root);
29788
+ if (!contained3(physicalBundleRoot, physicalRoot)) {
29768
29789
  throw migrationError2(`${root.root} resolves outside bundle ${root.bundleId}.`);
29769
29790
  }
29770
29791
  const visit2 = (directory) => {
@@ -29790,12 +29811,7 @@ function walkTasks2(root, tasksDir, out) {
29790
29811
  mode: current.mode,
29791
29812
  writable: root.writable,
29792
29813
  onDiskWritable: writable2(candidate) && writable2(parent) && (current.mode & 146) !== 0,
29793
- containmentRoot: physicalRoot,
29794
- inspectionIdentity: Object.freeze({
29795
- file: current.identity,
29796
- root: rootIdentity,
29797
- bundleRoot: bundleIdentity
29798
- })
29814
+ containmentRoot: physicalRoot
29799
29815
  });
29800
29816
  }
29801
29817
  };
@@ -29859,17 +29875,9 @@ function assertUnchanged2(change) {
29859
29875
  if (!current.bytes.equals(change.before) || current.mode !== change.mode) {
29860
29876
  throw migrationError2(`${change.filePath} changed after preview; no task files were replaced.`);
29861
29877
  }
29862
- const planned = change.inspectionIdentity?.file;
29863
- if (planned && !sameIdentity3(planned, current.identity)) {
29864
- throw migrationError2(`${change.filePath} identity changed after preview; no task files were replaced.`);
29865
- }
29866
29878
  return current;
29867
29879
  }
29868
29880
  function applyTaskToV4MigrationPlan(plan, options) {
29869
- const blocked3 = plan.files.filter((file) => file.status === "blocked");
29870
- if (blocked3.length > 0) {
29871
- throw migrationError2(`plan is blocked: ${blocked3.map((file) => `${file.filePath} (${file.reason})`).join(", ")}. No files were written.`);
29872
- }
29873
29881
  const changes = plan.files.filter((file) => file.status === "changed");
29874
29882
  for (const change of changes) {
29875
29883
  assertUnchanged2(change);
@@ -30013,12 +30021,12 @@ async function runMigrationApply(options = {}) {
30013
30021
  }
30014
30022
  const result = withConfigLock(() => withMaintenanceStartBarrier(() => {
30015
30023
  const before = inspectCurrentTaskPlan();
30016
- if (before.result.status !== "ready")
30024
+ if (before.result.taskV3Migration.changed === 0)
30017
30025
  return before.result;
30018
30026
  const backupPath = path33.join(getDataDir(), "backups", "task-v3", `${Date.now()}-${randomUUID2()}`);
30019
30027
  const applied = applyTaskToV3MigrationPlan(before.plan, { backupRoot: backupPath });
30020
30028
  const after = inspectCurrentTaskPlan().result;
30021
- if (after.status !== "current") {
30029
+ if (after.taskV3Migration.changed > 0) {
30022
30030
  throw new ConfigError("Task migration did not converge to task v3.", "INVALID_CONFIG_FILE");
30023
30031
  }
30024
30032
  return { ...after, backupPath, applied: applied.changed.length };
@@ -30076,12 +30084,12 @@ async function runTaskV4MigrationApply(options = {}) {
30076
30084
  }
30077
30085
  const result = withConfigLock(() => withMaintenanceStartBarrier(() => {
30078
30086
  const before = inspectCurrentTaskV4Plan();
30079
- if (before.result.status !== "ready")
30087
+ if (before.result.taskV4Migration.changed === 0)
30080
30088
  return before.result;
30081
30089
  const backupPath = path33.join(getDataDir(), "backups", "task-v4", `${Date.now()}-${randomUUID2()}`);
30082
30090
  const applied = applyTaskToV4MigrationPlan(before.plan, { backupRoot: backupPath });
30083
30091
  const after = inspectCurrentTaskV4Plan().result;
30084
- if (after.status !== "current") {
30092
+ if (after.taskV4Migration.changed > 0) {
30085
30093
  throw new ConfigError("Task migration did not converge to task source v4.", "INVALID_CONFIG_FILE");
30086
30094
  }
30087
30095
  return { ...after, backupPath, applied: applied.changed.length };