@devrouter/cli 0.0.68 → 0.0.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/devrouter.js CHANGED
@@ -4133,7 +4133,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs7.default.read
4133
4133
  const config = parseConfig(parsed ?? {}, configPath);
4134
4134
  const requiredVersion = config.devrouter?.version;
4135
4135
  if (requiredVersion && !hasWarnedVersionMismatch) {
4136
- const cliVersion = true ? "0.0.68" : "0.0.0-dev";
4136
+ const cliVersion = true ? "0.0.69" : "0.0.0-dev";
4137
4137
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
4138
4138
  hasWarnedVersionMismatch = true;
4139
4139
  process.stderr.write(
@@ -5386,7 +5386,7 @@ var init_reliability_contract = __esm({
5386
5386
 
5387
5387
  // src/core/reliability-operation-store.ts
5388
5388
  function installedCliVersion() {
5389
- return true ? "0.0.68" : "0.0.0-dev";
5389
+ return true ? "0.0.69" : "0.0.0-dev";
5390
5390
  }
5391
5391
  function compareCliVersions(left, right) {
5392
5392
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -10047,12 +10047,108 @@ function containerIdentity(container) {
10047
10047
  mounts: container.mounts.map((mount) => JSON.stringify(mount)).sort()
10048
10048
  });
10049
10049
  }
10050
+ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
10051
+ if (!import_node_fs23.default.existsSync(import_node_path31.default.join(repoPath, ".devrouter.yml"))) return false;
10052
+ const linked = isLinkedWorktree(repoPath);
10053
+ const workspace = linked ? resolveWorktreeWorkspace(repoPath) : void 0;
10054
+ const registration2 = () => {
10055
+ resetWorkspaceRuntimeCaches();
10056
+ if (resolveWorkspaceRuntimeOrDefault(repoPath) !== "devsy")
10057
+ throw new Error("Initial managed stop provider selection changed.");
10058
+ const owner2 = inspectDevsyWorkspaceOwnership(listDevsyWorkspaces(), devsyId, repoPath);
10059
+ if (owner2.status !== "owned")
10060
+ throw new Error("Initial managed stop requires one exact Devsy registration.");
10061
+ if (linked) {
10062
+ const record5 = workspace ? readWorkspaceOwnership(repoPath, workspace) : void 0;
10063
+ if (!record5 || record5.devpodId !== devsyId || !sameWorkspacePath(record5.worktreePath, repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace)
10064
+ throw new Error("Initial managed stop workspace ownership changed.");
10065
+ }
10066
+ return owner2.workspace;
10067
+ };
10068
+ const runtime = loadRuntimeConfig(repoPath, workspace ?? "");
10069
+ if (!runtime.config.managedRuntime) return false;
10070
+ const owner = registration2();
10071
+ if (!owner.context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(owner.context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId))
10072
+ throw new Error("Initial managed stop requires exact provider context.");
10073
+ const plan = inspectManagedDevcontainerConfig({
10074
+ repoPath,
10075
+ config: runtime.config,
10076
+ profile: runtime.resolvedProfile,
10077
+ linked
10078
+ });
10079
+ const providerRoot = import_node_path31.default.resolve(process.env.DEVSY_HOME || import_node_path31.default.join(import_node_os4.default.homedir(), ".devsy"));
10080
+ const featureDirectory = import_node_path31.default.join(
10081
+ providerRoot,
10082
+ "contexts",
10083
+ owner.context,
10084
+ "workspaces",
10085
+ devsyId,
10086
+ "agent",
10087
+ ".docker-compose"
10088
+ );
10089
+ const observe = () => {
10090
+ if (!(0, import_node_util9.isDeepStrictEqual)(registration2(), owner) || readManagedRuntimeState(repoPath, workspace))
10091
+ throw new Error("Initial managed stop ownership or runtime state changed.");
10092
+ const matches = inspectWorkspaceContainers().filter(
10093
+ (c) => sameWorkspacePath(
10094
+ c.labels["com.docker.compose.project.working_dir"] ?? "",
10095
+ plan.composeDirectory
10096
+ )
10097
+ );
10098
+ if (!matches.length) return [];
10099
+ const projects = new Set(matches.map((c) => c.labels["com.docker.compose.project"]));
10100
+ if (projects.size !== 1 || !matches[0]?.labels["com.docker.compose.project"])
10101
+ throw new Error("Initial managed stop requires one exact Compose project.");
10102
+ const population = inspectManagedStopContainers(
10103
+ matches[0].labels["com.docker.compose.project"]
10104
+ );
10105
+ if (!sameSet(
10106
+ matches.map((c) => c.id),
10107
+ population.map((c) => c.id)
10108
+ ))
10109
+ throw new Error("Initial managed stop population changed.");
10110
+ proveManagedComposePopulation({
10111
+ plan,
10112
+ repoPath,
10113
+ composeProject: matches[0].labels["com.docker.compose.project"],
10114
+ providerRoot,
10115
+ featureDirectory,
10116
+ containers: population
10117
+ });
10118
+ return population;
10119
+ };
10120
+ const initial = observe();
10121
+ const stable = () => {
10122
+ const current = observe();
10123
+ if (!sameSet(
10124
+ initial.map((c) => c.id),
10125
+ current.map((c) => c.id)
10126
+ ) || current.some((c) => {
10127
+ const prior = initial.find((p) => p.id === c.id);
10128
+ return !prior || containerIdentity(prior) !== containerIdentity(c) || !prior.state.Running && c.state.Running;
10129
+ }))
10130
+ throw new Error("Initial managed stop population or identity changed.");
10131
+ return current;
10132
+ };
10133
+ stable();
10134
+ if (!initial.length) return false;
10135
+ for (const container of initial) {
10136
+ const current = stable().find((c) => c.id === container.id);
10137
+ if (current?.state.Running)
10138
+ stopExactManagedService(current.id, current.labels["com.docker.compose.service"] ?? "", {
10139
+ timeoutMs: 3e4
10140
+ });
10141
+ }
10142
+ if (stable().some((c) => c.state.Running))
10143
+ throw new Error("Initial managed stop left a workload running.");
10144
+ return true;
10145
+ }
10050
10146
  function stopRetainedManagedDevsyWorkspace(options) {
10051
10147
  const { repoPath, devsyId } = options;
10052
10148
  const linked = isLinkedWorktree(repoPath);
10053
10149
  const workspace = linked ? resolveWorktreeWorkspace(repoPath) : void 0;
10054
10150
  const retainedState = readManagedRuntimeState(repoPath, workspace);
10055
- if (!retainedState) return false;
10151
+ if (!retainedState) return stopInitialManagedDevsyWorkspace(repoPath, devsyId);
10056
10152
  const state = retainedState;
10057
10153
  if (state.devpodId !== devsyId || linked && !workspace) {
10058
10154
  throw new Error("Managed stop requires the exact retained workspace identity.");
@@ -10247,10 +10343,11 @@ function stopRetainedManagedDevsyWorkspace(options) {
10247
10343
  if (providerFailed) throw providerError;
10248
10344
  return true;
10249
10345
  }
10250
- var import_node_os4, import_node_path31, import_node_util9;
10346
+ var import_node_fs23, import_node_os4, import_node_path31, import_node_util9;
10251
10347
  var init_managed_devsy_stop = __esm({
10252
10348
  "src/core/managed-devsy-stop.ts"() {
10253
10349
  "use strict";
10350
+ import_node_fs23 = __toESM(require("fs"));
10254
10351
  import_node_os4 = __toESM(require("os"));
10255
10352
  import_node_path31 = __toESM(require("path"));
10256
10353
  import_node_util9 = require("util");
@@ -10808,7 +10905,7 @@ function hasControlCharacter2(value) {
10808
10905
  function readPolicyFile(filePath) {
10809
10906
  let initialStats;
10810
10907
  try {
10811
- initialStats = import_node_fs23.default.lstatSync(filePath);
10908
+ initialStats = import_node_fs24.default.lstatSync(filePath);
10812
10909
  } catch (error) {
10813
10910
  if (isErrno(error, "ENOENT")) return { status: "absent" };
10814
10911
  return { status: "invalid", error: "network policy could not be read" };
@@ -10821,16 +10918,16 @@ function readPolicyFile(filePath) {
10821
10918
  }
10822
10919
  let fileDescriptor;
10823
10920
  try {
10824
- fileDescriptor = import_node_fs23.default.openSync(
10921
+ fileDescriptor = import_node_fs24.default.openSync(
10825
10922
  filePath,
10826
- import_node_fs23.default.constants.O_RDONLY | import_node_fs23.default.constants.O_NONBLOCK | (import_node_fs23.default.constants.O_NOFOLLOW ?? 0)
10923
+ import_node_fs24.default.constants.O_RDONLY | import_node_fs24.default.constants.O_NONBLOCK | (import_node_fs24.default.constants.O_NOFOLLOW ?? 0)
10827
10924
  );
10828
10925
  } catch (error) {
10829
10926
  if (isErrno(error, "ENOENT")) return { status: "absent" };
10830
10927
  return { status: "invalid", error: "network policy could not be read" };
10831
10928
  }
10832
10929
  try {
10833
- const openedStats = import_node_fs23.default.fstatSync(fileDescriptor);
10930
+ const openedStats = import_node_fs24.default.fstatSync(fileDescriptor);
10834
10931
  if (!openedStats.isFile()) {
10835
10932
  return { status: "invalid", error: "network policy must be a regular file" };
10836
10933
  }
@@ -10843,12 +10940,12 @@ function readPolicyFile(filePath) {
10843
10940
  const buffer = Buffer.alloc(openedStats.size);
10844
10941
  let offset = 0;
10845
10942
  while (offset < buffer.length) {
10846
- const bytesRead = import_node_fs23.default.readSync(fileDescriptor, buffer, offset, buffer.length - offset, null);
10943
+ const bytesRead = import_node_fs24.default.readSync(fileDescriptor, buffer, offset, buffer.length - offset, null);
10847
10944
  if (bytesRead === 0)
10848
10945
  return { status: "invalid", error: "network policy changed during read" };
10849
10946
  offset += bytesRead;
10850
10947
  }
10851
- const finalStats = import_node_fs23.default.fstatSync(fileDescriptor);
10948
+ const finalStats = import_node_fs24.default.fstatSync(fileDescriptor);
10852
10949
  if (!finalStats.isFile() || finalStats.size !== openedStats.size) {
10853
10950
  return { status: "invalid", error: "network policy changed during read" };
10854
10951
  }
@@ -10857,7 +10954,7 @@ function readPolicyFile(filePath) {
10857
10954
  return { status: "invalid", error: "network policy could not be read" };
10858
10955
  } finally {
10859
10956
  try {
10860
- import_node_fs23.default.closeSync(fileDescriptor);
10957
+ import_node_fs24.default.closeSync(fileDescriptor);
10861
10958
  } catch {
10862
10959
  }
10863
10960
  }
@@ -10874,11 +10971,11 @@ function assertDisjoint(cidrs, message) {
10874
10971
  function isErrno(error, code) {
10875
10972
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
10876
10973
  }
10877
- var import_node_fs23, import_node_path32, NETWORK_POLICY_VERSION, NETWORK_POLICY_FILE, DEFAULT_NETWORK_POLICY_ENDPOINT_RESERVE, NETWORK_POLICY_PREFIX_LENGTHS, MAX_NETWORK_POLICY_FILE_BYTES, MAX_NETWORK_POLICY_ARRAY_ENTRIES, MAX_NETWORK_POLICY_DAEMON_ID_LENGTH, NetworkPolicyValidationError, NETWORK_POLICY_KEYS, PRIVATE_IPV4_RANGES;
10974
+ var import_node_fs24, import_node_path32, NETWORK_POLICY_VERSION, NETWORK_POLICY_FILE, DEFAULT_NETWORK_POLICY_ENDPOINT_RESERVE, NETWORK_POLICY_PREFIX_LENGTHS, MAX_NETWORK_POLICY_FILE_BYTES, MAX_NETWORK_POLICY_ARRAY_ENTRIES, MAX_NETWORK_POLICY_DAEMON_ID_LENGTH, NetworkPolicyValidationError, NETWORK_POLICY_KEYS, PRIVATE_IPV4_RANGES;
10878
10975
  var init_network_policy = __esm({
10879
10976
  "src/core/network-policy.ts"() {
10880
10977
  "use strict";
10881
- import_node_fs23 = __toESM(require("fs"));
10978
+ import_node_fs24 = __toESM(require("fs"));
10882
10979
  import_node_path32 = __toESM(require("path"));
10883
10980
  init_network_capacity();
10884
10981
  init_router();
@@ -10984,27 +11081,27 @@ function assertExactKeys(value, keys2) {
10984
11081
  function readDocument(store) {
10985
11082
  let descriptor;
10986
11083
  try {
10987
- descriptor = import_node_fs24.default.openSync(
11084
+ descriptor = import_node_fs25.default.openSync(
10988
11085
  store.filePath,
10989
- import_node_fs24.default.constants.O_RDONLY | import_node_fs24.default.constants.O_NONBLOCK | (import_node_fs24.default.constants.O_NOFOLLOW ?? 0)
11086
+ import_node_fs25.default.constants.O_RDONLY | import_node_fs25.default.constants.O_NONBLOCK | (import_node_fs25.default.constants.O_NOFOLLOW ?? 0)
10990
11087
  );
10991
11088
  } catch (error) {
10992
11089
  if (isErrno2(error, "ENOENT")) return void 0;
10993
11090
  throw claimError("invalid-state", "network claim state could not be read");
10994
11091
  }
10995
11092
  try {
10996
- const stat = import_node_fs24.default.fstatSync(descriptor);
11093
+ const stat = import_node_fs25.default.fstatSync(descriptor);
10997
11094
  if (!stat.isFile() || stat.size > NETWORK_CLAIMS_MAX_FILE_BYTES || (stat.mode & 63) !== 0) {
10998
11095
  throw claimError("invalid-state", "network claim state is not a bounded private file");
10999
11096
  }
11000
11097
  const buffer = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
11001
11098
  let count = 0;
11002
11099
  while (count < buffer.length) {
11003
- const bytesRead = import_node_fs24.default.readSync(descriptor, buffer, count, buffer.length - count, null);
11100
+ const bytesRead = import_node_fs25.default.readSync(descriptor, buffer, count, buffer.length - count, null);
11004
11101
  if (bytesRead === 0) break;
11005
11102
  count += bytesRead;
11006
11103
  }
11007
- const finalStat = import_node_fs24.default.fstatSync(descriptor);
11104
+ const finalStat = import_node_fs25.default.fstatSync(descriptor);
11008
11105
  if (count > NETWORK_CLAIMS_MAX_FILE_BYTES || finalStat.size !== stat.size) {
11009
11106
  throw claimError("invalid-state", "network claim state changed during read");
11010
11107
  }
@@ -11016,7 +11113,7 @@ function readDocument(store) {
11016
11113
  }
11017
11114
  return validateDocument(parsed, store.daemonId);
11018
11115
  } finally {
11019
- import_node_fs24.default.closeSync(descriptor);
11116
+ import_node_fs25.default.closeSync(descriptor);
11020
11117
  }
11021
11118
  }
11022
11119
  function validateDocument(value, daemonId) {
@@ -11120,12 +11217,12 @@ function isErrno2(error, code) {
11120
11217
  function claimError(code, message) {
11121
11218
  return new NetworkClaimError(code, message);
11122
11219
  }
11123
- var import_node_crypto13, import_node_fs24, import_node_path33, NETWORK_CLAIMS_VERSION, NETWORK_CLAIMS_ROOT, NETWORK_CLAIMS_MAX_RECORDS, NETWORK_CLAIMS_MAX_FILE_BYTES, NetworkClaimError, CLAIM_CORE_KEYS, CLAIM_KEYS, FENCE_KEYS, DOCUMENT_KEYS, MAX_CLAIM_TEXT, MAX_ENDPOINT_TEXT;
11220
+ var import_node_crypto13, import_node_fs25, import_node_path33, NETWORK_CLAIMS_VERSION, NETWORK_CLAIMS_ROOT, NETWORK_CLAIMS_MAX_RECORDS, NETWORK_CLAIMS_MAX_FILE_BYTES, NetworkClaimError, CLAIM_CORE_KEYS, CLAIM_KEYS, FENCE_KEYS, DOCUMENT_KEYS, MAX_CLAIM_TEXT, MAX_ENDPOINT_TEXT;
11124
11221
  var init_network_claims = __esm({
11125
11222
  "src/core/network-claims.ts"() {
11126
11223
  "use strict";
11127
11224
  import_node_crypto13 = require("crypto");
11128
- import_node_fs24 = __toESM(require("fs"));
11225
+ import_node_fs25 = __toESM(require("fs"));
11129
11226
  import_node_path33 = __toESM(require("path"));
11130
11227
  init_atomic_file();
11131
11228
  init_file_lock();
@@ -11182,7 +11279,7 @@ function networkOwnerKey(repoPath) {
11182
11279
  function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
11183
11280
  let names;
11184
11281
  try {
11185
- names = import_node_fs25.default.readdirSync(root);
11282
+ names = import_node_fs26.default.readdirSync(root);
11186
11283
  } catch (error) {
11187
11284
  if (error.code === "ENOENT") return void 0;
11188
11285
  throw new Error("Network claims cannot be inspected; repair is required.");
@@ -11192,20 +11289,20 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
11192
11289
  let found;
11193
11290
  for (const file of files) {
11194
11291
  const full = import_node_path34.default.join(root, file);
11195
- const stat = import_node_fs25.default.lstatSync(full);
11292
+ const stat = import_node_fs26.default.lstatSync(full);
11196
11293
  if (!stat.isFile() || stat.size > NETWORK_CLAIMS_MAX_FILE_BYTES)
11197
11294
  throw new Error("Invalid retained network claim file.");
11198
11295
  let value;
11199
- const descriptor = import_node_fs25.default.openSync(
11296
+ const descriptor = import_node_fs26.default.openSync(
11200
11297
  full,
11201
- import_node_fs25.default.constants.O_RDONLY | import_node_fs25.default.constants.O_NONBLOCK | import_node_fs25.default.constants.O_NOFOLLOW
11298
+ import_node_fs26.default.constants.O_RDONLY | import_node_fs26.default.constants.O_NONBLOCK | import_node_fs26.default.constants.O_NOFOLLOW
11202
11299
  );
11203
11300
  try {
11204
- if (!import_node_fs25.default.fstatSync(descriptor).isFile()) throw new Error();
11301
+ if (!import_node_fs26.default.fstatSync(descriptor).isFile()) throw new Error();
11205
11302
  const bytes2 = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
11206
11303
  let size = 0;
11207
11304
  while (size < bytes2.length) {
11208
- const count = import_node_fs25.default.readSync(descriptor, bytes2, size, bytes2.length - size, null);
11305
+ const count = import_node_fs26.default.readSync(descriptor, bytes2, size, bytes2.length - size, null);
11209
11306
  if (!count) break;
11210
11307
  size += count;
11211
11308
  }
@@ -11215,7 +11312,7 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
11215
11312
  } catch {
11216
11313
  throw new Error("Retained network claim could not be read safely.");
11217
11314
  } finally {
11218
- import_node_fs25.default.closeSync(descriptor);
11315
+ import_node_fs26.default.closeSync(descriptor);
11219
11316
  }
11220
11317
  if (typeof value.daemonId !== "string" || `${(0, import_node_crypto14.createHash)("sha256").update(value.daemonId).digest("hex")}.json` !== file)
11221
11318
  throw new Error("Retained network daemon identity is invalid.");
@@ -11231,12 +11328,12 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
11231
11328
  }
11232
11329
  return found;
11233
11330
  }
11234
- var import_node_crypto14, import_node_fs25, import_node_path34;
11331
+ var import_node_crypto14, import_node_fs26, import_node_path34;
11235
11332
  var init_network_claim_lookup = __esm({
11236
11333
  "src/core/network-claim-lookup.ts"() {
11237
11334
  "use strict";
11238
11335
  import_node_crypto14 = require("crypto");
11239
- import_node_fs25 = __toESM(require("fs"));
11336
+ import_node_fs26 = __toESM(require("fs"));
11240
11337
  import_node_path34 = __toESM(require("path"));
11241
11338
  init_network_claims();
11242
11339
  }
@@ -11396,7 +11493,7 @@ var init_network_provider_effect = __esm({
11396
11493
 
11397
11494
  // src/core/devsy-mutation.ts
11398
11495
  function withMutationLock(activity, target, operation) {
11399
- import_node_fs26.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
11496
+ import_node_fs27.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
11400
11497
  return withFileLockSync(
11401
11498
  DEVSY_MUTATION_LOCK_FILE,
11402
11499
  {
@@ -11477,12 +11574,12 @@ function stopOwnedDevsyWorkspace(devsyId, worktreePath) {
11477
11574
  function deleteOwnedDevsyWorkspace(devsyId, worktreePath) {
11478
11575
  return mutateOwnedDevsyWorkspace("delete", devsyId, worktreePath);
11479
11576
  }
11480
- var import_node_child_process16, import_node_fs26, import_node_path35, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
11577
+ var import_node_child_process16, import_node_fs27, import_node_path35, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
11481
11578
  var init_devsy_mutation = __esm({
11482
11579
  "src/core/devsy-mutation.ts"() {
11483
11580
  "use strict";
11484
11581
  import_node_child_process16 = require("child_process");
11485
- import_node_fs26 = __toESM(require("fs"));
11582
+ import_node_fs27 = __toESM(require("fs"));
11486
11583
  import_node_path35 = __toESM(require("path"));
11487
11584
  init_devsy_agent();
11488
11585
  init_devsy_workspaces();
@@ -12213,7 +12310,7 @@ async function runLifecycleWorker(request2, supervision, beforeAdmission) {
12213
12310
  if (process.platform === "win32")
12214
12311
  throw new Error("Lifecycle workers require POSIX process-group ownership.");
12215
12312
  const workerPath = import_node_path36.default.join(__dirname, "devrouter-lifecycle-worker.js");
12216
- if (!import_node_fs27.default.existsSync(workerPath))
12313
+ if (!import_node_fs28.default.existsSync(workerPath))
12217
12314
  throw new Error("The packaged lifecycle worker is missing; rebuild the CLI.");
12218
12315
  const child = (0, import_node_child_process17.fork)(workerPath, [], {
12219
12316
  detached: true,
@@ -12442,13 +12539,13 @@ async function runLifecycleWorker(request2, supervision, beforeAdmission) {
12442
12539
  function newLifecycleIds() {
12443
12540
  return { requestId: (0, import_node_crypto16.randomUUID)(), operationId: (0, import_node_crypto16.randomUUID)(), workerId: (0, import_node_crypto16.randomUUID)() };
12444
12541
  }
12445
- var import_node_child_process17, import_node_crypto16, import_node_fs27, import_node_path36, OUTPUT_BUFFER_LIMIT, DEFAULT_OUTPUT_PAGE_BYTES, LifecycleOutput, LifecycleWorkerAdmissionBusyError;
12542
+ var import_node_child_process17, import_node_crypto16, import_node_fs28, import_node_path36, OUTPUT_BUFFER_LIMIT, DEFAULT_OUTPUT_PAGE_BYTES, LifecycleOutput, LifecycleWorkerAdmissionBusyError;
12446
12543
  var init_reliability_worker = __esm({
12447
12544
  "src/core/reliability-worker.ts"() {
12448
12545
  "use strict";
12449
12546
  import_node_child_process17 = require("child_process");
12450
12547
  import_node_crypto16 = require("crypto");
12451
- import_node_fs27 = __toESM(require("fs"));
12548
+ import_node_fs28 = __toESM(require("fs"));
12452
12549
  import_node_path36 = __toESM(require("path"));
12453
12550
  init_file_lock();
12454
12551
  init_reliability_contract();
@@ -12676,7 +12773,7 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
12676
12773
  throw new Error(
12677
12774
  "Traefik routes did not reload; shared router restart is disabled for this operation."
12678
12775
  );
12679
- import_node_fs28.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12776
+ import_node_fs29.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12680
12777
  return withFileLock(
12681
12778
  ROUTER_RELOAD_LOCK_FILE,
12682
12779
  {
@@ -12707,12 +12804,12 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
12707
12804
  async function ensureTraefikRoutesRemoved(routes, options = {}) {
12708
12805
  return ensureTraefikRouteExpectation(routes, "removed", options);
12709
12806
  }
12710
- var import_node_child_process18, import_node_fs28, import_node_path37, ROUTER_API_BASE, ROUTER_RELOAD_LOCK_FILE, ROUTER_API_REQUEST_TIMEOUT_SECONDS, DEFAULT_INITIAL_TIMEOUT_MS, DEFAULT_RECOVERY_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS, ROUTER_API_PAGE_SIZE;
12807
+ var import_node_child_process18, import_node_fs29, import_node_path37, ROUTER_API_BASE, ROUTER_RELOAD_LOCK_FILE, ROUTER_API_REQUEST_TIMEOUT_SECONDS, DEFAULT_INITIAL_TIMEOUT_MS, DEFAULT_RECOVERY_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS, ROUTER_API_PAGE_SIZE;
12711
12808
  var init_traefik_route_health = __esm({
12712
12809
  "src/core/traefik-route-health.ts"() {
12713
12810
  "use strict";
12714
12811
  import_node_child_process18 = require("child_process");
12715
- import_node_fs28 = __toESM(require("fs"));
12812
+ import_node_fs29 = __toESM(require("fs"));
12716
12813
  import_node_path37 = __toESM(require("path"));
12717
12814
  init_file_lock();
12718
12815
  init_host_routes();
@@ -12730,7 +12827,7 @@ var init_traefik_route_health = __esm({
12730
12827
 
12731
12828
  // src/core/devpod-mutation.ts
12732
12829
  function withMutationLock2(activity, target, operation) {
12733
- import_node_fs29.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12830
+ import_node_fs30.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12734
12831
  return withFileLockSync(
12735
12832
  DEVPOD_MUTATION_LOCK_FILE,
12736
12833
  {
@@ -12820,12 +12917,12 @@ function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
12820
12917
  resetWorkspaceRuntimeCaches();
12821
12918
  return result;
12822
12919
  }
12823
- var import_node_child_process19, import_node_fs29, import_node_path38, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
12920
+ var import_node_child_process19, import_node_fs30, import_node_path38, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
12824
12921
  var init_devpod_mutation = __esm({
12825
12922
  "src/core/devpod-mutation.ts"() {
12826
12923
  "use strict";
12827
12924
  import_node_child_process19 = require("child_process");
12828
- import_node_fs29 = __toESM(require("fs"));
12925
+ import_node_fs30 = __toESM(require("fs"));
12829
12926
  import_node_path38 = __toESM(require("path"));
12830
12927
  init_devpod_workspaces();
12831
12928
  init_devsy_mutation();
@@ -12840,6 +12937,278 @@ var init_devpod_mutation = __esm({
12840
12937
  }
12841
12938
  });
12842
12939
 
12940
+ // src/core/host-port-claims.ts
12941
+ function isRecord6(value) {
12942
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12943
+ }
12944
+ function optionalLabel(labels, key) {
12945
+ const value = isRecord6(labels) ? labels[key] : void 0;
12946
+ return typeof value === "string" && value.length > 0 ? value : void 0;
12947
+ }
12948
+ function normalizeHostIp(value) {
12949
+ if (typeof value !== "string") return null;
12950
+ const trimmed = value.trim();
12951
+ if (trimmed.length === 0 || trimmed === "0.0.0.0" || trimmed === "::") return null;
12952
+ return trimmed;
12953
+ }
12954
+ function parseSinglePortValue(value, service, label2) {
12955
+ if (typeof value === "number") {
12956
+ if (!Number.isInteger(value) || value <= 0 || value > 65535) {
12957
+ throw new Error(`Service '${service}' has an out-of-range ${label2} port.`);
12958
+ }
12959
+ return value;
12960
+ }
12961
+ if (typeof value === "string") {
12962
+ const parsed = Number(value);
12963
+ if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
12964
+ throw new Error(`Service '${service}' has an out-of-range ${label2} port '${value}'.`);
12965
+ }
12966
+ return parsed;
12967
+ }
12968
+ throw new Error(`Service '${service}' has an unsupported ${label2} port value.`);
12969
+ }
12970
+ function parseFixedPublishedPort(value, service) {
12971
+ if (value === void 0 || value === null || value === "" || value === 0 || value === "0") {
12972
+ return null;
12973
+ }
12974
+ if (typeof value === "string" && value.includes("-")) {
12975
+ return null;
12976
+ }
12977
+ return parseSinglePortValue(value, service, "published host");
12978
+ }
12979
+ function parsePublishedRange(value, service) {
12980
+ const match = /^(\d+)-(\d+)$/.exec(value.trim());
12981
+ if (!match) return void 0;
12982
+ const lower = Number(match[1]);
12983
+ const upper = Number(match[2]);
12984
+ if (!Number.isInteger(lower) || !Number.isInteger(upper) || lower <= 0 || upper < lower || upper > 65535) {
12985
+ throw new Error(`Service '${service}' publishes an invalid host port range '${value}'.`);
12986
+ }
12987
+ return [lower, upper];
12988
+ }
12989
+ function resolveFixedPublishedHostPorts(renderedConfig) {
12990
+ const services = isRecord6(renderedConfig) ? renderedConfig.services : void 0;
12991
+ const bindings = [];
12992
+ const pushBinding = (binding) => {
12993
+ bindings.push(binding);
12994
+ if (bindings.length > MAX_DESIRED_HOST_PORTS) {
12995
+ throw new Error(
12996
+ `The managed Compose model declares more than ${MAX_DESIRED_HOST_PORTS} fixed host ports.`
12997
+ );
12998
+ }
12999
+ };
13000
+ for (const [service, serviceValue] of Object.entries(isRecord6(services) ? services : {})) {
13001
+ const serviceRecord = isRecord6(serviceValue) ? serviceValue : {};
13002
+ if (!Array.isArray(serviceRecord.ports)) continue;
13003
+ for (const port of serviceRecord.ports) {
13004
+ if (!isRecord6(port)) {
13005
+ throw new Error(`Service '${service}' declares an unsupported port entry.`);
13006
+ }
13007
+ parseSinglePortValue(port.target, service, "container target");
13008
+ const hostIp = normalizeHostIp(port.host_ip);
13009
+ const protocol = typeof port.protocol === "string" && port.protocol ? port.protocol : "tcp";
13010
+ if (typeof port.published !== "string" || !port.published.includes("-")) {
13011
+ const hostPort = parseFixedPublishedPort(port.published, service);
13012
+ if (hostPort === null) continue;
13013
+ pushBinding({ service, hostIp, hostPort, protocol });
13014
+ continue;
13015
+ }
13016
+ const range = parsePublishedRange(port.published, service);
13017
+ if (!range) {
13018
+ throw new Error(
13019
+ `Service '${service}' publishes an unsupported host port value '${String(port.published)}'.`
13020
+ );
13021
+ }
13022
+ for (let hostPort = range[0]; hostPort <= range[1]; hostPort += 1) {
13023
+ pushBinding({ service, hostIp, hostPort, protocol });
13024
+ }
13025
+ }
13026
+ }
13027
+ return bindings;
13028
+ }
13029
+ function hostPortBindingsConflict(desired, holder) {
13030
+ return desired.hostPort === holder.hostPort && desired.protocol === holder.protocol && // Wildcards collide with everything in their protocol family; specific
13031
+ // addresses only collide with themselves. IPv6 nuance intentionally
13032
+ // over-approximates: a loud refusal beats a missed kernel collision.
13033
+ (desired.hostIp === null || holder.hostIp === null || desired.hostIp === holder.hostIp);
13034
+ }
13035
+ function runDocker(args) {
13036
+ const result = (0, import_node_child_process20.spawnSync)("docker", args, {
13037
+ ...networkDockerOptions(),
13038
+ encoding: "utf-8",
13039
+ timeout: DOCKER_TIMEOUT_MS,
13040
+ maxBuffer: DOCKER_MAX_BUFFER
13041
+ });
13042
+ if (result.error || result.status !== 0) {
13043
+ const reason = (result.stderr || result.stdout || result.error?.message || "unknown error").toString().trim();
13044
+ throw new Error(`docker ${String(args[0])} failed: ${reason}`);
13045
+ }
13046
+ if (typeof result.stdout !== "string") {
13047
+ throw new Error(`docker ${String(args[0])} returned invalid output.`);
13048
+ }
13049
+ return result.stdout;
13050
+ }
13051
+ function parseHolderPortMap(value) {
13052
+ const ports = [];
13053
+ for (const [key, entries] of Object.entries(isRecord6(value) ? value : {})) {
13054
+ const separator = key.lastIndexOf("/");
13055
+ if (separator <= 0) continue;
13056
+ const protocol = key.slice(separator + 1);
13057
+ if (!Array.isArray(entries)) continue;
13058
+ for (const entry of entries) {
13059
+ if (!isRecord6(entry) || entry.HostPort === void 0 || entry.HostPort === null) continue;
13060
+ const bound = Number(entry.HostPort);
13061
+ if (!Number.isInteger(bound) || bound <= 0) continue;
13062
+ ports.push({ hostIp: normalizeHostIp(entry.HostIp), hostPort: bound, protocol });
13063
+ }
13064
+ }
13065
+ return ports;
13066
+ }
13067
+ function parseHolderSnapshots(stdout) {
13068
+ return stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
13069
+ let parsed;
13070
+ try {
13071
+ parsed = JSON.parse(line);
13072
+ } catch {
13073
+ throw new Error("Host-port holder inspection returned malformed JSON.");
13074
+ }
13075
+ if (!isRecord6(parsed) || typeof parsed.id !== "string" || parsed.id.length === 0) {
13076
+ throw new Error("Host-port holder inspection returned an invalid container record.");
13077
+ }
13078
+ const name = typeof parsed.name === "string" ? parsed.name.replace(/^\//, "") : parsed.id;
13079
+ return {
13080
+ id: parsed.id,
13081
+ name,
13082
+ composeProject: optionalLabel(parsed.labels, "com.docker.compose.project"),
13083
+ workingDir: optionalLabel(parsed.labels, "com.docker.compose.project.working_dir"),
13084
+ ports: parseHolderPortMap(parsed.ports)
13085
+ };
13086
+ });
13087
+ }
13088
+ function listHolderSnapshots() {
13089
+ const listed = runDocker(["ps", "--filter", "status=running", "--format", "{{.ID}}"]);
13090
+ const ids2 = listed.split(/\r?\n/).map((id5) => id5.trim()).filter(Boolean);
13091
+ if (ids2.length === 0) return [];
13092
+ if (ids2.length > MAX_RUNNING_HOLDERS) {
13093
+ throw new Error(
13094
+ `Host-port holder inspection found ${ids2.length} running containers, above its bound.`
13095
+ );
13096
+ }
13097
+ const inspected = runDocker(["inspect", "--format", HOST_PORT_INSPECT_TEMPLATE, ...ids2]);
13098
+ return parseHolderSnapshots(inspected);
13099
+ }
13100
+ function renderManagedComposeModel(plan, workspace) {
13101
+ const fileArgs = plan.composeFiles.flatMap((file) => ["-f", file]);
13102
+ const result = (0, import_node_child_process20.spawnSync)(
13103
+ "docker",
13104
+ [
13105
+ "compose",
13106
+ "--profile",
13107
+ "*",
13108
+ "--project-directory",
13109
+ plan.composeDirectory,
13110
+ ...fileArgs,
13111
+ "config",
13112
+ "--format",
13113
+ "json"
13114
+ ],
13115
+ {
13116
+ cwd: plan.composeDirectory,
13117
+ encoding: "utf-8",
13118
+ env: managedComposeEnvironment(workspace),
13119
+ stdio: ["ignore", "pipe", "pipe"],
13120
+ timeout: COMPOSE_RENDER_TIMEOUT_MS,
13121
+ maxBuffer: COMPOSE_RENDER_MAX_BUFFER
13122
+ }
13123
+ );
13124
+ if (result.status !== 0 || result.error || !result.stdout?.trim()) {
13125
+ const reason = (result.stderr || result.error?.message || "unknown error").toString().trim().split(/\r?\n/).slice(-3).join("; ");
13126
+ throw new Error(
13127
+ `Host-port claims could not render the managed Compose model that the start would bind: ${reason}`
13128
+ );
13129
+ }
13130
+ try {
13131
+ return JSON.parse(result.stdout);
13132
+ } catch {
13133
+ throw new Error("Host-port claims received a malformed rendered Compose model.");
13134
+ }
13135
+ }
13136
+ function attributeHolderWorktree(workingDir) {
13137
+ if (import_node_path39.default.basename(workingDir) !== ".devcontainer") return void 0;
13138
+ return comparableWorkspacePath(import_node_path39.default.dirname(workingDir));
13139
+ }
13140
+ function holderAttribution(holder, repoPath) {
13141
+ const attribution = {};
13142
+ if (holder.composeProject) attribution.holderComposeProject = holder.composeProject;
13143
+ const worktree = holder.workingDir ? attributeHolderWorktree(holder.workingDir) : void 0;
13144
+ if (!worktree) return attribution;
13145
+ attribution.holderWorktreePath = worktree;
13146
+ try {
13147
+ const owner = listWorkspaceOwnership(repoPath).find(
13148
+ (record5) => comparableWorkspacePath(record5.worktreePath) === worktree
13149
+ );
13150
+ if (owner) {
13151
+ attribution.holderWorkspace = owner.workspace;
13152
+ if (owner.branch) attribution.holderBranch = owner.branch;
13153
+ }
13154
+ } catch {
13155
+ }
13156
+ return attribution;
13157
+ }
13158
+ function remediationFor(holder, attribution) {
13159
+ if (attribution.holderWorktreePath) {
13160
+ return `Stop the holding workspace with 'devrouter stop ${attribution.holderWorktreePath}', or override the consumer's published host binding through its own configuration, then run ensure again.`;
13161
+ }
13162
+ return `Stop or reconfigure the holding container '${holder.name}' and run ensure again; devrouter never rewrites consumer-declared host bindings.`;
13163
+ }
13164
+ function detectHostPortClaimConflicts(options) {
13165
+ const desired = resolveFixedPublishedHostPorts(
13166
+ renderManagedComposeModel(options.plan, options.workspace)
13167
+ );
13168
+ if (desired.length === 0) return [];
13169
+ const holders = listHolderSnapshots();
13170
+ const devcontainerDir = import_node_path39.default.join(options.repoPath, ".devcontainer");
13171
+ const conflicts = [];
13172
+ for (const binding of desired) {
13173
+ for (const holder of holders) {
13174
+ for (const holderPort of holder.ports) {
13175
+ if (!hostPortBindingsConflict(binding, holderPort)) continue;
13176
+ if (holder.workingDir && sameWorkspacePath(holder.workingDir, devcontainerDir)) continue;
13177
+ const attribution = holderAttribution(holder, options.repoPath);
13178
+ conflicts.push({
13179
+ service: binding.service,
13180
+ hostIp: binding.hostIp,
13181
+ hostPort: binding.hostPort,
13182
+ protocol: binding.protocol,
13183
+ holderContainer: holder.name,
13184
+ ...attribution,
13185
+ remediation: remediationFor(holder, attribution)
13186
+ });
13187
+ }
13188
+ }
13189
+ }
13190
+ return conflicts;
13191
+ }
13192
+ var import_node_child_process20, import_node_path39, COMPOSE_RENDER_TIMEOUT_MS, COMPOSE_RENDER_MAX_BUFFER, DOCKER_TIMEOUT_MS, DOCKER_MAX_BUFFER, MAX_DESIRED_HOST_PORTS, MAX_RUNNING_HOLDERS, HOST_PORT_INSPECT_TEMPLATE;
13193
+ var init_host_port_claims = __esm({
13194
+ "src/core/host-port-claims.ts"() {
13195
+ "use strict";
13196
+ import_node_child_process20 = require("child_process");
13197
+ import_node_path39 = __toESM(require("path"));
13198
+ init_devcontainer_profile();
13199
+ init_network_effect_scope();
13200
+ init_workspace();
13201
+ init_workspace_ownership();
13202
+ COMPOSE_RENDER_TIMEOUT_MS = 3e4;
13203
+ COMPOSE_RENDER_MAX_BUFFER = 8 * 1024 * 1024;
13204
+ DOCKER_TIMEOUT_MS = 1e4;
13205
+ DOCKER_MAX_BUFFER = 1024 * 1024;
13206
+ MAX_DESIRED_HOST_PORTS = 1024;
13207
+ MAX_RUNNING_HOLDERS = 512;
13208
+ HOST_PORT_INSPECT_TEMPLATE = '{"id":{{json .Id}},"name":{{json .Name}},"labels":{"com.docker.compose.project":{{json (index .Config.Labels "com.docker.compose.project")}},"com.docker.compose.project.working_dir":{{json (index .Config.Labels "com.docker.compose.project.working_dir")}}},"ports":{{json .NetworkSettings.Ports}}}';
13209
+ }
13210
+ });
13211
+
12843
13212
  // src/core/http-route-probe.ts
12844
13213
  function probeHttpRoute(host, options = {}) {
12845
13214
  const maxTimeSeconds = options.maxTimeSeconds ?? DEFAULT_MAX_TIME_SECONDS;
@@ -12866,7 +13235,7 @@ function probeHttpRoute(host, options = {}) {
12866
13235
  args.push("--cacert", CERT_FILE);
12867
13236
  }
12868
13237
  args.push(url);
12869
- const result = (0, import_node_child_process20.spawnSync)("curl", args, { encoding: "utf-8" });
13238
+ const result = (0, import_node_child_process21.spawnSync)("curl", args, { encoding: "utf-8" });
12870
13239
  const stdout = result.stdout?.trim() ?? "";
12871
13240
  const status = /^\d{3}$/.test(stdout) ? Number(stdout) : void 0;
12872
13241
  const ok = result.status === 0 && status !== void 0 && status >= 100 && status < 500;
@@ -12901,7 +13270,7 @@ function probeHttpReadinessSync(host, readiness, maxTimeSeconds, repoPath) {
12901
13270
  }
12902
13271
  let result;
12903
13272
  try {
12904
- result = (0, import_node_child_process20.spawnSync)("curl", args, {
13273
+ result = (0, import_node_child_process21.spawnSync)("curl", args, {
12905
13274
  encoding: "utf-8",
12906
13275
  maxBuffer: CONTRACT_MAX_BUFFER,
12907
13276
  timeout: Math.ceil(parentTimeoutMs)
@@ -12983,11 +13352,11 @@ function transportFailure() {
12983
13352
  classification: "transport"
12984
13353
  };
12985
13354
  }
12986
- var import_node_child_process20, DEFAULT_MAX_TIME_SECONDS, CONTROLLER_CURL_MAX_TIME_SECONDS, PARENT_TIMEOUT_PADDING_MS, CONTRACT_MAX_BUFFER, MIME_TOKEN_RE2;
13355
+ var import_node_child_process21, DEFAULT_MAX_TIME_SECONDS, CONTROLLER_CURL_MAX_TIME_SECONDS, PARENT_TIMEOUT_PADDING_MS, CONTRACT_MAX_BUFFER, MIME_TOKEN_RE2;
12987
13356
  var init_http_route_probe = __esm({
12988
13357
  "src/core/http-route-probe.ts"() {
12989
13358
  "use strict";
12990
- import_node_child_process20 = require("child_process");
13359
+ import_node_child_process21 = require("child_process");
12991
13360
  init_router();
12992
13361
  init_tls();
12993
13362
  DEFAULT_MAX_TIME_SECONDS = 5;
@@ -12999,20 +13368,20 @@ var init_http_route_probe = __esm({
12999
13368
  });
13000
13369
 
13001
13370
  // src/core/managed-host-preparation.ts
13002
- var import_node_child_process21, import_node_fs30;
13371
+ var import_node_child_process22, import_node_fs31;
13003
13372
  var init_managed_host_preparation = __esm({
13004
13373
  "src/core/managed-host-preparation.ts"() {
13005
13374
  "use strict";
13006
- import_node_child_process21 = require("child_process");
13007
- import_node_fs30 = __toESM(require("fs"));
13375
+ import_node_child_process22 = require("child_process");
13376
+ import_node_fs31 = __toESM(require("fs"));
13008
13377
  init_repo_config();
13009
13378
  }
13010
13379
  });
13011
13380
 
13012
13381
  // src/core/managed-post-start.ts
13013
13382
  function readRegularFileBytes(filePath) {
13014
- if (!import_node_fs31.default.existsSync(filePath) || !import_node_fs31.default.lstatSync(filePath).isFile()) return void 0;
13015
- return import_node_fs31.default.readFileSync(filePath);
13383
+ if (!import_node_fs32.default.existsSync(filePath) || !import_node_fs32.default.lstatSync(filePath).isFile()) return void 0;
13384
+ return import_node_fs32.default.readFileSync(filePath);
13016
13385
  }
13017
13386
  function readRegularFile(filePath) {
13018
13387
  return readRegularFileBytes(filePath)?.toString("utf-8");
@@ -13021,11 +13390,11 @@ function adapterFingerprint(adapter) {
13021
13390
  return (0, import_node_crypto17.createHash)("sha256").update(adapter).digest("hex");
13022
13391
  }
13023
13392
  function resolveManagedPostStartPlan(repoPath) {
13024
- const adapterPath = import_node_path39.default.join(repoPath, MANAGED_ADAPTER_PATH);
13393
+ const adapterPath = import_node_path40.default.join(repoPath, MANAGED_ADAPTER_PATH);
13025
13394
  const adapterBytes = readRegularFileBytes(adapterPath);
13026
13395
  const adapterText = adapterBytes?.toString("utf-8");
13027
- const dockerfilePath = import_node_path39.default.join(repoPath, ".devcontainer", "Dockerfile");
13028
- const devcontainerPath = import_node_path39.default.join(repoPath, ".devcontainer", "devcontainer.json");
13396
+ const dockerfilePath = import_node_path40.default.join(repoPath, ".devcontainer", "Dockerfile");
13397
+ const devcontainerPath = import_node_path40.default.join(repoPath, ".devcontainer", "devcontainer.json");
13029
13398
  const dockerfile = readRegularFile(dockerfilePath) ?? "";
13030
13399
  const devcontainer = readRegularFile(devcontainerPath) ?? "";
13031
13400
  const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
@@ -13071,7 +13440,7 @@ function assertSafeProcessName(name) {
13071
13440
  }
13072
13441
  function runManagedProcessAction(options) {
13073
13442
  assertSafeProcessName(options.name);
13074
- const result = (0, import_node_child_process22.spawnSync)(
13443
+ const result = (0, import_node_child_process23.spawnSync)(
13075
13444
  "docker",
13076
13445
  [
13077
13446
  "exec",
@@ -13108,14 +13477,14 @@ function runManagedProcessAction(options) {
13108
13477
  }
13109
13478
  return "drifted";
13110
13479
  }
13111
- var import_node_child_process22, import_node_crypto17, import_node_fs31, import_node_path39, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
13480
+ var import_node_child_process23, import_node_crypto17, import_node_fs32, import_node_path40, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
13112
13481
  var init_managed_post_start = __esm({
13113
13482
  "src/core/managed-post-start.ts"() {
13114
13483
  "use strict";
13115
- import_node_child_process22 = require("child_process");
13484
+ import_node_child_process23 = require("child_process");
13116
13485
  import_node_crypto17 = require("crypto");
13117
- import_node_fs31 = __toESM(require("fs"));
13118
- import_node_path39 = __toESM(require("path"));
13486
+ import_node_fs32 = __toESM(require("fs"));
13487
+ import_node_path40 = __toESM(require("path"));
13119
13488
  init_devcontainer_config();
13120
13489
  init_network_effect_scope();
13121
13490
  MANAGED_MARKER = "devrouter:managed devcontainer";
@@ -13567,7 +13936,7 @@ function readDocker(endpoint, args, timeoutMs) {
13567
13936
  const env = { ...process.env };
13568
13937
  delete env.DOCKER_CONTEXT;
13569
13938
  delete env.DOCKER_HOST;
13570
- const result = (0, import_node_child_process23.spawnSync)("docker", ["--host", endpoint, ...args], {
13939
+ const result = (0, import_node_child_process24.spawnSync)("docker", ["--host", endpoint, ...args], {
13571
13940
  env,
13572
13941
  encoding: "utf-8",
13573
13942
  timeout: timeoutMs,
@@ -13722,11 +14091,11 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
13722
14091
  }
13723
14092
  return result;
13724
14093
  }
13725
- var import_node_child_process23, NetworkInventoryError, MAX_IDS, MAX_OUTPUT_BYTES, FULL_ID, NETWORK_TEMPLATE, CONTAINER_TEMPLATE;
14094
+ var import_node_child_process24, NetworkInventoryError, MAX_IDS, MAX_OUTPUT_BYTES, FULL_ID, NETWORK_TEMPLATE, CONTAINER_TEMPLATE;
13726
14095
  var init_network_inventory = __esm({
13727
14096
  "src/core/network-inventory.ts"() {
13728
14097
  "use strict";
13729
- import_node_child_process23 = require("child_process");
14098
+ import_node_child_process24 = require("child_process");
13730
14099
  init_devpod_environment();
13731
14100
  NetworkInventoryError = class extends Error {
13732
14101
  };
@@ -14054,26 +14423,26 @@ function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
14054
14423
  try {
14055
14424
  if (!endpoint.startsWith("unix:///")) return false;
14056
14425
  const socket = endpoint.slice("unix://".length);
14057
- const table = import_node_fs32.default.readFileSync(import_node_path40.default.join(procRoot, "net/unix"), "utf8");
14426
+ const table = import_node_fs33.default.readFileSync(import_node_path41.default.join(procRoot, "net/unix"), "utf8");
14058
14427
  if (Buffer.byteLength(table) > 1024 * 1024) return false;
14059
14428
  const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
14060
14429
  if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
14061
14430
  const inode = `socket:[${rows2[0][6]}]`;
14062
- const ownNamespace = import_node_fs32.default.readlinkSync(import_node_path40.default.join(procRoot, "self/ns/net"));
14063
- const pids = import_node_fs32.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
14431
+ const ownNamespace = import_node_fs33.default.readlinkSync(import_node_path41.default.join(procRoot, "self/ns/net"));
14432
+ const pids = import_node_fs33.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
14064
14433
  if (pids.length > 4096) return false;
14065
14434
  for (const pid of pids) {
14066
14435
  if (Date.now() > deadline) return false;
14067
14436
  try {
14068
- const root = import_node_path40.default.join(procRoot, pid);
14069
- if (import_node_path40.default.basename(import_node_fs32.default.readlinkSync(import_node_path40.default.join(root, "exe"))) !== "dockerd") continue;
14070
- if (import_node_fs32.default.readlinkSync(import_node_path40.default.join(root, "ns/net")) !== ownNamespace) continue;
14071
- const descriptors = import_node_fs32.default.readdirSync(import_node_path40.default.join(root, "fd"));
14437
+ const root = import_node_path41.default.join(procRoot, pid);
14438
+ if (import_node_path41.default.basename(import_node_fs33.default.readlinkSync(import_node_path41.default.join(root, "exe"))) !== "dockerd") continue;
14439
+ if (import_node_fs33.default.readlinkSync(import_node_path41.default.join(root, "ns/net")) !== ownNamespace) continue;
14440
+ const descriptors = import_node_fs33.default.readdirSync(import_node_path41.default.join(root, "fd"));
14072
14441
  if (descriptors.length > 4096) continue;
14073
14442
  if (descriptors.some((fd) => {
14074
14443
  if (Date.now() > deadline) return false;
14075
14444
  try {
14076
- return import_node_fs32.default.readlinkSync(import_node_path40.default.join(root, "fd", fd)) === inode;
14445
+ return import_node_fs33.default.readlinkSync(import_node_path41.default.join(root, "fd", fd)) === inode;
14077
14446
  } catch {
14078
14447
  return false;
14079
14448
  }
@@ -14087,12 +14456,12 @@ function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
14087
14456
  }
14088
14457
  return false;
14089
14458
  }
14090
- var import_node_fs32, import_node_path40;
14459
+ var import_node_fs33, import_node_path41;
14091
14460
  var init_network_local_daemon = __esm({
14092
14461
  "src/core/network-local-daemon.ts"() {
14093
14462
  "use strict";
14094
- import_node_fs32 = __toESM(require("fs"));
14095
- import_node_path40 = __toESM(require("path"));
14463
+ import_node_fs33 = __toESM(require("fs"));
14464
+ import_node_path41 = __toESM(require("path"));
14096
14465
  }
14097
14466
  });
14098
14467
 
@@ -14120,7 +14489,7 @@ function parseLinuxNetworkRoutes(output2) {
14120
14489
  function collectNetworkRoutes(options) {
14121
14490
  if ((options.platform ?? process.platform) !== "linux" || !hasLocalDockerNetworkNamespace(options.endpoint))
14122
14491
  return { status: "unknown", routes: [] };
14123
- const result = (0, import_node_child_process24.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
14492
+ const result = (0, import_node_child_process25.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
14124
14493
  encoding: "utf8",
14125
14494
  timeout: 5e3,
14126
14495
  maxBuffer: 1024 * 1024
@@ -14128,11 +14497,11 @@ function collectNetworkRoutes(options) {
14128
14497
  if (result.error || result.status !== 0) return { status: "unknown", routes: [] };
14129
14498
  return parseLinuxNetworkRoutes(result.stdout);
14130
14499
  }
14131
- var import_node_child_process24, import_node_net3;
14500
+ var import_node_child_process25, import_node_net3;
14132
14501
  var init_network_routes = __esm({
14133
14502
  "src/core/network-routes.ts"() {
14134
14503
  "use strict";
14135
- import_node_child_process24 = require("child_process");
14504
+ import_node_child_process25 = require("child_process");
14136
14505
  import_node_net3 = require("net");
14137
14506
  init_network_capacity();
14138
14507
  init_network_local_daemon();
@@ -14208,14 +14577,14 @@ var init_network_compose = __esm({
14208
14577
  });
14209
14578
 
14210
14579
  // src/core/network-compose-files.ts
14211
- var import_node_child_process25, import_node_crypto19, import_node_fs33, import_node_path41, import_yaml4, MAX_BYTES3;
14580
+ var import_node_child_process26, import_node_crypto19, import_node_fs34, import_node_path42, import_yaml4, MAX_BYTES3;
14212
14581
  var init_network_compose_files = __esm({
14213
14582
  "src/core/network-compose-files.ts"() {
14214
14583
  "use strict";
14215
- import_node_child_process25 = require("child_process");
14584
+ import_node_child_process26 = require("child_process");
14216
14585
  import_node_crypto19 = require("crypto");
14217
- import_node_fs33 = __toESM(require("fs"));
14218
- import_node_path41 = __toESM(require("path"));
14586
+ import_node_fs34 = __toESM(require("fs"));
14587
+ import_node_path42 = __toESM(require("path"));
14219
14588
  import_yaml4 = __toESM(require("yaml"));
14220
14589
  init_atomic_file();
14221
14590
  init_devcontainer_profile();
@@ -14225,11 +14594,11 @@ var init_network_compose_files = __esm({
14225
14594
  });
14226
14595
 
14227
14596
  // src/core/network-connected-route.ts
14228
- var import_node_child_process26;
14597
+ var import_node_child_process27;
14229
14598
  var init_network_connected_route = __esm({
14230
14599
  "src/core/network-connected-route.ts"() {
14231
14600
  "use strict";
14232
- import_node_child_process26 = require("child_process");
14601
+ import_node_child_process27 = require("child_process");
14233
14602
  init_network_capacity();
14234
14603
  }
14235
14604
  });
@@ -14319,19 +14688,20 @@ function resolveLinkedTarget(repoPath) {
14319
14688
  gitCommonDir: resolveGitCommonDir(repoPath)
14320
14689
  };
14321
14690
  }
14322
- var import_node_child_process27, import_node_fs34, import_node_path42;
14691
+ var import_node_child_process28, import_node_fs35, import_node_path43;
14323
14692
  var init_workspace_ensure = __esm({
14324
14693
  "src/core/workspace-ensure.ts"() {
14325
14694
  "use strict";
14326
- import_node_child_process27 = require("child_process");
14327
- import_node_fs34 = __toESM(require("fs"));
14328
- import_node_path42 = __toESM(require("path"));
14695
+ import_node_child_process28 = require("child_process");
14696
+ import_node_fs35 = __toESM(require("fs"));
14697
+ import_node_path43 = __toESM(require("path"));
14329
14698
  init_devcontainer_profile();
14330
14699
  init_devpod_environment();
14331
14700
  init_devpod_mutation();
14332
14701
  init_devpod_workspaces();
14333
14702
  init_devsy_mutation();
14334
14703
  init_file_lock();
14704
+ init_host_port_claims();
14335
14705
  init_host_routes();
14336
14706
  init_http_route_probe();
14337
14707
  init_managed_host_preparation();
@@ -14460,13 +14830,13 @@ function prepareManagedLifecycleOperation(input2) {
14460
14830
  };
14461
14831
  });
14462
14832
  }
14463
- function assertCurrentCapacityController(expected, directory = import_node_path43.default.join(DEVROUTER_HOME, "controller")) {
14833
+ function assertCurrentCapacityController(expected, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller")) {
14464
14834
  const current = new ControllerStore(directory).read();
14465
14835
  if (!expected || current?.store !== expected.store || current.epoch !== expected.epoch)
14466
14836
  throw new Error("Capacity controller incarnation changed.");
14467
14837
  }
14468
14838
  function settlePreparedLifecycleCapacity(input2) {
14469
- const directory = input2.directory ?? import_node_path43.default.join(DEVROUTER_HOME, "controller");
14839
+ const directory = input2.directory ?? import_node_path44.default.join(DEVROUTER_HOME, "controller");
14470
14840
  const store = new CapacityStore(directory);
14471
14841
  for (let attempt = 0; attempt < 3; attempt++) {
14472
14842
  const snapshot = store.read();
@@ -14680,7 +15050,7 @@ async function superviseLifecycle(kind, repoPath, options = {}, command) {
14680
15050
  removeSignals();
14681
15051
  }
14682
15052
  }
14683
- function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs, maxSampleAgeMs, directory = import_node_path43.default.join(DEVROUTER_HOME, "controller"), controller, admission) {
15053
+ function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs, maxSampleAgeMs, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller"), controller, admission) {
14684
15054
  if (request2.kind === "stop") throw new Error("Stop never requires capacity admission.");
14685
15055
  const capacity = new CapacityStore(directory);
14686
15056
  const snapshot = capacity.read();
@@ -14780,7 +15150,7 @@ function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs,
14780
15150
  return decision;
14781
15151
  }
14782
15152
  function capacityPolicyTargets(repoPath) {
14783
- const policy = readCapacityPolicy(import_node_path43.default.join(DEVROUTER_HOME, "controller"));
15153
+ const policy = readCapacityPolicy(import_node_path44.default.join(DEVROUTER_HOME, "controller"));
14784
15154
  return policy?.admissions === "enabled" && policy.enrollments.some((entry) => entry.repoPath === repoPath);
14785
15155
  }
14786
15156
  function controllerWaitSeconds(deadline) {
@@ -14789,7 +15159,7 @@ function controllerWaitSeconds(deadline) {
14789
15159
  async function superviseThroughController(input2) {
14790
15160
  if (input2.kind === "exec" && !input2.command)
14791
15161
  throw new Error("Exec requires a command for controller admission.");
14792
- const directory = import_node_path43.default.join(DEVROUTER_HOME, "controller");
15162
+ const directory = import_node_path44.default.join(DEVROUTER_HOME, "controller");
14793
15163
  const cancellation = { requested: false };
14794
15164
  const removeSignals = installLifecycleWaitSignals(cancellation);
14795
15165
  const cancelled = () => {
@@ -14846,7 +15216,7 @@ async function superviseThroughController(input2) {
14846
15216
  removeSignals();
14847
15217
  }
14848
15218
  }
14849
- function renewLifecycleCapacity(request2, policy, samples, controller, directory = import_node_path43.default.join(DEVROUTER_HOME, "controller"), nowMs = Date.now()) {
15219
+ function renewLifecycleCapacity(request2, policy, samples, controller, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller"), nowMs = Date.now()) {
14850
15220
  return updateReliabilityOperation(request2.identity, (record5) => {
14851
15221
  const binding = record5.capacity;
14852
15222
  if (!matchesFence(record5, request2.fence) || !binding || binding.operationId !== request2.operationId || binding.workerId !== request2.workerId || binding.controller?.store !== controller.store || binding.controller?.epoch !== controller.epoch)
@@ -14924,7 +15294,7 @@ function retireQueuedLifecycle(request2, supersededOnly = false) {
14924
15294
  return true;
14925
15295
  });
14926
15296
  }
14927
- function bindLifecycleCapacity(request2, binding, directory = import_node_path43.default.join(DEVROUTER_HOME, "controller")) {
15297
+ function bindLifecycleCapacity(request2, binding, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller")) {
14928
15298
  if (request2.kind === "stop") throw new Error("Stop never requires capacity admission.");
14929
15299
  updateReliabilityOperation(request2.identity, (record5) => {
14930
15300
  if (!matchesFence(record5, request2.fence) || record5.worker || record5.state.operation?.id !== request2.operationId || record5.state.operation.status !== "NOT_STARTED" || record5.state.operation.drained)
@@ -14939,12 +15309,12 @@ function bindLifecycleCapacity(request2, binding, directory = import_node_path43
14939
15309
  stepRecord(record5, { ...request2.fence, type: "admission", result: "admitted" });
14940
15310
  });
14941
15311
  }
14942
- var import_node_crypto21, import_node_path43, import_node_util10, activeWorker, lockHeld, BUSY_LIFECYCLE_WAIT_MS, BUSY_LIFECYCLE_POLL_MS, CAPACITY_CLIENT_WAIT_MS, BUSY_LIFECYCLE_PROGRESS_MS;
15312
+ var import_node_crypto21, import_node_path44, import_node_util10, activeWorker, lockHeld, BUSY_LIFECYCLE_WAIT_MS, BUSY_LIFECYCLE_POLL_MS, CAPACITY_CLIENT_WAIT_MS, BUSY_LIFECYCLE_PROGRESS_MS;
14943
15313
  var init_reliability_lifecycle = __esm({
14944
15314
  "src/core/reliability-lifecycle.ts"() {
14945
15315
  "use strict";
14946
15316
  import_node_crypto21 = require("crypto");
14947
- import_node_path43 = __toESM(require("path"));
15317
+ import_node_path44 = __toESM(require("path"));
14948
15318
  import_node_util10 = require("util");
14949
15319
  init_capacity_accounting();
14950
15320
  init_capacity_policy();
@@ -15431,7 +15801,7 @@ function createCapacityController(options) {
15431
15801
  const record5 = readReliabilityOperation(identity2);
15432
15802
  if (!record5?.capacity || !record5.preparation && !record5.capacity.execSteady || record5.worker || !record5.state.operation?.drained || !["ensure", "exec"].includes(record5.state.operation.kind))
15433
15803
  continue;
15434
- const bytes2 = readControllerEvidence(import_node_path44.default.join(enrollment.repoPath, ".devrouter.yml"));
15804
+ const bytes2 = readControllerEvidence(import_node_path45.default.join(enrollment.repoPath, ".devrouter.yml"));
15435
15805
  const estimates = loadRepoConfig(enrollment.repoPath, () => bytes2).capacity;
15436
15806
  if (!estimates) continue;
15437
15807
  settlePreparedLifecycleCapacity({
@@ -15591,12 +15961,12 @@ function createCapacityController(options) {
15591
15961
  }
15592
15962
  };
15593
15963
  }
15594
- var import_node_crypto22, import_node_path44, import_node_util12;
15964
+ var import_node_crypto22, import_node_path45, import_node_util12;
15595
15965
  var init_capacity_controller = __esm({
15596
15966
  "src/core/capacity-controller.ts"() {
15597
15967
  "use strict";
15598
15968
  import_node_crypto22 = require("crypto");
15599
- import_node_path44 = __toESM(require("path"));
15969
+ import_node_path45 = __toESM(require("path"));
15600
15970
  import_node_util12 = require("util");
15601
15971
  init_capacity_docker_probe();
15602
15972
  init_capacity_enrollment();
@@ -15937,12 +16307,12 @@ async function containers(project, signal) {
15937
16307
  }
15938
16308
  return values.sort((a, b) => a.id.localeCompare(b.id));
15939
16309
  }
15940
- var import_node_crypto24, import_node_path45, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
16310
+ var import_node_crypto24, import_node_path46, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
15941
16311
  var init_controller_observation = __esm({
15942
16312
  "src/core/controller-observation.ts"() {
15943
16313
  "use strict";
15944
16314
  import_node_crypto24 = require("crypto");
15945
- import_node_path45 = __toESM(require("path"));
16315
+ import_node_path46 = __toESM(require("path"));
15946
16316
  init_controller_binding();
15947
16317
  init_controller_monitor();
15948
16318
  init_controller_probe();
@@ -15985,14 +16355,14 @@ var init_controller_observation = __esm({
15985
16355
  )).trim().split("\n");
15986
16356
  if (metadata.length !== 2) throw new Error("Observation ownership paths unavailable.");
15987
16357
  const [gitDir, commonDir] = metadata;
15988
- const sourcePath = import_node_path45.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
16358
+ const sourcePath = import_node_path46.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
15989
16359
  const files = [
15990
- import_node_path45.default.join(commonDir, "devrouter/workspaces", `${environment.workspace}.json`),
15991
- import_node_path45.default.join(environment.repoPath, ".devrouter.yml"),
15992
- import_node_path45.default.join(gitDir, "devrouter-workspace"),
16360
+ import_node_path46.default.join(commonDir, "devrouter/workspaces", `${environment.workspace}.json`),
16361
+ import_node_path46.default.join(environment.repoPath, ".devrouter.yml"),
16362
+ import_node_path46.default.join(gitDir, "devrouter-workspace"),
15993
16363
  managedRuntimeStatePath(environment.repoPath, environment.workspace),
15994
16364
  sourcePath,
15995
- import_node_path45.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
16365
+ import_node_path46.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
15996
16366
  ];
15997
16367
  const persisted = captureControllerEvidence(files);
15998
16368
  if (controllerBindingFingerprint(persisted.contents[0], persisted.contents[1]) !== environment.fingerprint)
@@ -16017,7 +16387,7 @@ var init_controller_observation = __esm({
16017
16387
  if (!journal) throw new Error("Observation journal is unavailable.");
16018
16388
  const routes = readRoutes().filter((route) => route.repoPath === environment.repoPath);
16019
16389
  const before = await containers(state.composeProject, signal);
16020
- const composeDirectory = import_node_path45.default.join(environment.repoPath, ".devcontainer");
16390
+ const composeDirectory = import_node_path46.default.join(environment.repoPath, ".devcontainer");
16021
16391
  const references = typeof source.dockerComposeFile === "string" ? [source.dockerComposeFile] : source.dockerComposeFile;
16022
16392
  if (!Array.isArray(references) || !references.length || references.some((value) => typeof value !== "string"))
16023
16393
  throw new Error("Observation Compose source is unavailable.");
@@ -16034,7 +16404,7 @@ var init_controller_observation = __esm({
16034
16404
  if (!recorded || container.labels["com.docker.compose.project.working_dir"] !== composeDirectory)
16035
16405
  throw new Error("Observation Compose ownership is unavailable.");
16036
16406
  const files2 = recorded.split(",").map((file) => file.trim());
16037
- if (files2.some((file) => !import_node_path45.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
16407
+ if (files2.some((file) => !import_node_path46.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
16038
16408
  repoPath: environment.repoPath,
16039
16409
  service: container.labels["com.docker.compose.service"] ?? "",
16040
16410
  composeProject: state.composeProject,
@@ -16393,26 +16763,26 @@ var init_controller_sessions = __esm({
16393
16763
 
16394
16764
  // src/core/controller-server.ts
16395
16765
  function privateDirectory(directory) {
16396
- const absolute2 = import_node_path46.default.resolve(directory);
16397
- let current = import_node_path46.default.parse(absolute2).root;
16398
- for (const part of absolute2.slice(current.length).split(import_node_path46.default.sep)) {
16399
- current = import_node_path46.default.join(current, part);
16766
+ const absolute2 = import_node_path47.default.resolve(directory);
16767
+ let current = import_node_path47.default.parse(absolute2).root;
16768
+ for (const part of absolute2.slice(current.length).split(import_node_path47.default.sep)) {
16769
+ current = import_node_path47.default.join(current, part);
16400
16770
  try {
16401
- const stat2 = import_node_fs35.default.lstatSync(current);
16771
+ const stat2 = import_node_fs36.default.lstatSync(current);
16402
16772
  if (!stat2.isDirectory() || stat2.isSymbolicLink())
16403
16773
  throw new Error("Unsafe controller directory.");
16404
16774
  } catch (error) {
16405
16775
  if (error.code !== "ENOENT") throw error;
16406
- import_node_fs35.default.mkdirSync(current, { mode: 448 });
16776
+ import_node_fs36.default.mkdirSync(current, { mode: 448 });
16407
16777
  }
16408
16778
  }
16409
- const stat = import_node_fs35.default.lstatSync(absolute2);
16779
+ const stat = import_node_fs36.default.lstatSync(absolute2);
16410
16780
  if (stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
16411
16781
  throw new Error("Controller directory is not private.");
16412
16782
  }
16413
16783
  function validateOwnedFile(file, socket) {
16414
16784
  try {
16415
- const stat = import_node_fs35.default.lstatSync(file);
16785
+ const stat = import_node_fs36.default.lstatSync(file);
16416
16786
  if (stat.uid !== process.getuid?.() || (socket ? !stat.isSocket() : !stat.isFile()) || (stat.mode & 63) !== 0)
16417
16787
  throw new Error("Unsafe controller artifact.");
16418
16788
  return true;
@@ -16424,19 +16794,19 @@ function validateOwnedFile(file, socket) {
16424
16794
  async function runController(options) {
16425
16795
  if (options.operations && options.createOperations)
16426
16796
  throw new Error("Controller operations have multiple owners.");
16427
- const socketPath = import_node_path46.default.join(options.directory, "control.sock");
16797
+ const socketPath = import_node_path47.default.join(options.directory, "control.sock");
16428
16798
  if (Buffer.byteLength(socketPath) > 103) throw new Error("Controller socket path is too long.");
16429
16799
  privateDirectory(options.directory);
16430
- const lockPath = import_node_path46.default.join(options.directory, "owner.lock");
16800
+ const lockPath = import_node_path47.default.join(options.directory, "owner.lock");
16431
16801
  try {
16432
- const stat = import_node_fs35.default.lstatSync(lockPath);
16802
+ const stat = import_node_fs36.default.lstatSync(lockPath);
16433
16803
  if (!stat.isFile() || stat.uid !== process.getuid?.())
16434
16804
  throw new Error("Unsafe controller owner lock.");
16435
16805
  } catch (error) {
16436
16806
  if (error.code !== "ENOENT") throw error;
16437
16807
  }
16438
16808
  await withFileLock(lockPath, { activity: "controller ownership", waitMs: 0 }, async () => {
16439
- if (validateOwnedFile(socketPath, true)) import_node_fs35.default.unlinkSync(socketPath);
16809
+ if (validateOwnedFile(socketPath, true)) import_node_fs36.default.unlinkSync(socketPath);
16440
16810
  const sessions = new ControllerSessions(new ControllerStore(options.directory));
16441
16811
  const incarnation = sessions.read();
16442
16812
  let startupAvailable = true;
@@ -16815,7 +17185,7 @@ async function runController(options) {
16815
17185
  resolve();
16816
17186
  });
16817
17187
  });
16818
- import_node_fs35.default.chmodSync(socketPath, 384);
17188
+ import_node_fs36.default.chmodSync(socketPath, 384);
16819
17189
  await new Promise((resolve) => {
16820
17190
  finish = resolve;
16821
17191
  options.signal.addEventListener("abort", shutdown, { once: true });
@@ -16834,13 +17204,13 @@ async function runController(options) {
16834
17204
  }
16835
17205
  });
16836
17206
  }
16837
- var import_node_fs35, import_node_net4, import_node_path46, FRAME_BYTES;
17207
+ var import_node_fs36, import_node_net4, import_node_path47, FRAME_BYTES;
16838
17208
  var init_controller_server = __esm({
16839
17209
  "src/core/controller-server.ts"() {
16840
17210
  "use strict";
16841
- import_node_fs35 = __toESM(require("fs"));
17211
+ import_node_fs36 = __toESM(require("fs"));
16842
17212
  import_node_net4 = __toESM(require("net"));
16843
- import_node_path46 = __toESM(require("path"));
17213
+ import_node_path47 = __toESM(require("path"));
16844
17214
  init_controller_monitor();
16845
17215
  init_controller_protocol();
16846
17216
  init_controller_sessions();
@@ -16857,7 +17227,7 @@ __export(controller_exports, {
16857
17227
  runControllerCommand: () => runControllerCommand
16858
17228
  });
16859
17229
  async function runControllerCommand(method, options, repo, dependencies = {}) {
16860
- const directory = import_node_path47.default.join(DEVROUTER_HOME, "controller");
17230
+ const directory = import_node_path48.default.join(DEVROUTER_HOME, "controller");
16861
17231
  try {
16862
17232
  if (method === "run") {
16863
17233
  const controller = new AbortController();
@@ -16892,7 +17262,7 @@ async function runControllerCommand(method, options, repo, dependencies = {}) {
16892
17262
  if (fields2.timeout !== void 0) fields2.timeout = Number(fields2.timeout);
16893
17263
  await controllerRequest(
16894
17264
  directory,
16895
- { method, ...fields2, ...repo ? { path: import_node_path47.default.resolve(repo) } : {} },
17265
+ { method, ...fields2, ...repo ? { path: import_node_path48.default.resolve(repo) } : {} },
16896
17266
  (value) => process.stdout.write(`${JSON.stringify(value)}
16897
17267
  `)
16898
17268
  );
@@ -16907,11 +17277,11 @@ async function runControllerCommand(method, options, repo, dependencies = {}) {
16907
17277
  process.exitCode = 1;
16908
17278
  }
16909
17279
  }
16910
- var import_node_path47;
17280
+ var import_node_path48;
16911
17281
  var init_controller = __esm({
16912
17282
  "src/commands/controller.ts"() {
16913
17283
  "use strict";
16914
- import_node_path47 = __toESM(require("path"));
17284
+ import_node_path48 = __toESM(require("path"));
16915
17285
  init_capacity_collector();
16916
17286
  init_capacity_controller();
16917
17287
  init_capacity_policy();
@@ -16962,28 +17332,28 @@ function replaceDevrouterSection(content) {
16962
17332
  return `${content.slice(0, start)}${buildDevrouterSection()}${content.slice(end)}`;
16963
17333
  }
16964
17334
  function writeRepoFile(repoPath, relPath, content) {
16965
- const absolutePath = (0, import_node_path48.join)(repoPath, relPath);
16966
- (0, import_node_fs36.mkdirSync)((0, import_node_path48.dirname)(absolutePath), { recursive: true });
16967
- (0, import_node_fs36.writeFileSync)(absolutePath, content, "utf-8");
17335
+ const absolutePath = (0, import_node_path49.join)(repoPath, relPath);
17336
+ (0, import_node_fs37.mkdirSync)((0, import_node_path49.dirname)(absolutePath), { recursive: true });
17337
+ (0, import_node_fs37.writeFileSync)(absolutePath, content, "utf-8");
16968
17338
  return absolutePath;
16969
17339
  }
16970
17340
  function ensureAgentsMdSection(repoPath) {
16971
- const filePath = (0, import_node_path48.join)(repoPath, AGENTS_MD);
16972
- if ((0, import_node_fs36.existsSync)(filePath)) {
16973
- const content = (0, import_node_fs36.readFileSync)(filePath, "utf-8");
17341
+ const filePath = (0, import_node_path49.join)(repoPath, AGENTS_MD);
17342
+ if ((0, import_node_fs37.existsSync)(filePath)) {
17343
+ const content = (0, import_node_fs37.readFileSync)(filePath, "utf-8");
16974
17344
  if (content.includes(DEVROUTER_SENTINEL)) {
16975
17345
  const updated = replaceDevrouterSection(content);
16976
17346
  if (updated === void 0 || updated === content) return { path: filePath, written: false };
16977
- (0, import_node_fs36.writeFileSync)(filePath, updated, "utf-8");
17347
+ (0, import_node_fs37.writeFileSync)(filePath, updated, "utf-8");
16978
17348
  return { path: filePath, written: true };
16979
17349
  }
16980
- (0, import_node_fs36.writeFileSync)(filePath, `${content.trimEnd()}
17350
+ (0, import_node_fs37.writeFileSync)(filePath, `${content.trimEnd()}
16981
17351
 
16982
17352
  ${buildDevrouterSection()}
16983
17353
  `, "utf-8");
16984
17354
  return { path: filePath, written: true };
16985
17355
  }
16986
- (0, import_node_fs36.writeFileSync)(filePath, `# AGENTS.md
17356
+ (0, import_node_fs37.writeFileSync)(filePath, `# AGENTS.md
16987
17357
 
16988
17358
  ${buildDevrouterSection()}
16989
17359
  `, "utf-8");
@@ -16993,12 +17363,12 @@ function ensureSkillFile(repoPath) {
16993
17363
  const filePath = writeRepoFile(repoPath, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT);
16994
17364
  return { path: filePath, written: true };
16995
17365
  }
16996
- var import_node_fs36, import_node_path48, DEVROUTER_SENTINEL, DEVROUTER_END_SENTINEL, LEGACY_DEVROUTER_SECTION_END, AGENTS_MD, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT;
17366
+ var import_node_fs37, import_node_path49, DEVROUTER_SENTINEL, DEVROUTER_END_SENTINEL, LEGACY_DEVROUTER_SECTION_END, AGENTS_MD, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT;
16997
17367
  var init_agents_md = __esm({
16998
17368
  "src/core/agents-md.ts"() {
16999
17369
  "use strict";
17000
- import_node_fs36 = require("fs");
17001
- import_node_path48 = require("path");
17370
+ import_node_fs37 = require("fs");
17371
+ import_node_path49 = require("path");
17002
17372
  DEVROUTER_SENTINEL = "<!-- devrouter -->";
17003
17373
  DEVROUTER_END_SENTINEL = "<!-- /devrouter -->";
17004
17374
  LEGACY_DEVROUTER_SECTION_END = "- `devrouter ls`";
@@ -17459,6 +17829,7 @@ function buildOnboardingPrompt(options = {}) {
17459
17829
  "- if kind=app and runtime=proxy:",
17460
17830
  " - HTTP-only optional readiness: { path: /api/health, statuses: [200], contentType: application/json }; statuses default to [200], explicit lists allow unique 2xx/4xx codes, never redirects. Paths stay on the configured host with no query, fragment, percent escapes, backslash or dot segments. Media types match their case-insensitive base type.",
17461
17831
  " - Declared application readiness failures return applicationReadiness.status=application-error and a nonzero ensure exit, while retaining tooling and routes for application fixes; do not reset caches or recreate the provider. Absent readiness means legacy route liveness, not full application readiness.",
17832
+ " - A configured FIXED published host binding already held by a running container refuses managed ensure before any provider start (hostPortConflicts in --json, nonzero exit) naming the port, holder container, compose project, and owning workspace when attributable. Ephemeral host::container bindings are exempt. Never rewrite consumer ports to resolve a conflict: stop the holding workspace or use the consumer's own override, then ensure again. Unverifiable evidence also refuses.",
17462
17833
  ' - upstream: "host:port" (an already-running port, e.g. a devcontainer published on 127.0.0.1:3000, or a container reachable by name on a shared Docker network such as `derivatives-db:5432`)',
17463
17834
  ` - upstream may use the \`${WORKSPACE_PLACEHOLDER}\` placeholder (e.g. \`${WORKSPACE_PLACEHOLDER}-app:3000\`) to target a per-workspace devcontainer alias; it is substituted with the resolved workspace token at runtime and re-validated. Do NOT put \`${WORKSPACE_PLACEHOLDER}\` in \`host\` (rejected) \u2014 the host is auto-namespaced.`,
17464
17835
  " - managed `devrouter ensure` requires every HTTP/TCP proxy upstream to begin with the exact resolved workspace/project alias prefix before it mutates DevPod or routes",
@@ -17565,6 +17936,7 @@ function buildOnboardingPrompt(options = {}) {
17565
17936
  "- Avoid pre-wrapper DB assignments such as `DATABASE_URI=... <wrapper> run -- ...`; wrapper-managed env may override those values.",
17566
17937
  "- `devrouter doctor --repo <REPO_PATH>` warns on risky pre-wrapper DB assignments before `run --` for host apps that depend on postgres.",
17567
17938
  "- With TLS enabled, `devrouter doctor --repo <REPO_PATH>` also warns on cert SAN mismatches for configured hosts (`repo.tls-host-coverage`).",
17939
+ "- `devrouter doctor --repo <REPO_PATH>` errors on configured fixed host-port bindings already held by running containers (`repo.host-port-claims`) and warns when that comparison is unavailable.",
17568
17940
  "- Robust one-shot migrate example (argv-safe, no nested shell quoting): `devrouter app exec <name> --repo <REPO_PATH> --yes -- infisical run --projectId <id> --env=<env> -- pnpm payload migrate`.",
17569
17941
  "- Robust one-shot seed example: `devrouter app exec <name> --repo <REPO_PATH> --yes -- infisical run --projectId <id> --env=<env> -- pnpm payload seed`.",
17570
17942
  "- Environment probe/debug example (run before migrations): `devrouter app exec <name> --repo <REPO_PATH> --yes -- printenv DB_URL DB_HOST DB_PORT DB_SHADOW_URL`.",
@@ -18275,11 +18647,11 @@ function extractCurrentVersionFromRepoConfig(repoPath) {
18275
18647
  };
18276
18648
  }
18277
18649
  function readPromptDirectory(promptDirPath) {
18278
- if (!import_node_fs37.default.existsSync(promptDirPath)) {
18650
+ if (!import_node_fs38.default.existsSync(promptDirPath)) {
18279
18651
  throw new Error(`Missing ${UPGRADE_PROMPTS_DIR} directory at ${promptDirPath}.`);
18280
18652
  }
18281
18653
  const releases = [];
18282
- const files = import_node_fs37.default.readdirSync(promptDirPath, { withFileTypes: true });
18654
+ const files = import_node_fs38.default.readdirSync(promptDirPath, { withFileTypes: true });
18283
18655
  for (const file of files) {
18284
18656
  if (!file.isFile() || !file.name.endsWith(".md")) {
18285
18657
  continue;
@@ -18289,8 +18661,8 @@ function readPromptDirectory(promptDirPath) {
18289
18661
  continue;
18290
18662
  }
18291
18663
  const version = normalizeVersion(basename);
18292
- const promptPath = import_node_path49.default.join(promptDirPath, file.name);
18293
- const prompt = import_node_fs37.default.readFileSync(promptPath, "utf-8").trim();
18664
+ const promptPath = import_node_path50.default.join(promptDirPath, file.name);
18665
+ const prompt = import_node_fs38.default.readFileSync(promptPath, "utf-8").trim();
18294
18666
  releases.push({ version, prompt, promptPath });
18295
18667
  }
18296
18668
  const deduped = /* @__PURE__ */ new Map();
@@ -18311,19 +18683,19 @@ function listAvailableUpgradeTargets(currentVersion, releases) {
18311
18683
  }
18312
18684
  function resolvePromptDirectory(explicitPath) {
18313
18685
  if (explicitPath) {
18314
- return import_node_path49.default.resolve(explicitPath);
18686
+ return import_node_path50.default.resolve(explicitPath);
18315
18687
  }
18316
- const entryFile = process.argv[1] ? import_node_path49.default.resolve(process.argv[1]) : __filename;
18317
- const entryDir = import_node_path49.default.dirname(entryFile);
18318
- const resolvedEntryDir = import_node_fs37.default.existsSync(entryFile) ? import_node_path49.default.dirname(import_node_fs37.default.realpathSync(entryFile)) : entryDir;
18688
+ const entryFile = process.argv[1] ? import_node_path50.default.resolve(process.argv[1]) : __filename;
18689
+ const entryDir = import_node_path50.default.dirname(entryFile);
18690
+ const resolvedEntryDir = import_node_fs38.default.existsSync(entryFile) ? import_node_path50.default.dirname(import_node_fs38.default.realpathSync(entryFile)) : entryDir;
18319
18691
  const candidates = [
18320
- import_node_path49.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
18321
- import_node_path49.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
18322
- import_node_path49.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
18323
- import_node_path49.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
18324
- import_node_path49.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
18692
+ import_node_path50.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
18693
+ import_node_path50.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
18694
+ import_node_path50.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
18695
+ import_node_path50.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
18696
+ import_node_path50.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
18325
18697
  ];
18326
- const existing = candidates.find((candidate) => import_node_fs37.default.existsSync(candidate));
18698
+ const existing = candidates.find((candidate) => import_node_fs38.default.existsSync(candidate));
18327
18699
  return existing ?? candidates[0];
18328
18700
  }
18329
18701
  function loadUpgradeCatalog(options = {}) {
@@ -18339,12 +18711,12 @@ function loadUpgradeCatalog(options = {}) {
18339
18711
  releases
18340
18712
  };
18341
18713
  }
18342
- var import_node_fs37, import_node_path49, SEMVER_RE, UPGRADE_PROMPTS_DIR;
18714
+ var import_node_fs38, import_node_path50, SEMVER_RE, UPGRADE_PROMPTS_DIR;
18343
18715
  var init_upgrade = __esm({
18344
18716
  "src/core/upgrade.ts"() {
18345
18717
  "use strict";
18346
- import_node_fs37 = __toESM(require("fs"));
18347
- import_node_path49 = __toESM(require("path"));
18718
+ import_node_fs38 = __toESM(require("fs"));
18719
+ import_node_path50 = __toESM(require("path"));
18348
18720
  init_repo_config();
18349
18721
  SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
18350
18722
  UPGRADE_PROMPTS_DIR = "upgrade-prompts";
@@ -18487,7 +18859,7 @@ function isLoopbackHost(host) {
18487
18859
  return host === "127.0.0.1" || host === "localhost" || host === "host.docker.internal";
18488
18860
  }
18489
18861
  function inspectCompose(composeFile, _workspace) {
18490
- if (!import_node_fs38.default.existsSync(composeFile)) {
18862
+ if (!import_node_fs39.default.existsSync(composeFile)) {
18491
18863
  return {
18492
18864
  aliases: [],
18493
18865
  publishedPorts: [],
@@ -18496,7 +18868,7 @@ function inspectCompose(composeFile, _workspace) {
18496
18868
  };
18497
18869
  }
18498
18870
  try {
18499
- const raw = import_node_fs38.default.readFileSync(composeFile, "utf-8");
18871
+ const raw = import_node_fs39.default.readFileSync(composeFile, "utf-8");
18500
18872
  const parsed = import_yaml5.default.parse(raw);
18501
18873
  const root = asRecord(parsed);
18502
18874
  const services = asRecord(root?.services);
@@ -18552,12 +18924,51 @@ function routedProxyApps(config) {
18552
18924
  (app) => app.kind !== "dependency" && app.runtime === "proxy"
18553
18925
  );
18554
18926
  }
18927
+ function buildHostPortClaimsCheck(repoPath, workspace) {
18928
+ try {
18929
+ const linked = isLinkedWorktree(repoPath);
18930
+ const runtime = loadRuntimeConfig(repoPath);
18931
+ const plan = inspectManagedDevcontainerConfig({
18932
+ repoPath,
18933
+ config: runtime.config,
18934
+ profile: runtime.resolvedProfile,
18935
+ linked
18936
+ });
18937
+ const gitCommonDir = linked ? resolveGitCommonDir(repoPath) : void 0;
18938
+ const workspaceEnv = linked && workspace && gitCommonDir ? { token: workspace, gitCommonDir } : void 0;
18939
+ const conflicts = detectHostPortClaimConflicts({ repoPath, plan, workspace: workspaceEnv });
18940
+ if (conflicts.length === 0) {
18941
+ return {
18942
+ id: "repo.host-port-claims",
18943
+ level: "ok",
18944
+ summary: "No configured fixed host-port binding conflicts with a running container."
18945
+ };
18946
+ }
18947
+ return {
18948
+ id: "repo.host-port-claims",
18949
+ level: "error",
18950
+ summary: `${conflicts.length} configured fixed host-port binding${conflicts.length === 1 ? "" : "s"} conflict${conflicts.length === 1 ? "s" : ""} with running containers.`,
18951
+ details: conflicts.map(
18952
+ (conflict) => `${conflict.service}: ${conflict.hostIp ?? "*"}:${conflict.hostPort}/${conflict.protocol} held by '${conflict.holderContainer}'` + (conflict.holderWorkspace ? ` (workspace '${conflict.holderWorkspace}')` : "")
18953
+ ).join("; "),
18954
+ suggestion: "Stop the holding workspace or change the consumer's own published binding, then run ensure again. Devrouter never rewrites consumer-declared host bindings."
18955
+ };
18956
+ } catch (error) {
18957
+ return {
18958
+ id: "repo.host-port-claims",
18959
+ level: "warn",
18960
+ summary: "Could not compare configured fixed host-port bindings against live holders.",
18961
+ details: error instanceof Error ? error.message : String(error),
18962
+ suggestion: "Check Docker availability; a managed ensure refuses on this evidence before starting."
18963
+ };
18964
+ }
18965
+ }
18555
18966
  function buildDevcontainerChecks(repoPath, config, workspace) {
18556
- const devcontainerDir = import_node_path50.default.join(repoPath, ".devcontainer");
18557
- if (!import_node_fs38.default.existsSync(devcontainerDir)) {
18967
+ const devcontainerDir = import_node_path51.default.join(repoPath, ".devcontainer");
18968
+ if (!import_node_fs39.default.existsSync(devcontainerDir)) {
18558
18969
  return [];
18559
18970
  }
18560
- const compose = inspectCompose(import_node_path50.default.join(devcontainerDir, "docker-compose.yml"), workspace);
18971
+ const compose = inspectCompose(import_node_path51.default.join(devcontainerDir, "docker-compose.yml"), workspace);
18561
18972
  const checks = [];
18562
18973
  if (compose.parseError) {
18563
18974
  checks.push({
@@ -18584,9 +18995,9 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
18584
18995
  details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
18585
18996
  suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
18586
18997
  });
18587
- const dockerfilePath = import_node_path50.default.join(devcontainerDir, "Dockerfile");
18588
- const dockerfileExists = import_node_fs38.default.existsSync(dockerfilePath);
18589
- const dockerfile = dockerfileExists ? import_node_fs38.default.readFileSync(dockerfilePath, "utf-8") : "";
18998
+ const dockerfilePath = import_node_path51.default.join(devcontainerDir, "Dockerfile");
18999
+ const dockerfileExists = import_node_fs39.default.existsSync(dockerfilePath);
19000
+ const dockerfile = dockerfileExists ? import_node_fs39.default.readFileSync(dockerfilePath, "utf-8") : "";
18590
19001
  const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
18591
19002
  (artifact) => dockerfile.includes(artifact)
18592
19003
  );
@@ -18623,6 +19034,9 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
18623
19034
  });
18624
19035
  return checks;
18625
19036
  }
19037
+ if (config.managedRuntime) {
19038
+ checks.push(buildHostPortClaimsCheck(repoPath, workspace));
19039
+ }
18626
19040
  const aliasSet = new Set(
18627
19041
  compose.aliases.flatMap((alias) => expandAliasCandidates(alias, workspace)).map(normalizeWorkspaceToken)
18628
19042
  );
@@ -18641,14 +19055,19 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
18641
19055
  });
18642
19056
  return checks;
18643
19057
  }
18644
- var import_node_fs38, import_node_path50, import_yaml5;
19058
+ var import_node_fs39, import_node_path51, import_yaml5;
18645
19059
  var init_devcontainer_diagnostics = __esm({
18646
19060
  "src/core/devcontainer-diagnostics.ts"() {
18647
19061
  "use strict";
18648
- import_node_fs38 = __toESM(require("fs"));
18649
- import_node_path50 = __toESM(require("path"));
19062
+ import_node_fs39 = __toESM(require("fs"));
19063
+ import_node_path51 = __toESM(require("path"));
18650
19064
  import_yaml5 = __toESM(require("yaml"));
19065
+ init_devcontainer_profile();
19066
+ init_host_port_claims();
18651
19067
  init_managed_post_start();
19068
+ init_repo_config();
19069
+ init_workspace();
19070
+ init_workspace_ownership();
18652
19071
  }
18653
19072
  });
18654
19073
 
@@ -18736,11 +19155,11 @@ function reconcileRouteRunConflict(repoPath, app) {
18736
19155
  }
18737
19156
  }
18738
19157
  }
18739
- var import_node_fs39;
19158
+ var import_node_fs40;
18740
19159
  var init_route_state = __esm({
18741
19160
  "src/core/route-state.ts"() {
18742
19161
  "use strict";
18743
- import_node_fs39 = __toESM(require("fs"));
19162
+ import_node_fs40 = __toESM(require("fs"));
18744
19163
  init_host_routes();
18745
19164
  init_workspace();
18746
19165
  }
@@ -18915,7 +19334,7 @@ function toRepoStatus(repoPath) {
18915
19334
  const resolvedRepoPath = resolveRepoPath(repoPath);
18916
19335
  const configPath = getRepoConfigPath(resolvedRepoPath);
18917
19336
  const explicitRepo = typeof repoPath === "string" && repoPath.trim().length > 0;
18918
- const configExists = import_node_fs40.default.existsSync(configPath);
19337
+ const configExists = import_node_fs41.default.existsSync(configPath);
18919
19338
  if (!explicitRepo && !configExists) {
18920
19339
  return void 0;
18921
19340
  }
@@ -19063,11 +19482,11 @@ async function collectRouterStatus(repoPath) {
19063
19482
  }
19064
19483
  };
19065
19484
  }
19066
- var import_node_fs40;
19485
+ var import_node_fs41;
19067
19486
  var init_status = __esm({
19068
19487
  "src/core/status.ts"() {
19069
19488
  "use strict";
19070
- import_node_fs40 = __toESM(require("fs"));
19489
+ import_node_fs41 = __toESM(require("fs"));
19071
19490
  init_docker();
19072
19491
  init_managed_runtime_state();
19073
19492
  init_managed_runtime_status();
@@ -19084,7 +19503,7 @@ function outputFromResult(result) {
19084
19503
  return output2.length > 0 ? output2 : void 0;
19085
19504
  }
19086
19505
  function runTool(command, args = []) {
19087
- const result = (0, import_node_child_process28.spawnSync)(command, args, {
19506
+ const result = (0, import_node_child_process29.spawnSync)(command, args, {
19088
19507
  encoding: "utf-8"
19089
19508
  });
19090
19509
  if (result.error) {
@@ -19141,12 +19560,12 @@ function parseMinimumNodeMajor(value) {
19141
19560
  return Number(match[1]);
19142
19561
  }
19143
19562
  function readPackageJson(repoPath) {
19144
- const packagePath = import_node_path51.default.join(repoPath, "package.json");
19145
- if (!import_node_fs41.default.existsSync(packagePath)) {
19563
+ const packagePath = import_node_path52.default.join(repoPath, "package.json");
19564
+ if (!import_node_fs42.default.existsSync(packagePath)) {
19146
19565
  return void 0;
19147
19566
  }
19148
19567
  try {
19149
- return JSON.parse(import_node_fs41.default.readFileSync(packagePath, "utf-8"));
19568
+ return JSON.parse(import_node_fs42.default.readFileSync(packagePath, "utf-8"));
19150
19569
  } catch {
19151
19570
  return void 0;
19152
19571
  }
@@ -19330,13 +19749,13 @@ function buildGlobalToolChecksWithoutTelemetry(repoPath) {
19330
19749
  checks.push(nodeToolchainCheck(repoPath));
19331
19750
  return checks;
19332
19751
  }
19333
- var import_node_child_process28, import_node_fs41, import_node_path51, DEVSY_DISABLE_TELEMETRY;
19752
+ var import_node_child_process29, import_node_fs42, import_node_path52, DEVSY_DISABLE_TELEMETRY;
19334
19753
  var init_tool_diagnostics = __esm({
19335
19754
  "src/core/tool-diagnostics.ts"() {
19336
19755
  "use strict";
19337
- import_node_child_process28 = require("child_process");
19338
- import_node_fs41 = __toESM(require("fs"));
19339
- import_node_path51 = __toESM(require("path"));
19756
+ import_node_child_process29 = require("child_process");
19757
+ import_node_fs42 = __toESM(require("fs"));
19758
+ import_node_path52 = __toESM(require("path"));
19340
19759
  init_devsy_agent();
19341
19760
  init_workspace_runtime();
19342
19761
  DEVSY_DISABLE_TELEMETRY = "DEVSY_DISABLE_TELEMETRY";
@@ -19353,10 +19772,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
19353
19772
  if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
19354
19773
  const comparableRepo = comparableWorkspacePath(repoPath);
19355
19774
  const comparableWorktree = comparableWorkspacePath(worktreePath);
19356
- const localRoot = import_node_path52.default.join(comparableRepo, "trees") + import_node_path52.default.sep;
19775
+ const localRoot = import_node_path53.default.join(comparableRepo, "trees") + import_node_path53.default.sep;
19357
19776
  if (comparableWorktree.startsWith(localRoot)) return true;
19358
- const legacyPrefix = `${import_node_path52.default.basename(comparableRepo)}-`;
19359
- return import_node_path52.default.dirname(comparableWorktree) === import_node_path52.default.dirname(comparableRepo) && import_node_path52.default.basename(comparableWorktree).startsWith(legacyPrefix);
19777
+ const legacyPrefix = `${import_node_path53.default.basename(comparableRepo)}-`;
19778
+ return import_node_path53.default.dirname(comparableWorktree) === import_node_path53.default.dirname(comparableRepo) && import_node_path53.default.basename(comparableWorktree).startsWith(legacyPrefix);
19360
19779
  }
19361
19780
  function previewActions(devpodStatus, routeCount, includeRecord) {
19362
19781
  const actions = [
@@ -19608,11 +20027,11 @@ function applyWorkspaceGc(plan) {
19608
20027
  candidates
19609
20028
  };
19610
20029
  }
19611
- var import_node_path52;
20030
+ var import_node_path53;
19612
20031
  var init_workspace_gc = __esm({
19613
20032
  "src/core/workspace-gc.ts"() {
19614
20033
  "use strict";
19615
- import_node_path52 = __toESM(require("path"));
20034
+ import_node_path53 = __toESM(require("path"));
19616
20035
  init_devpod_mutation();
19617
20036
  init_devpod_workspaces();
19618
20037
  init_host_routes();
@@ -19682,11 +20101,11 @@ function inspectPostgresCredentials(repoPath, config) {
19682
20101
  parseErrors.push(`${app.name}: ${composeFile} (${message})`);
19683
20102
  continue;
19684
20103
  }
19685
- if (!import_node_fs42.default.existsSync(absolutePath)) {
20104
+ if (!import_node_fs43.default.existsSync(absolutePath)) {
19686
20105
  continue;
19687
20106
  }
19688
20107
  try {
19689
- const raw = import_node_fs42.default.readFileSync(absolutePath, "utf-8");
20108
+ const raw = import_node_fs43.default.readFileSync(absolutePath, "utf-8");
19690
20109
  const parsed = import_yaml6.default.parse(raw);
19691
20110
  const root = asRecord2(parsed);
19692
20111
  const services = asRecord2(root?.services);
@@ -19966,7 +20385,7 @@ async function buildDoctorReport(options = {}) {
19966
20385
  const config = runtimeConfig.config;
19967
20386
  loadedConfig = config;
19968
20387
  loadedWorkspace = runtimeConfig.workspace;
19969
- const cliVersion = true ? "0.0.68" : "0.0.0-dev";
20388
+ const cliVersion = true ? "0.0.69" : "0.0.0-dev";
19970
20389
  const configVersion = config.devrouter?.version;
19971
20390
  if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
19972
20391
  addCheck(checks, {
@@ -19996,9 +20415,9 @@ async function buildDoctorReport(options = {}) {
19996
20415
  (app) => app.docker.composeFiles.map((filePath) => ({
19997
20416
  app: app.name,
19998
20417
  filePath,
19999
- absolutePath: import_node_path53.default.resolve(repo.path, filePath)
20418
+ absolutePath: import_node_path54.default.resolve(repo.path, filePath)
20000
20419
  }))
20001
- ).filter((entry) => !import_node_fs42.default.existsSync(entry.absolutePath));
20420
+ ).filter((entry) => !import_node_fs43.default.existsSync(entry.absolutePath));
20002
20421
  addCheck(checks, {
20003
20422
  id: "repo.compose-files",
20004
20423
  level: missingComposeFiles.length === 0 ? "ok" : "error",
@@ -20041,8 +20460,8 @@ async function buildDoctorReport(options = {}) {
20041
20460
  const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
20042
20461
  app: app.name,
20043
20462
  cwd: app.hostRun.cwd,
20044
- absolutePath: import_node_path53.default.resolve(repo.path, app.hostRun.cwd)
20045
- })).filter((entry) => !import_node_fs42.default.existsSync(entry.absolutePath));
20463
+ absolutePath: import_node_path54.default.resolve(repo.path, app.hostRun.cwd)
20464
+ })).filter((entry) => !import_node_fs43.default.existsSync(entry.absolutePath));
20046
20465
  addCheck(checks, {
20047
20466
  id: "repo.host-cwd",
20048
20467
  level: missingHostCwds.length === 0 ? "ok" : "error",
@@ -20186,12 +20605,12 @@ async function buildDoctorReport(options = {}) {
20186
20605
  nextSteps
20187
20606
  };
20188
20607
  }
20189
- var import_node_fs42, import_node_path53, import_yaml6, POSTGRES_DEFAULTS;
20608
+ var import_node_fs43, import_node_path54, import_yaml6, POSTGRES_DEFAULTS;
20190
20609
  var init_doctor = __esm({
20191
20610
  "src/core/doctor.ts"() {
20192
20611
  "use strict";
20193
- import_node_fs42 = __toESM(require("fs"));
20194
- import_node_path53 = __toESM(require("path"));
20612
+ import_node_fs43 = __toESM(require("fs"));
20613
+ import_node_path54 = __toESM(require("path"));
20195
20614
  import_yaml6 = __toESM(require("yaml"));
20196
20615
  init_docker();
20197
20616
  init_host_routes();
@@ -20507,12 +20926,12 @@ function parseSsPortListeners(stdout, port) {
20507
20926
  return listeners;
20508
20927
  }
20509
20928
  function findPortListeners(port) {
20510
- const result = (0, import_node_child_process29.spawnSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
20929
+ const result = (0, import_node_child_process30.spawnSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
20511
20930
  encoding: "utf-8"
20512
20931
  });
20513
20932
  if (result.error && result.error.code === "ENOENT") {
20514
20933
  if (process.platform === "linux") {
20515
- const ssResult = (0, import_node_child_process29.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
20934
+ const ssResult = (0, import_node_child_process30.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
20516
20935
  if (ssResult.status === 0 && ssResult.stdout) {
20517
20936
  return parseSsPortListeners(ssResult.stdout, port);
20518
20937
  }
@@ -20541,11 +20960,11 @@ function findPortListeners(port) {
20541
20960
  };
20542
20961
  });
20543
20962
  }
20544
- var import_node_child_process29;
20963
+ var import_node_child_process30;
20545
20964
  var init_ports = __esm({
20546
20965
  "src/util/ports.ts"() {
20547
20966
  "use strict";
20548
- import_node_child_process29 = require("child_process");
20967
+ import_node_child_process30 = require("child_process");
20549
20968
  }
20550
20969
  });
20551
20970
 
@@ -20632,14 +21051,33 @@ async function runEnsureCommand(options) {
20632
21051
  profile: options.profile,
20633
21052
  ...options.repair ? { repair: true } : {}
20634
21053
  });
21054
+ const conflicts = result.hostPortConflicts ?? [];
20635
21055
  const applicationFailed = result.applicationReadiness?.status === "application-error";
20636
- if (applicationFailed) process.exitCode = 1;
21056
+ if (applicationFailed || conflicts.length > 0) process.exitCode = 1;
20637
21057
  if (options.json) {
20638
21058
  process.stdout.write(`${JSON.stringify(result, null, 2)}
20639
21059
  `);
20640
21060
  return;
20641
21061
  }
20642
21062
  const label2 = result.kind === "primary" ? `Primary checkout [profile: ${result.profile}]` : `Workspace '${result.workspace}' [profile: ${result.profile}]`;
21063
+ if (conflicts.length > 0) {
21064
+ const lines = conflicts.map((conflict) => {
21065
+ const desired = `${conflict.hostIp ?? "*"}:${conflict.hostPort}/${conflict.protocol}`;
21066
+ const holder = [
21067
+ `'${conflict.holderContainer}'`,
21068
+ conflict.holderComposeProject ? `compose project '${conflict.holderComposeProject}'` : void 0,
21069
+ conflict.holderWorkspace ? `workspace '${conflict.holderWorkspace}'` : void 0
21070
+ ].filter(Boolean).join(", ");
21071
+ return ` - service '${conflict.service}' needs ${desired}, held by ${holder}
21072
+ ${conflict.remediation}`;
21073
+ });
21074
+ process.stdout.write(
21075
+ `${label2} was not started: ${conflicts.length} fixed host-port claim${conflicts.length === 1 ? "" : "s"} conflict${conflicts.length === 1 ? "s" : ""} with running containers.
21076
+ ${lines.join("\n")}
21077
+ `
21078
+ );
21079
+ return;
21080
+ }
20643
21081
  const routes = result.urls.map((url) => ` ${url}`).join("\n");
20644
21082
  process.stdout.write(
20645
21083
  `${label2} ${applicationFailed ? "has available infrastructure but failed its application readiness contract" : "is ready"} (${result.devpodId}).
@@ -20891,7 +21329,7 @@ async function runOpenCommand(name) {
20891
21329
  );
20892
21330
  return;
20893
21331
  }
20894
- const result = (0, import_node_child_process30.spawnSync)("open", [url], { encoding: "utf-8" });
21332
+ const result = (0, import_node_child_process31.spawnSync)("open", [url], { encoding: "utf-8" });
20895
21333
  if (result.status !== 0) {
20896
21334
  const details = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
20897
21335
  throw new Error(`Unable to open '${url}': ${details || "unknown error"}`);
@@ -20899,11 +21337,11 @@ async function runOpenCommand(name) {
20899
21337
  process.stdout.write(`Opened ${url}
20900
21338
  `);
20901
21339
  }
20902
- var import_node_child_process30;
21340
+ var import_node_child_process31;
20903
21341
  var init_open = __esm({
20904
21342
  "src/commands/open.ts"() {
20905
21343
  "use strict";
20906
- import_node_child_process30 = require("child_process");
21344
+ import_node_child_process31 = require("child_process");
20907
21345
  init_docker();
20908
21346
  init_host_routes();
20909
21347
  init_repo_config();
@@ -20926,7 +21364,7 @@ async function runLogsCommand(options) {
20926
21364
  const args = ["logs", "--tail", tail, ROUTER_CONTAINER_NAME];
20927
21365
  if (options.follow) {
20928
21366
  args.splice(1, 0, "-f");
20929
- const child = (0, import_node_child_process31.spawn)("docker", args, { stdio: "inherit" });
21367
+ const child = (0, import_node_child_process32.spawn)("docker", args, { stdio: "inherit" });
20930
21368
  const onSignal = () => {
20931
21369
  child.kill("SIGTERM");
20932
21370
  };
@@ -20940,17 +21378,17 @@ async function runLogsCommand(options) {
20940
21378
  });
20941
21379
  });
20942
21380
  } else {
20943
- const result = (0, import_node_child_process31.spawnSync)("docker", args, { stdio: "inherit" });
21381
+ const result = (0, import_node_child_process32.spawnSync)("docker", args, { stdio: "inherit" });
20944
21382
  if (result.status !== 0) {
20945
21383
  throw new Error("Failed to retrieve router logs.");
20946
21384
  }
20947
21385
  }
20948
21386
  }
20949
- var import_node_child_process31;
21387
+ var import_node_child_process32;
20950
21388
  var init_logs = __esm({
20951
21389
  "src/commands/logs.ts"() {
20952
21390
  "use strict";
20953
- import_node_child_process31 = require("child_process");
21391
+ import_node_child_process32 = require("child_process");
20954
21392
  init_docker();
20955
21393
  init_router();
20956
21394
  }
@@ -20990,17 +21428,17 @@ function asRecord3(value) {
20990
21428
  return value;
20991
21429
  }
20992
21430
  function readJson(filePath) {
20993
- if (!import_node_fs43.default.existsSync(filePath)) {
21431
+ if (!import_node_fs44.default.existsSync(filePath)) {
20994
21432
  return void 0;
20995
21433
  }
20996
21434
  try {
20997
- return JSON.parse(import_node_fs43.default.readFileSync(filePath, "utf-8"));
21435
+ return JSON.parse(import_node_fs44.default.readFileSync(filePath, "utf-8"));
20998
21436
  } catch {
20999
21437
  return void 0;
21000
21438
  }
21001
21439
  }
21002
21440
  function relative(repoPath, filePath) {
21003
- return import_node_path54.default.relative(repoPath, filePath) || ".";
21441
+ return import_node_path55.default.relative(repoPath, filePath) || ".";
21004
21442
  }
21005
21443
  function redactEnvAssignments(value) {
21006
21444
  return value.replace(
@@ -21041,7 +21479,7 @@ function inspectPackageManager(repoPath, pkg) {
21041
21479
  ["bun.lock", "bun"]
21042
21480
  ];
21043
21481
  for (const [fileName, name] of lockfiles) {
21044
- if (import_node_fs43.default.existsSync(import_node_path54.default.join(repoPath, fileName))) {
21482
+ if (import_node_fs44.default.existsSync(import_node_path55.default.join(repoPath, fileName))) {
21045
21483
  return { name, source: fileName };
21046
21484
  }
21047
21485
  }
@@ -21056,9 +21494,9 @@ function inspectNode(repoPath, pkg) {
21056
21494
  if (typeof engines?.node === "string") {
21057
21495
  return { version: engines.node, source: "package.json:engines.node" };
21058
21496
  }
21059
- const nvmrc = import_node_path54.default.join(repoPath, ".nvmrc");
21060
- if (import_node_fs43.default.existsSync(nvmrc)) {
21061
- const version = import_node_fs43.default.readFileSync(nvmrc, "utf-8").trim();
21497
+ const nvmrc = import_node_path55.default.join(repoPath, ".nvmrc");
21498
+ if (import_node_fs44.default.existsSync(nvmrc)) {
21499
+ const version = import_node_fs44.default.readFileSync(nvmrc, "utf-8").trim();
21062
21500
  return { version, source: ".nvmrc" };
21063
21501
  }
21064
21502
  return void 0;
@@ -21119,7 +21557,7 @@ function configuredComposeFiles(repoPath) {
21119
21557
  const files = config.apps.filter(
21120
21558
  (app) => app.runtime === "docker"
21121
21559
  ).flatMap((app) => app.docker.composeFiles).filter(
21122
- (fileName) => !import_node_path54.default.isAbsolute(fileName) && !import_node_path54.default.normalize(fileName).startsWith("..")
21560
+ (fileName) => !import_node_path55.default.isAbsolute(fileName) && !import_node_path55.default.normalize(fileName).startsWith("..")
21123
21561
  );
21124
21562
  return Array.from(new Set(files));
21125
21563
  } catch {
@@ -21137,7 +21575,7 @@ function composeFiles(repoPath) {
21137
21575
  ...configuredComposeFiles(repoPath)
21138
21576
  ];
21139
21577
  return Array.from(new Set(candidates)).filter(
21140
- (fileName) => import_node_fs43.default.existsSync(import_node_path54.default.join(repoPath, fileName))
21578
+ (fileName) => import_node_fs44.default.existsSync(import_node_path55.default.join(repoPath, fileName))
21141
21579
  );
21142
21580
  }
21143
21581
  function stringArray2(value) {
@@ -21175,7 +21613,7 @@ function inspectServices(repoPath) {
21175
21613
  const services = [];
21176
21614
  for (const fileName of composeFiles(repoPath)) {
21177
21615
  try {
21178
- const parsed = import_yaml7.default.parse(import_node_fs43.default.readFileSync(import_node_path54.default.join(repoPath, fileName), "utf-8"));
21616
+ const parsed = import_yaml7.default.parse(import_node_fs44.default.readFileSync(import_node_path55.default.join(repoPath, fileName), "utf-8"));
21179
21617
  const serviceMap = asRecord3(asRecord3(parsed)?.services);
21180
21618
  for (const [name, value] of Object.entries(serviceMap ?? {})) {
21181
21619
  const service = asRecord3(value);
@@ -21210,8 +21648,8 @@ function inspectServices(repoPath) {
21210
21648
  return services;
21211
21649
  }
21212
21650
  function inspectEnvFiles(repoPath) {
21213
- const files = import_node_fs43.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
21214
- const content = import_node_fs43.default.readFileSync(import_node_path54.default.join(repoPath, fileName), "utf-8");
21651
+ const files = import_node_fs44.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
21652
+ const content = import_node_fs44.default.readFileSync(import_node_path55.default.join(repoPath, fileName), "utf-8");
21215
21653
  const names = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && line.includes("=")).map((line) => line.split("=")[0]?.trim()).filter((name) => Boolean(name)).sort();
21216
21654
  return { path: fileName, names };
21217
21655
  });
@@ -21225,18 +21663,18 @@ function inspectEnvFiles(repoPath) {
21225
21663
  };
21226
21664
  }
21227
21665
  function inspectDevcontainer(repoPath) {
21228
- const dir = import_node_path54.default.join(repoPath, ".devcontainer");
21229
- if (!import_node_fs43.default.existsSync(dir)) {
21666
+ const dir = import_node_path55.default.join(repoPath, ".devcontainer");
21667
+ if (!import_node_fs44.default.existsSync(dir)) {
21230
21668
  return { exists: false, files: [] };
21231
21669
  }
21232
21670
  return {
21233
21671
  exists: true,
21234
- files: import_node_fs43.default.readdirSync(dir).filter((fileName) => import_node_fs43.default.statSync(import_node_path54.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
21672
+ files: import_node_fs44.default.readdirSync(dir).filter((fileName) => import_node_fs44.default.statSync(import_node_path55.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
21235
21673
  };
21236
21674
  }
21237
21675
  function inspectDevrouter(repoPath) {
21238
21676
  const configPath = getRepoConfigPath(repoPath);
21239
- if (!import_node_fs43.default.existsSync(configPath)) {
21677
+ if (!import_node_fs44.default.existsSync(configPath)) {
21240
21678
  return {
21241
21679
  exists: false,
21242
21680
  configPath,
@@ -21280,15 +21718,15 @@ function inspectAgentGuidance(repoPath) {
21280
21718
  ["AGENTS.md", "agents"],
21281
21719
  ["CLAUDE.md", "claude"]
21282
21720
  ]) {
21283
- if (import_node_fs43.default.existsSync(import_node_path54.default.join(repoPath, fileName))) {
21721
+ if (import_node_fs44.default.existsSync(import_node_path55.default.join(repoPath, fileName))) {
21284
21722
  results.push({ path: fileName, kind });
21285
21723
  }
21286
21724
  }
21287
- const skillsDir = import_node_path54.default.join(repoPath, ".agents", "skills");
21288
- if (import_node_fs43.default.existsSync(skillsDir)) {
21289
- for (const name of import_node_fs43.default.readdirSync(skillsDir).sort()) {
21290
- const skillPath = import_node_path54.default.join(skillsDir, name, "SKILL.md");
21291
- if (import_node_fs43.default.existsSync(skillPath)) {
21725
+ const skillsDir = import_node_path55.default.join(repoPath, ".agents", "skills");
21726
+ if (import_node_fs44.default.existsSync(skillsDir)) {
21727
+ for (const name of import_node_fs44.default.readdirSync(skillsDir).sort()) {
21728
+ const skillPath = import_node_path55.default.join(skillsDir, name, "SKILL.md");
21729
+ if (import_node_fs44.default.existsSync(skillPath)) {
21292
21730
  results.push({ path: relative(repoPath, skillPath), kind: "skill" });
21293
21731
  }
21294
21732
  }
@@ -21331,7 +21769,7 @@ function buildIssues(report) {
21331
21769
  }
21332
21770
  function inspectRepo(options = {}) {
21333
21771
  const repoPath = resolveRepoPath(options.repo);
21334
- const pkg = readJson(import_node_path54.default.join(repoPath, "package.json"));
21772
+ const pkg = readJson(import_node_path55.default.join(repoPath, "package.json"));
21335
21773
  const scripts = inspectScripts(pkg);
21336
21774
  const reportWithoutIssues = {
21337
21775
  repoPath,
@@ -21350,12 +21788,12 @@ function inspectRepo(options = {}) {
21350
21788
  issues: buildIssues(reportWithoutIssues)
21351
21789
  };
21352
21790
  }
21353
- var import_node_fs43, import_node_path54, import_yaml7;
21791
+ var import_node_fs44, import_node_path55, import_yaml7;
21354
21792
  var init_repo_inspect = __esm({
21355
21793
  "src/core/repo-inspect.ts"() {
21356
21794
  "use strict";
21357
- import_node_fs43 = __toESM(require("fs"));
21358
- import_node_path54 = __toESM(require("path"));
21795
+ import_node_fs44 = __toESM(require("fs"));
21796
+ import_node_path55 = __toESM(require("path"));
21359
21797
  import_yaml7 = __toESM(require("yaml"));
21360
21798
  init_repo_config();
21361
21799
  }
@@ -21460,7 +21898,7 @@ function requiredFileChecks(repoPath) {
21460
21898
  ".devcontainer/docker-compose.yml",
21461
21899
  ".devrouter.yml"
21462
21900
  ];
21463
- const missing = required.filter((fileName) => !import_node_fs44.default.existsSync(import_node_path55.default.join(repoPath, fileName)));
21901
+ const missing = required.filter((fileName) => !import_node_fs45.default.existsSync(import_node_path56.default.join(repoPath, fileName)));
21464
21902
  return {
21465
21903
  id: "repo.devcontainer.verify-files",
21466
21904
  level: missing.length === 0 ? "ok" : "error",
@@ -21685,12 +22123,12 @@ async function verifyDevcontainer(options = {}) {
21685
22123
  nextSteps: collectNextSteps3(checks)
21686
22124
  };
21687
22125
  }
21688
- var import_node_fs44, import_node_path55;
22126
+ var import_node_fs45, import_node_path56;
21689
22127
  var init_devcontainer_verify = __esm({
21690
22128
  "src/core/devcontainer-verify.ts"() {
21691
22129
  "use strict";
21692
- import_node_fs44 = __toESM(require("fs"));
21693
- import_node_path55 = __toESM(require("path"));
22130
+ import_node_fs45 = __toESM(require("fs"));
22131
+ import_node_path56 = __toESM(require("path"));
21694
22132
  init_capabilities();
21695
22133
  init_doctor();
21696
22134
  init_host_routes();
@@ -21987,7 +22425,7 @@ function packageManagerIssues(repo) {
21987
22425
  }
21988
22426
  function plannedFiles(repoPath, version) {
21989
22427
  const repo = inspectRepo({ repo: repoPath });
21990
- const projectName = sanitizeProjectName(import_node_path56.default.basename(repo.repoPath));
22428
+ const projectName = sanitizeProjectName(import_node_path57.default.basename(repo.repoPath));
21991
22429
  const nodeMajor = majorVersion(repo.node?.version, "24");
21992
22430
  const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
21993
22431
  const port = inferPort2(repo);
@@ -22032,8 +22470,8 @@ function plannedFiles(repoPath, version) {
22032
22470
  };
22033
22471
  }
22034
22472
  function classifyFile(repoPath, file) {
22035
- const absolutePath = import_node_path56.default.join(repoPath, file.relativePath);
22036
- if (!import_node_fs45.default.existsSync(absolutePath)) {
22473
+ const absolutePath = import_node_path57.default.join(repoPath, file.relativePath);
22474
+ if (!import_node_fs46.default.existsSync(absolutePath)) {
22037
22475
  return {
22038
22476
  path: file.relativePath,
22039
22477
  action: "create",
@@ -22041,7 +22479,7 @@ function classifyFile(repoPath, file) {
22041
22479
  bytes: Buffer.byteLength(file.content)
22042
22480
  };
22043
22481
  }
22044
- const current = import_node_fs45.default.readFileSync(absolutePath, "utf-8");
22482
+ const current = import_node_fs46.default.readFileSync(absolutePath, "utf-8");
22045
22483
  if (!current.includes(MANAGED_MARKER2)) {
22046
22484
  return {
22047
22485
  path: file.relativePath,
@@ -22098,11 +22536,11 @@ function buildPlan(repoPath, dryRun, version) {
22098
22536
  };
22099
22537
  }
22100
22538
  function writeFile(repoPath, file) {
22101
- const absolutePath = import_node_path56.default.join(repoPath, file.relativePath);
22102
- import_node_fs45.default.mkdirSync(import_node_path56.default.dirname(absolutePath), { recursive: true });
22103
- import_node_fs45.default.writeFileSync(absolutePath, file.content, "utf-8");
22539
+ const absolutePath = import_node_path57.default.join(repoPath, file.relativePath);
22540
+ import_node_fs46.default.mkdirSync(import_node_path57.default.dirname(absolutePath), { recursive: true });
22541
+ import_node_fs46.default.writeFileSync(absolutePath, file.content, "utf-8");
22104
22542
  if (file.executable) {
22105
- import_node_fs45.default.chmodSync(absolutePath, 493);
22543
+ import_node_fs46.default.chmodSync(absolutePath, 493);
22106
22544
  }
22107
22545
  }
22108
22546
  function writeDevcontainer(options = {}) {
@@ -22140,12 +22578,12 @@ function writeDevcontainer(options = {}) {
22140
22578
  nextSteps: postWriteNextSteps(repoPath)
22141
22579
  };
22142
22580
  }
22143
- var import_node_fs45, import_node_path56, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
22581
+ var import_node_fs46, import_node_path57, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
22144
22582
  var init_devcontainer_write = __esm({
22145
22583
  "src/core/devcontainer-write.ts"() {
22146
22584
  "use strict";
22147
- import_node_fs45 = __toESM(require("fs"));
22148
- import_node_path56 = __toESM(require("path"));
22585
+ import_node_fs46 = __toESM(require("fs"));
22586
+ import_node_path57 = __toESM(require("path"));
22149
22587
  init_repo_config();
22150
22588
  init_repo_inspect();
22151
22589
  MANAGED_MARKER2 = "devrouter:managed devcontainer";
@@ -22258,11 +22696,11 @@ var init_repo_devcontainer = __esm({
22258
22696
  function fail3(message) {
22259
22697
  throw new Error(message);
22260
22698
  }
22261
- function isRecord6(value) {
22699
+ function isRecord7(value) {
22262
22700
  return typeof value === "object" && value !== null && !Array.isArray(value);
22263
22701
  }
22264
22702
  function requireRecord(value, label2) {
22265
- if (!isRecord6(value)) fail3(`${label2} must be a mapping.`);
22703
+ if (!isRecord7(value)) fail3(`${label2} must be a mapping.`);
22266
22704
  return value;
22267
22705
  }
22268
22706
  function requireExactKeys(value, allowed, label2) {
@@ -22479,13 +22917,13 @@ function buildProfilePlanReport(options) {
22479
22917
  return { ...report, contractPath, bindings };
22480
22918
  }
22481
22919
  function loadContract(repoPath, contractPath) {
22482
- if (import_node_path57.default.isAbsolute(contractPath)) {
22920
+ if (import_node_path58.default.isAbsolute(contractPath)) {
22483
22921
  fail3("profile plan contract path must be relative to the repository.");
22484
22922
  }
22485
22923
  const absolutePath = assertPathWithinRepo(contractPath, repoPath, "Profile plan contract");
22486
22924
  let metadata;
22487
22925
  try {
22488
- metadata = import_node_fs46.default.lstatSync(absolutePath);
22926
+ metadata = import_node_fs47.default.lstatSync(absolutePath);
22489
22927
  } catch (error) {
22490
22928
  fail3(`could not inspect profile plan contract '${contractPath}': ${error.message}`);
22491
22929
  }
@@ -22495,16 +22933,16 @@ function loadContract(repoPath, contractPath) {
22495
22933
  if (metadata.size > MAX_CONTRACT_BYTES) {
22496
22934
  fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
22497
22935
  }
22498
- const realRepo = import_node_fs46.default.realpathSync(repoPath);
22499
- const realContract = import_node_fs46.default.realpathSync(absolutePath);
22936
+ const realRepo = import_node_fs47.default.realpathSync(repoPath);
22937
+ const realContract = import_node_fs47.default.realpathSync(absolutePath);
22500
22938
  assertPathWithinRepo(realContract, realRepo, "Profile plan contract");
22501
- const source = import_node_fs46.default.readFileSync(absolutePath, "utf-8");
22939
+ const source = import_node_fs47.default.readFileSync(absolutePath, "utf-8");
22502
22940
  if (Buffer.byteLength(source, "utf-8") > MAX_CONTRACT_BYTES) {
22503
22941
  fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
22504
22942
  }
22505
22943
  return {
22506
22944
  contract: parseProfilePlanContract(source),
22507
- contractPath: import_node_path57.default.relative(repoPath, absolutePath).split(import_node_path57.default.sep).join("/")
22945
+ contractPath: import_node_path58.default.relative(repoPath, absolutePath).split(import_node_path58.default.sep).join("/")
22508
22946
  };
22509
22947
  }
22510
22948
  function resolveProfilePlan(options) {
@@ -22518,12 +22956,12 @@ function resolveProfilePlan(options) {
22518
22956
  contractPath: loaded.contractPath
22519
22957
  });
22520
22958
  }
22521
- var import_node_fs46, import_node_path57, import_yaml8, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
22959
+ var import_node_fs47, import_node_path58, import_yaml8, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
22522
22960
  var init_profile_plan = __esm({
22523
22961
  "src/core/profile-plan.ts"() {
22524
22962
  "use strict";
22525
- import_node_fs46 = __toESM(require("fs"));
22526
- import_node_path57 = __toESM(require("path"));
22963
+ import_node_fs47 = __toESM(require("fs"));
22964
+ import_node_path58 = __toESM(require("path"));
22527
22965
  import_yaml8 = require("yaml");
22528
22966
  init_paths();
22529
22967
  init_profile_resolution();
@@ -22555,7 +22993,7 @@ async function runProfileResolveCommand(options) {
22555
22993
  async function runProfilePlanCommand(options) {
22556
22994
  const report = resolveProfilePlan(options);
22557
22995
  if (options.output) {
22558
- writeFileAtomically(import_node_path58.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
22996
+ writeFileAtomically(import_node_path59.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
22559
22997
  `);
22560
22998
  }
22561
22999
  if (options.json) {
@@ -22591,14 +23029,14 @@ function printProfilePlanSummary(report, output2) {
22591
23029
  process.stdout.write(`Binding ${key}: ${renderValues(report.bindings[key] ?? [])}
22592
23030
  `);
22593
23031
  }
22594
- if (output2) process.stdout.write(`Output: ${import_node_path58.default.resolve(output2)}
23032
+ if (output2) process.stdout.write(`Output: ${import_node_path59.default.resolve(output2)}
22595
23033
  `);
22596
23034
  }
22597
- var import_node_path58;
23035
+ var import_node_path59;
22598
23036
  var init_profile = __esm({
22599
23037
  "src/commands/profile.ts"() {
22600
23038
  "use strict";
22601
- import_node_path58 = __toESM(require("path"));
23039
+ import_node_path59 = __toESM(require("path"));
22602
23040
  init_atomic_file();
22603
23041
  init_output();
22604
23042
  init_profile_plan();
@@ -22888,7 +23326,7 @@ function sanitizeRouterId(value) {
22888
23326
  return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
22889
23327
  }
22890
23328
  function repoHash(repoPath) {
22891
- return (0, import_node_crypto26.createHash)("sha1").update(import_node_path59.default.resolve(repoPath)).digest("hex").slice(0, 12);
23329
+ return (0, import_node_crypto26.createHash)("sha1").update(import_node_path60.default.resolve(repoPath)).digest("hex").slice(0, 12);
22892
23330
  }
22893
23331
  function asDockerApp(app) {
22894
23332
  return app.runtime === "docker";
@@ -22967,11 +23405,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
22967
23405
  if (dockerApps.length === 0) {
22968
23406
  throw new Error("No docker apps selected to prepare compose overlay.");
22969
23407
  }
22970
- const cachePath = import_node_path59.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
22971
- import_node_fs47.default.mkdirSync(cachePath, { recursive: true });
22972
- const overlayPath = import_node_path59.default.join(cachePath, "compose.devrouter.yml");
23408
+ const cachePath = import_node_path60.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
23409
+ import_node_fs48.default.mkdirSync(cachePath, { recursive: true });
23410
+ const overlayPath = import_node_path60.default.join(cachePath, "compose.devrouter.yml");
22973
23411
  const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
22974
- import_node_fs47.default.writeFileSync(overlayPath, import_yaml9.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
23412
+ import_node_fs48.default.writeFileSync(overlayPath, import_yaml9.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
22975
23413
  return {
22976
23414
  overlayPath,
22977
23415
  composeFiles: ensureComposeFiles(dockerApps),
@@ -22985,7 +23423,7 @@ function runDockerComposeUp(repoPath, composeFiles2, overlayPath, services) {
22985
23423
  fileArgs.push("-f", resolved);
22986
23424
  }
22987
23425
  const args = ["compose", ...fileArgs, "-f", overlayPath, "up", "-d", "--wait", ...services];
22988
- const result = (0, import_node_child_process32.spawnSync)("docker", args, {
23426
+ const result = (0, import_node_child_process33.spawnSync)("docker", args, {
22989
23427
  encoding: "utf-8",
22990
23428
  cwd: repoPath
22991
23429
  });
@@ -23013,7 +23451,7 @@ function queryRunningComposeServices(repoPath, composeFiles2, overlayPath, servi
23013
23451
  "--services",
23014
23452
  ...services
23015
23453
  ];
23016
- const result = (0, import_node_child_process32.spawnSync)("docker", args, {
23454
+ const result = (0, import_node_child_process33.spawnSync)("docker", args, {
23017
23455
  encoding: "utf-8",
23018
23456
  cwd: repoPath
23019
23457
  });
@@ -23039,7 +23477,7 @@ function runDockerComposeStop(repoPath, composeFiles2, overlayPath, services) {
23039
23477
  fileArgs.push("-f", resolved);
23040
23478
  }
23041
23479
  const args = ["compose", ...fileArgs, "-f", overlayPath, "stop", ...services];
23042
- const result = (0, import_node_child_process32.spawnSync)("docker", args, {
23480
+ const result = (0, import_node_child_process33.spawnSync)("docker", args, {
23043
23481
  encoding: "utf-8",
23044
23482
  cwd: repoPath
23045
23483
  });
@@ -23065,7 +23503,7 @@ function runDockerComposeLogs(repoPath, composeFiles2, overlayPath, services, ta
23065
23503
  String(tail),
23066
23504
  ...services
23067
23505
  ];
23068
- (0, import_node_child_process32.spawnSync)("docker", args, {
23506
+ (0, import_node_child_process33.spawnSync)("docker", args, {
23069
23507
  stdio: "inherit",
23070
23508
  cwd: repoPath
23071
23509
  });
@@ -23077,7 +23515,7 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
23077
23515
  fileArgs.push("-f", resolved);
23078
23516
  }
23079
23517
  const args = ["compose", ...fileArgs, "-f", overlayPath, "port", service, String(internalPort)];
23080
- const result = (0, import_node_child_process32.spawnSync)("docker", args, {
23518
+ const result = (0, import_node_child_process33.spawnSync)("docker", args, {
23081
23519
  encoding: "utf-8",
23082
23520
  cwd: repoPath
23083
23521
  });
@@ -23091,14 +23529,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
23091
23529
  const port = Number(match[1]);
23092
23530
  return Number.isInteger(port) && port > 0 ? port : void 0;
23093
23531
  }
23094
- var import_node_child_process32, import_node_crypto26, import_node_fs47, import_node_path59, import_yaml9;
23532
+ var import_node_child_process33, import_node_crypto26, import_node_fs48, import_node_path60, import_yaml9;
23095
23533
  var init_docker_run = __esm({
23096
23534
  "src/core/docker-run.ts"() {
23097
23535
  "use strict";
23098
- import_node_child_process32 = require("child_process");
23536
+ import_node_child_process33 = require("child_process");
23099
23537
  import_node_crypto26 = require("crypto");
23100
- import_node_fs47 = __toESM(require("fs"));
23101
- import_node_path59 = __toESM(require("path"));
23538
+ import_node_fs48 = __toESM(require("fs"));
23539
+ import_node_path60 = __toESM(require("path"));
23102
23540
  import_yaml9 = __toESM(require("yaml"));
23103
23541
  init_docker_error_guidance();
23104
23542
  init_paths();
@@ -23188,7 +23626,7 @@ function isProcessRunning(pid) {
23188
23626
  }
23189
23627
  }
23190
23628
  function readProcessTree(rootPid) {
23191
- const result = (0, import_node_child_process33.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
23629
+ const result = (0, import_node_child_process34.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
23192
23630
  if (result.status !== 0) {
23193
23631
  return [rootPid];
23194
23632
  }
@@ -23299,12 +23737,12 @@ function detectListeningPorts(pids) {
23299
23737
  if (pids.length === 0) {
23300
23738
  return [];
23301
23739
  }
23302
- const result = (0, import_node_child_process33.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
23740
+ const result = (0, import_node_child_process34.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
23303
23741
  encoding: "utf-8"
23304
23742
  });
23305
23743
  if (result.error && result.error.code === "ENOENT") {
23306
23744
  if (process.platform === "linux") {
23307
- const ssResult = (0, import_node_child_process33.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
23745
+ const ssResult = (0, import_node_child_process34.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
23308
23746
  if (ssResult.status === 0 && ssResult.stdout) {
23309
23747
  return parseSsListeningPorts(ssResult.stdout, new Set(pids));
23310
23748
  }
@@ -23363,7 +23801,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
23363
23801
  app.hostRun.command,
23364
23802
  true
23365
23803
  ) : app.hostRun.command;
23366
- const child = (0, import_node_child_process33.spawn)(spawnCommand, {
23804
+ const child = (0, import_node_child_process34.spawn)(spawnCommand, {
23367
23805
  cwd: commandCwd,
23368
23806
  stdio: "inherit",
23369
23807
  shell: true,
@@ -23731,7 +24169,7 @@ async function execWithAppEnv(options) {
23731
24169
  options.command[0],
23732
24170
  true
23733
24171
  );
23734
- child = (0, import_node_child_process33.spawn)(wrapped, {
24172
+ child = (0, import_node_child_process34.spawn)(wrapped, {
23735
24173
  cwd: deps.repoPath,
23736
24174
  stdio: "inherit",
23737
24175
  shell: true,
@@ -23745,7 +24183,7 @@ async function execWithAppEnv(options) {
23745
24183
  false
23746
24184
  );
23747
24185
  const [cmd, ...wrappedArgs] = wrapped;
23748
- child = (0, import_node_child_process33.spawn)(cmd, wrappedArgs, {
24186
+ child = (0, import_node_child_process34.spawn)(cmd, wrappedArgs, {
23749
24187
  cwd: deps.repoPath,
23750
24188
  stdio: "inherit",
23751
24189
  shell: false,
@@ -23753,7 +24191,7 @@ async function execWithAppEnv(options) {
23753
24191
  });
23754
24192
  }
23755
24193
  } else if (options.shell) {
23756
- child = (0, import_node_child_process33.spawn)(options.command[0], {
24194
+ child = (0, import_node_child_process34.spawn)(options.command[0], {
23757
24195
  cwd: deps.repoPath,
23758
24196
  stdio: "inherit",
23759
24197
  shell: true,
@@ -23761,7 +24199,7 @@ async function execWithAppEnv(options) {
23761
24199
  });
23762
24200
  } else {
23763
24201
  const [command, ...args] = options.command;
23764
- child = (0, import_node_child_process33.spawn)(command, args, {
24202
+ child = (0, import_node_child_process34.spawn)(command, args, {
23765
24203
  cwd: deps.repoPath,
23766
24204
  stdio: "inherit",
23767
24205
  shell: false,
@@ -23782,11 +24220,11 @@ async function execWithAppEnv(options) {
23782
24220
  deps.stopDeps();
23783
24221
  }
23784
24222
  }
23785
- var import_node_child_process33, import_node_net5, import_node_process, import_promises, POLL_INTERVAL_MS, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
24223
+ var import_node_child_process34, import_node_net5, import_node_process, import_promises, POLL_INTERVAL_MS, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
23786
24224
  var init_app_run = __esm({
23787
24225
  "src/core/app-run.ts"() {
23788
24226
  "use strict";
23789
- import_node_child_process33 = require("child_process");
24227
+ import_node_child_process34 = require("child_process");
23790
24228
  import_node_net5 = __toESM(require("net"));
23791
24229
  import_node_process = require("process");
23792
24230
  import_promises = require("readline/promises");
@@ -23920,13 +24358,13 @@ function measureWorktreeConsumption(worktreePath, options) {
23920
24358
  const startedAt = Date.now();
23921
24359
  let rootStat;
23922
24360
  try {
23923
- rootStat = import_node_fs48.default.lstatSync(worktreePath);
24361
+ rootStat = import_node_fs49.default.lstatSync(worktreePath);
23924
24362
  } catch (error) {
23925
24363
  return { status: "unknown", reason: describeError(error, worktreePath) };
23926
24364
  }
23927
24365
  let rootEntries;
23928
24366
  try {
23929
- rootEntries = import_node_fs48.default.readdirSync(worktreePath, { withFileTypes: true });
24367
+ rootEntries = import_node_fs49.default.readdirSync(worktreePath, { withFileTypes: true });
23930
24368
  } catch (error) {
23931
24369
  return { status: "unknown", reason: describeError(error, worktreePath) };
23932
24370
  }
@@ -23952,10 +24390,10 @@ function measureWorktreeConsumption(worktreePath, options) {
23952
24390
  timedOut = true;
23953
24391
  break;
23954
24392
  }
23955
- const entryPath = import_node_path60.default.join(dirPath, entry.name);
24393
+ const entryPath = import_node_path61.default.join(dirPath, entry.name);
23956
24394
  let entryStat;
23957
24395
  try {
23958
- entryStat = import_node_fs48.default.lstatSync(entryPath);
24396
+ entryStat = import_node_fs49.default.lstatSync(entryPath);
23959
24397
  } catch (error) {
23960
24398
  if (error?.code === "ENOENT") continue;
23961
24399
  unreadableReason = describeIncompleteWalk(error);
@@ -23965,7 +24403,7 @@ function measureWorktreeConsumption(worktreePath, options) {
23965
24403
  if (!entryStat.isDirectory()) continue;
23966
24404
  let childEntries;
23967
24405
  try {
23968
- childEntries = import_node_fs48.default.readdirSync(entryPath, { withFileTypes: true });
24406
+ childEntries = import_node_fs49.default.readdirSync(entryPath, { withFileTypes: true });
23969
24407
  } catch (error) {
23970
24408
  unreadableReason = describeIncompleteWalk(error);
23971
24409
  break;
@@ -24029,12 +24467,12 @@ function describeError(error, worktreePath) {
24029
24467
  }
24030
24468
  return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
24031
24469
  }
24032
- var import_node_fs48, import_node_path60, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
24470
+ var import_node_fs49, import_node_path61, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
24033
24471
  var init_workspace_consumption = __esm({
24034
24472
  "src/core/workspace-consumption.ts"() {
24035
24473
  "use strict";
24036
- import_node_fs48 = __toESM(require("fs"));
24037
- import_node_path60 = __toESM(require("path"));
24474
+ import_node_fs49 = __toESM(require("fs"));
24475
+ import_node_path61 = __toESM(require("path"));
24038
24476
  init_devpod_environment();
24039
24477
  DEFAULT_DEADLINE_MS = 1e4;
24040
24478
  BLOCK_SIZE_BYTES = 512;
@@ -24042,7 +24480,7 @@ var init_workspace_consumption = __esm({
24042
24480
  });
24043
24481
 
24044
24482
  // src/core/workspace-cleanup.ts
24045
- function isRecord7(value) {
24483
+ function isRecord8(value) {
24046
24484
  return value !== null && typeof value === "object" && !Array.isArray(value);
24047
24485
  }
24048
24486
  function isSha(value) {
@@ -24135,7 +24573,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
24135
24573
  }
24136
24574
  function readGitSnapshot(worktree, commandRunner) {
24137
24575
  const comparablePath = comparableWorkspacePath(worktree.path);
24138
- if (worktree.prunable || !import_node_fs49.default.existsSync(comparablePath)) {
24576
+ if (worktree.prunable || !import_node_fs50.default.existsSync(comparablePath)) {
24139
24577
  return { worktree, checkout: "missing", head: null, committerDate: null };
24140
24578
  }
24141
24579
  const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
@@ -24186,14 +24624,14 @@ function parseGitHubChanges(value, project, branch) {
24186
24624
  if (!Array.isArray(value)) return void 0;
24187
24625
  const changes = [];
24188
24626
  for (const item of value) {
24189
- if (!isRecord7(item)) return void 0;
24190
- const repository = isRecord7(item.repository) ? item.repository.nameWithOwner : void 0;
24191
- const headRepository = isRecord7(item.headRepository) ? item.headRepository.nameWithOwner : void 0;
24627
+ if (!isRecord8(item)) return void 0;
24628
+ const repository = isRecord8(item.repository) ? item.repository.nameWithOwner : void 0;
24629
+ const headRepository = isRecord8(item.headRepository) ? item.headRepository.nameWithOwner : void 0;
24192
24630
  if (repository !== project || headRepository !== project || item.headRefName !== branch || !isSha(item.headRefOid) || typeof item.baseRefName !== "string") {
24193
24631
  continue;
24194
24632
  }
24195
24633
  const merged = item.state === "MERGED" && validTimestamp(item.mergedAt);
24196
- const mergeCommit = isRecord7(item.mergeCommit) && isSha(item.mergeCommit.oid) ? item.mergeCommit.oid : void 0;
24634
+ const mergeCommit = isRecord8(item.mergeCommit) && isSha(item.mergeCommit.oid) ? item.mergeCommit.oid : void 0;
24197
24635
  const baseSha = isSha(item.baseRefOid) ? item.baseRefOid : void 0;
24198
24636
  changes.push({
24199
24637
  sourceBranch: branch,
@@ -24210,14 +24648,14 @@ function parseGitLabChanges(value, _project, branch) {
24210
24648
  if (!Array.isArray(value)) return void 0;
24211
24649
  const changes = [];
24212
24650
  for (const item of value) {
24213
- if (!isRecord7(item)) return void 0;
24651
+ if (!isRecord8(item)) return void 0;
24214
24652
  const sourceProjectId = item.source_project_id;
24215
24653
  const targetProjectId = item.target_project_id;
24216
24654
  const sameProject = typeof sourceProjectId === "number" && typeof targetProjectId === "number" && sourceProjectId === targetProjectId;
24217
24655
  if (!sameProject || item.source_branch !== branch || !isSha(item.sha) || typeof item.target_branch !== "string") {
24218
24656
  continue;
24219
24657
  }
24220
- const diffRefs = isRecord7(item.diff_refs) ? item.diff_refs : void 0;
24658
+ const diffRefs = isRecord8(item.diff_refs) ? item.diff_refs : void 0;
24221
24659
  const mergeCommitSha = isSha(item.merge_commit_sha) ? item.merge_commit_sha : void 0;
24222
24660
  const baseSha = diffRefs && isSha(diffRefs.base_sha) ? diffRefs.base_sha : void 0;
24223
24661
  changes.push({
@@ -24660,7 +25098,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
24660
25098
  containers2 = measureContainersFn(worktreePaths);
24661
25099
  } catch (error) {
24662
25100
  const reason = `container measurement failed: ${describeCause(error)}`;
24663
- containers2 = new Map(worktreePaths.map((path61) => [path61, unknownContainers(reason)]));
25101
+ containers2 = new Map(worktreePaths.map((path62) => [path62, unknownContainers(reason)]));
24664
25102
  }
24665
25103
  }
24666
25104
  const rows2 = records.map((record5) => {
@@ -24710,12 +25148,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
24710
25148
  networkCapacity: (dependencies.inspectNetworkCapacity ?? inspectNetworkCapacity)()
24711
25149
  };
24712
25150
  }
24713
- var import_node_child_process34, import_node_fs49, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
25151
+ var import_node_child_process35, import_node_fs50, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
24714
25152
  var init_workspace_cleanup = __esm({
24715
25153
  "src/core/workspace-cleanup.ts"() {
24716
25154
  "use strict";
24717
- import_node_child_process34 = require("child_process");
24718
- import_node_fs49 = __toESM(require("fs"));
25155
+ import_node_child_process35 = require("child_process");
25156
+ import_node_fs50 = __toESM(require("fs"));
24719
25157
  init_devpod_workspaces();
24720
25158
  init_host_routes();
24721
25159
  init_network_diagnostics();
@@ -24733,7 +25171,7 @@ var init_workspace_cleanup = __esm({
24733
25171
  ];
24734
25172
  defaultCommandRunner = (command, args, input2) => {
24735
25173
  const environment = command === "git" ? READ_ONLY_GIT_ENV2 : { ...process.env, LC_ALL: "C" };
24736
- const result = (0, import_node_child_process34.spawnSync)(command, args, {
25174
+ const result = (0, import_node_child_process35.spawnSync)(command, args, {
24737
25175
  encoding: "utf-8",
24738
25176
  env: environment,
24739
25177
  ...input2 === void 0 ? {} : { input: input2 }
@@ -24827,20 +25265,20 @@ function warnMissingWorkspaceOwnership(repoPath) {
24827
25265
  );
24828
25266
  }
24829
25267
  function defaultWorktreePath(mainRepo, ws) {
24830
- return import_node_path61.default.join(mainRepo, "trees", ws);
25268
+ return import_node_path62.default.join(mainRepo, "trees", ws);
24831
25269
  }
24832
25270
  function assertDefaultWorktreeRootIgnored(mainRepo) {
24833
- const ignored = (0, import_node_child_process35.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
25271
+ const ignored = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
24834
25272
  encoding: "utf-8"
24835
25273
  });
24836
25274
  if (ignored.status !== 0) {
24837
25275
  throw new Error(
24838
- `Default worktree root '${import_node_path61.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path61.default.join(mainRepo, ".gitignore")}' or use --path.`
25276
+ `Default worktree root '${import_node_path62.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path62.default.join(mainRepo, ".gitignore")}' or use --path.`
24839
25277
  );
24840
25278
  }
24841
25279
  }
24842
25280
  function legacyDefaultWorktreePath(mainRepo, ws) {
24843
- return import_node_path61.default.join(import_node_path61.default.dirname(mainRepo), `${import_node_path61.default.basename(mainRepo)}-${ws}`);
25281
+ return import_node_path62.default.join(import_node_path62.default.dirname(mainRepo), `${import_node_path62.default.basename(mainRepo)}-${ws}`);
24844
25282
  }
24845
25283
  function teardownFallbackPath(mainRepo, workspace) {
24846
25284
  const candidates = [
@@ -24952,13 +25390,13 @@ function assertFullDownPreflight(mainRepo, target) {
24952
25390
  if (sameWorkspacePath(target.worktreePath, mainRepo)) {
24953
25391
  throw new Error("Refusing to remove the primary Git checkout.");
24954
25392
  }
24955
- if (!target.worktree || target.worktree.prunable || !import_node_fs50.default.existsSync(target.worktreePath)) return;
25393
+ if (!target.worktree || target.worktree.prunable || !import_node_fs51.default.existsSync(target.worktreePath)) return;
24956
25394
  if (target.worktree.locked) {
24957
25395
  throw new Error(
24958
25396
  `Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
24959
25397
  );
24960
25398
  }
24961
- const status = (0, import_node_child_process35.spawnSync)(
25399
+ const status = (0, import_node_child_process36.spawnSync)(
24962
25400
  "git",
24963
25401
  ["-C", target.worktreePath, "status", "--porcelain", "--untracked-files=normal"],
24964
25402
  { encoding: "utf-8" }
@@ -24992,9 +25430,9 @@ async function workspaceUp(branch, opts = {}) {
24992
25430
  (candidate) => defaultWorktreePath(mainRepo, candidate)
24993
25431
  );
24994
25432
  const generatedPath = candidatePaths.find(
24995
- (candidate) => !import_node_fs50.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
25433
+ (candidate) => !import_node_fs51.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
24996
25434
  );
24997
- const selectedPath = opts.path ? import_node_path61.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
25435
+ const selectedPath = opts.path ? import_node_path62.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
24998
25436
  if (!selectedPath) {
24999
25437
  throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
25000
25438
  }
@@ -25003,7 +25441,7 @@ async function workspaceUp(branch, opts = {}) {
25003
25441
  `Branch '${branch}' already uses worktree '${existingBranch.path}', not '${selectedPath}'.`
25004
25442
  );
25005
25443
  }
25006
- if (import_node_fs50.default.existsSync(selectedPath)) {
25444
+ if (import_node_fs51.default.existsSync(selectedPath)) {
25007
25445
  const registered = worktrees.find(
25008
25446
  (worktree) => sameWorkspacePath(worktree.path, selectedPath)
25009
25447
  );
@@ -25022,11 +25460,11 @@ async function workspaceUp(branch, opts = {}) {
25022
25460
  return selectedPath;
25023
25461
  }
25024
25462
  if (!opts.path) assertDefaultWorktreeRootIgnored(mainRepo);
25025
- const add = (0, import_node_child_process35.spawnSync)("git", ["-C", mainRepo, "worktree", "add", selectedPath, branch], {
25463
+ const add = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "worktree", "add", selectedPath, branch], {
25026
25464
  encoding: "utf-8"
25027
25465
  });
25028
25466
  if (add.status !== 0) {
25029
- const addNew = (0, import_node_child_process35.spawnSync)(
25467
+ const addNew = (0, import_node_child_process36.spawnSync)(
25030
25468
  "git",
25031
25469
  ["-C", mainRepo, "worktree", "add", "-b", branch, selectedPath],
25032
25470
  { encoding: "utf-8" }
@@ -25160,8 +25598,8 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
25160
25598
  }
25161
25599
  const result = await mutateWorkspaceRuntime("delete", resolved, worktrees, opts.quiet);
25162
25600
  if (removeWorktree) {
25163
- if (resolved.worktree && !resolved.worktree.prunable && import_node_fs50.default.existsSync(resolved.worktreePath)) {
25164
- const rm = (0, import_node_child_process35.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
25601
+ if (resolved.worktree && !resolved.worktree.prunable && import_node_fs51.default.existsSync(resolved.worktreePath)) {
25602
+ const rm = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
25165
25603
  encoding: "utf-8"
25166
25604
  });
25167
25605
  if (rm.status !== 0) {
@@ -25179,7 +25617,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
25179
25617
  }
25180
25618
  return result;
25181
25619
  };
25182
- return resolved.worktree && !resolved.worktree.prunable && import_node_fs50.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
25620
+ return resolved.worktree && !resolved.worktree.prunable && import_node_fs51.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
25183
25621
  }
25184
25622
  async function workspaceStop(target, opts = {}) {
25185
25623
  return runWorkspaceLifecycle("stop", target, opts);
@@ -25187,13 +25625,13 @@ async function workspaceStop(target, opts = {}) {
25187
25625
  async function workspaceDown(target, opts = {}) {
25188
25626
  return runWorkspaceLifecycle("down", target, opts);
25189
25627
  }
25190
- var import_node_child_process35, import_node_fs50, import_node_path61, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
25628
+ var import_node_child_process36, import_node_fs51, import_node_path62, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
25191
25629
  var init_workspace_lifecycle = __esm({
25192
25630
  "src/core/workspace-lifecycle.ts"() {
25193
25631
  "use strict";
25194
- import_node_child_process35 = require("child_process");
25195
- import_node_fs50 = __toESM(require("fs"));
25196
- import_node_path61 = __toESM(require("path"));
25632
+ import_node_child_process36 = require("child_process");
25633
+ import_node_fs51 = __toESM(require("fs"));
25634
+ import_node_path62 = __toESM(require("path"));
25197
25635
  init_devpod_mutation();
25198
25636
  init_devpod_workspaces();
25199
25637
  init_devsy_mutation();
@@ -25377,7 +25815,7 @@ var init_version = __esm({
25377
25815
 
25378
25816
  // src/cli.ts
25379
25817
  var import_commander = require("commander");
25380
- var CLI_VERSION = true ? "0.0.68" : "0.0.0-dev";
25818
+ var CLI_VERSION = true ? "0.0.69" : "0.0.0-dev";
25381
25819
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
25382
25820
  function withErrorHandling(action2) {
25383
25821
  return async (...args) => {
@@ -25747,13 +26185,13 @@ workspaceCommand2.command("down").description("Delete a workspace's runtime and
25747
26185
  await runWorkspaceDownCommand2(target, options);
25748
26186
  })
25749
26187
  );
25750
- workspaceCommand2.command("journal settle").description(
26188
+ workspaceCommand2.command("journal").description("Inspect and recover workspace lifecycle journals").command("settle").description(
25751
26189
  "Settle an interrupted lifecycle operation as unobservable so ensure and stop can proceed"
25752
26190
  ).argument("[path]", "Checkout path (defaults to current directory)").option("--json", "Output JSON").action(
25753
- withErrorHandling(async (path61, _options, command) => {
26191
+ withErrorHandling(async (path62, _options, command) => {
25754
26192
  const options = command.opts();
25755
26193
  const { runWorkspaceJournalSettleCommand: runWorkspaceJournalSettleCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
25756
- await runWorkspaceJournalSettleCommand2({ ...path61 ? { path: path61 } : {}, ...options });
26194
+ await runWorkspaceJournalSettleCommand2({ ...path62 ? { path: path62 } : {}, ...options });
25757
26195
  })
25758
26196
  );
25759
26197
  function parseVersionRequest(argv) {