@devrouter/cli 0.0.68 → 0.0.70

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.70" : "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.70" : "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,282 @@ 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
+ const unique = /* @__PURE__ */ new Map();
13066
+ for (const port of ports) {
13067
+ unique.set(`${port.hostIp ?? ""}|${port.hostPort}|${port.protocol}`, port);
13068
+ }
13069
+ return [...unique.values()];
13070
+ }
13071
+ function parseHolderSnapshots(stdout) {
13072
+ return stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
13073
+ let parsed;
13074
+ try {
13075
+ parsed = JSON.parse(line);
13076
+ } catch {
13077
+ throw new Error("Host-port holder inspection returned malformed JSON.");
13078
+ }
13079
+ if (!isRecord6(parsed) || typeof parsed.id !== "string" || parsed.id.length === 0) {
13080
+ throw new Error("Host-port holder inspection returned an invalid container record.");
13081
+ }
13082
+ const name = typeof parsed.name === "string" ? parsed.name.replace(/^\//, "") : parsed.id;
13083
+ return {
13084
+ id: parsed.id,
13085
+ name,
13086
+ composeProject: optionalLabel(parsed.labels, "com.docker.compose.project"),
13087
+ workingDir: optionalLabel(parsed.labels, "com.docker.compose.project.working_dir"),
13088
+ ports: parseHolderPortMap(parsed.ports)
13089
+ };
13090
+ });
13091
+ }
13092
+ function listHolderSnapshots() {
13093
+ const listed = runDocker(["ps", "--filter", "status=running", "--format", "{{.ID}}"]);
13094
+ const ids2 = listed.split(/\r?\n/).map((id5) => id5.trim()).filter(Boolean);
13095
+ if (ids2.length === 0) return [];
13096
+ if (ids2.length > MAX_RUNNING_HOLDERS) {
13097
+ throw new Error(
13098
+ `Host-port holder inspection found ${ids2.length} running containers, above its bound.`
13099
+ );
13100
+ }
13101
+ const inspected = runDocker(["inspect", "--format", HOST_PORT_INSPECT_TEMPLATE, ...ids2]);
13102
+ return parseHolderSnapshots(inspected);
13103
+ }
13104
+ function renderManagedComposeModel(plan, workspace) {
13105
+ const fileArgs = plan.composeFiles.flatMap((file) => ["-f", file]);
13106
+ const result = (0, import_node_child_process20.spawnSync)(
13107
+ "docker",
13108
+ [
13109
+ "compose",
13110
+ "--profile",
13111
+ "*",
13112
+ "--project-directory",
13113
+ plan.composeDirectory,
13114
+ ...fileArgs,
13115
+ "config",
13116
+ "--format",
13117
+ "json"
13118
+ ],
13119
+ {
13120
+ cwd: plan.composeDirectory,
13121
+ encoding: "utf-8",
13122
+ env: managedComposeEnvironment(workspace),
13123
+ stdio: ["ignore", "pipe", "pipe"],
13124
+ timeout: COMPOSE_RENDER_TIMEOUT_MS,
13125
+ maxBuffer: COMPOSE_RENDER_MAX_BUFFER
13126
+ }
13127
+ );
13128
+ if (result.status !== 0 || result.error || !result.stdout?.trim()) {
13129
+ const reason = (result.stderr || result.error?.message || "unknown error").toString().trim().split(/\r?\n/).slice(-3).join("; ");
13130
+ throw new Error(
13131
+ `Host-port claims could not render the managed Compose model that the start would bind: ${reason}`
13132
+ );
13133
+ }
13134
+ try {
13135
+ return JSON.parse(result.stdout);
13136
+ } catch {
13137
+ throw new Error("Host-port claims received a malformed rendered Compose model.");
13138
+ }
13139
+ }
13140
+ function attributeHolderWorktree(workingDir) {
13141
+ if (import_node_path39.default.basename(workingDir) !== ".devcontainer") return void 0;
13142
+ return comparableWorkspacePath(import_node_path39.default.dirname(workingDir));
13143
+ }
13144
+ function holderAttribution(holder, repoPath) {
13145
+ const attribution = {};
13146
+ if (holder.composeProject) attribution.holderComposeProject = holder.composeProject;
13147
+ const worktree = holder.workingDir ? attributeHolderWorktree(holder.workingDir) : void 0;
13148
+ if (!worktree) return attribution;
13149
+ attribution.holderWorktreePath = worktree;
13150
+ try {
13151
+ const owner = listWorkspaceOwnership(repoPath).find(
13152
+ (record5) => comparableWorkspacePath(record5.worktreePath) === worktree
13153
+ );
13154
+ if (owner) {
13155
+ attribution.holderWorkspace = owner.workspace;
13156
+ if (owner.branch) attribution.holderBranch = owner.branch;
13157
+ }
13158
+ } catch {
13159
+ }
13160
+ return attribution;
13161
+ }
13162
+ function remediationFor(holder, attribution) {
13163
+ if (attribution.holderWorktreePath) {
13164
+ 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.`;
13165
+ }
13166
+ return `Stop or reconfigure the holding container '${holder.name}' and run ensure again; devrouter never rewrites consumer-declared host bindings.`;
13167
+ }
13168
+ function detectHostPortClaimConflicts(options) {
13169
+ const desired = resolveFixedPublishedHostPorts(
13170
+ renderManagedComposeModel(options.plan, options.workspace)
13171
+ );
13172
+ if (desired.length === 0) return [];
13173
+ const holders = listHolderSnapshots();
13174
+ const devcontainerDir = import_node_path39.default.join(options.repoPath, ".devcontainer");
13175
+ const conflicts = [];
13176
+ for (const binding of desired) {
13177
+ for (const holder of holders) {
13178
+ for (const holderPort of holder.ports) {
13179
+ if (!hostPortBindingsConflict(binding, holderPort)) continue;
13180
+ if (holder.workingDir && sameWorkspacePath(holder.workingDir, devcontainerDir)) continue;
13181
+ const attribution = holderAttribution(holder, options.repoPath);
13182
+ conflicts.push({
13183
+ service: binding.service,
13184
+ hostIp: binding.hostIp,
13185
+ hostPort: binding.hostPort,
13186
+ protocol: binding.protocol,
13187
+ holderContainer: holder.name,
13188
+ ...attribution,
13189
+ remediation: remediationFor(holder, attribution)
13190
+ });
13191
+ }
13192
+ }
13193
+ }
13194
+ return conflicts;
13195
+ }
13196
+ 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;
13197
+ var init_host_port_claims = __esm({
13198
+ "src/core/host-port-claims.ts"() {
13199
+ "use strict";
13200
+ import_node_child_process20 = require("child_process");
13201
+ import_node_path39 = __toESM(require("path"));
13202
+ init_devcontainer_profile();
13203
+ init_network_effect_scope();
13204
+ init_workspace();
13205
+ init_workspace_ownership();
13206
+ COMPOSE_RENDER_TIMEOUT_MS = 3e4;
13207
+ COMPOSE_RENDER_MAX_BUFFER = 8 * 1024 * 1024;
13208
+ DOCKER_TIMEOUT_MS = 1e4;
13209
+ DOCKER_MAX_BUFFER = 1024 * 1024;
13210
+ MAX_DESIRED_HOST_PORTS = 1024;
13211
+ MAX_RUNNING_HOLDERS = 512;
13212
+ 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}}}';
13213
+ }
13214
+ });
13215
+
12843
13216
  // src/core/http-route-probe.ts
12844
13217
  function probeHttpRoute(host, options = {}) {
12845
13218
  const maxTimeSeconds = options.maxTimeSeconds ?? DEFAULT_MAX_TIME_SECONDS;
@@ -12866,7 +13239,7 @@ function probeHttpRoute(host, options = {}) {
12866
13239
  args.push("--cacert", CERT_FILE);
12867
13240
  }
12868
13241
  args.push(url);
12869
- const result = (0, import_node_child_process20.spawnSync)("curl", args, { encoding: "utf-8" });
13242
+ const result = (0, import_node_child_process21.spawnSync)("curl", args, { encoding: "utf-8" });
12870
13243
  const stdout = result.stdout?.trim() ?? "";
12871
13244
  const status = /^\d{3}$/.test(stdout) ? Number(stdout) : void 0;
12872
13245
  const ok = result.status === 0 && status !== void 0 && status >= 100 && status < 500;
@@ -12901,7 +13274,7 @@ function probeHttpReadinessSync(host, readiness, maxTimeSeconds, repoPath) {
12901
13274
  }
12902
13275
  let result;
12903
13276
  try {
12904
- result = (0, import_node_child_process20.spawnSync)("curl", args, {
13277
+ result = (0, import_node_child_process21.spawnSync)("curl", args, {
12905
13278
  encoding: "utf-8",
12906
13279
  maxBuffer: CONTRACT_MAX_BUFFER,
12907
13280
  timeout: Math.ceil(parentTimeoutMs)
@@ -12983,11 +13356,11 @@ function transportFailure() {
12983
13356
  classification: "transport"
12984
13357
  };
12985
13358
  }
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;
13359
+ 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
13360
  var init_http_route_probe = __esm({
12988
13361
  "src/core/http-route-probe.ts"() {
12989
13362
  "use strict";
12990
- import_node_child_process20 = require("child_process");
13363
+ import_node_child_process21 = require("child_process");
12991
13364
  init_router();
12992
13365
  init_tls();
12993
13366
  DEFAULT_MAX_TIME_SECONDS = 5;
@@ -12999,20 +13372,20 @@ var init_http_route_probe = __esm({
12999
13372
  });
13000
13373
 
13001
13374
  // src/core/managed-host-preparation.ts
13002
- var import_node_child_process21, import_node_fs30;
13375
+ var import_node_child_process22, import_node_fs31;
13003
13376
  var init_managed_host_preparation = __esm({
13004
13377
  "src/core/managed-host-preparation.ts"() {
13005
13378
  "use strict";
13006
- import_node_child_process21 = require("child_process");
13007
- import_node_fs30 = __toESM(require("fs"));
13379
+ import_node_child_process22 = require("child_process");
13380
+ import_node_fs31 = __toESM(require("fs"));
13008
13381
  init_repo_config();
13009
13382
  }
13010
13383
  });
13011
13384
 
13012
13385
  // src/core/managed-post-start.ts
13013
13386
  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);
13387
+ if (!import_node_fs32.default.existsSync(filePath) || !import_node_fs32.default.lstatSync(filePath).isFile()) return void 0;
13388
+ return import_node_fs32.default.readFileSync(filePath);
13016
13389
  }
13017
13390
  function readRegularFile(filePath) {
13018
13391
  return readRegularFileBytes(filePath)?.toString("utf-8");
@@ -13021,11 +13394,11 @@ function adapterFingerprint(adapter) {
13021
13394
  return (0, import_node_crypto17.createHash)("sha256").update(adapter).digest("hex");
13022
13395
  }
13023
13396
  function resolveManagedPostStartPlan(repoPath) {
13024
- const adapterPath = import_node_path39.default.join(repoPath, MANAGED_ADAPTER_PATH);
13397
+ const adapterPath = import_node_path40.default.join(repoPath, MANAGED_ADAPTER_PATH);
13025
13398
  const adapterBytes = readRegularFileBytes(adapterPath);
13026
13399
  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");
13400
+ const dockerfilePath = import_node_path40.default.join(repoPath, ".devcontainer", "Dockerfile");
13401
+ const devcontainerPath = import_node_path40.default.join(repoPath, ".devcontainer", "devcontainer.json");
13029
13402
  const dockerfile = readRegularFile(dockerfilePath) ?? "";
13030
13403
  const devcontainer = readRegularFile(devcontainerPath) ?? "";
13031
13404
  const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
@@ -13071,7 +13444,7 @@ function assertSafeProcessName(name) {
13071
13444
  }
13072
13445
  function runManagedProcessAction(options) {
13073
13446
  assertSafeProcessName(options.name);
13074
- const result = (0, import_node_child_process22.spawnSync)(
13447
+ const result = (0, import_node_child_process23.spawnSync)(
13075
13448
  "docker",
13076
13449
  [
13077
13450
  "exec",
@@ -13108,14 +13481,14 @@ function runManagedProcessAction(options) {
13108
13481
  }
13109
13482
  return "drifted";
13110
13483
  }
13111
- var import_node_child_process22, import_node_crypto17, import_node_fs31, import_node_path39, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
13484
+ var import_node_child_process23, import_node_crypto17, import_node_fs32, import_node_path40, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
13112
13485
  var init_managed_post_start = __esm({
13113
13486
  "src/core/managed-post-start.ts"() {
13114
13487
  "use strict";
13115
- import_node_child_process22 = require("child_process");
13488
+ import_node_child_process23 = require("child_process");
13116
13489
  import_node_crypto17 = require("crypto");
13117
- import_node_fs31 = __toESM(require("fs"));
13118
- import_node_path39 = __toESM(require("path"));
13490
+ import_node_fs32 = __toESM(require("fs"));
13491
+ import_node_path40 = __toESM(require("path"));
13119
13492
  init_devcontainer_config();
13120
13493
  init_network_effect_scope();
13121
13494
  MANAGED_MARKER = "devrouter:managed devcontainer";
@@ -13567,7 +13940,7 @@ function readDocker(endpoint, args, timeoutMs) {
13567
13940
  const env = { ...process.env };
13568
13941
  delete env.DOCKER_CONTEXT;
13569
13942
  delete env.DOCKER_HOST;
13570
- const result = (0, import_node_child_process23.spawnSync)("docker", ["--host", endpoint, ...args], {
13943
+ const result = (0, import_node_child_process24.spawnSync)("docker", ["--host", endpoint, ...args], {
13571
13944
  env,
13572
13945
  encoding: "utf-8",
13573
13946
  timeout: timeoutMs,
@@ -13722,11 +14095,11 @@ function collectDockerNetworkInventory(options = {}, dependencies = {}) {
13722
14095
  }
13723
14096
  return result;
13724
14097
  }
13725
- var import_node_child_process23, NetworkInventoryError, MAX_IDS, MAX_OUTPUT_BYTES, FULL_ID, NETWORK_TEMPLATE, CONTAINER_TEMPLATE;
14098
+ var import_node_child_process24, NetworkInventoryError, MAX_IDS, MAX_OUTPUT_BYTES, FULL_ID, NETWORK_TEMPLATE, CONTAINER_TEMPLATE;
13726
14099
  var init_network_inventory = __esm({
13727
14100
  "src/core/network-inventory.ts"() {
13728
14101
  "use strict";
13729
- import_node_child_process23 = require("child_process");
14102
+ import_node_child_process24 = require("child_process");
13730
14103
  init_devpod_environment();
13731
14104
  NetworkInventoryError = class extends Error {
13732
14105
  };
@@ -14054,26 +14427,26 @@ function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
14054
14427
  try {
14055
14428
  if (!endpoint.startsWith("unix:///")) return false;
14056
14429
  const socket = endpoint.slice("unix://".length);
14057
- const table = import_node_fs32.default.readFileSync(import_node_path40.default.join(procRoot, "net/unix"), "utf8");
14430
+ const table = import_node_fs33.default.readFileSync(import_node_path41.default.join(procRoot, "net/unix"), "utf8");
14058
14431
  if (Buffer.byteLength(table) > 1024 * 1024) return false;
14059
14432
  const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
14060
14433
  if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
14061
14434
  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));
14435
+ const ownNamespace = import_node_fs33.default.readlinkSync(import_node_path41.default.join(procRoot, "self/ns/net"));
14436
+ const pids = import_node_fs33.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
14064
14437
  if (pids.length > 4096) return false;
14065
14438
  for (const pid of pids) {
14066
14439
  if (Date.now() > deadline) return false;
14067
14440
  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"));
14441
+ const root = import_node_path41.default.join(procRoot, pid);
14442
+ if (import_node_path41.default.basename(import_node_fs33.default.readlinkSync(import_node_path41.default.join(root, "exe"))) !== "dockerd") continue;
14443
+ if (import_node_fs33.default.readlinkSync(import_node_path41.default.join(root, "ns/net")) !== ownNamespace) continue;
14444
+ const descriptors = import_node_fs33.default.readdirSync(import_node_path41.default.join(root, "fd"));
14072
14445
  if (descriptors.length > 4096) continue;
14073
14446
  if (descriptors.some((fd) => {
14074
14447
  if (Date.now() > deadline) return false;
14075
14448
  try {
14076
- return import_node_fs32.default.readlinkSync(import_node_path40.default.join(root, "fd", fd)) === inode;
14449
+ return import_node_fs33.default.readlinkSync(import_node_path41.default.join(root, "fd", fd)) === inode;
14077
14450
  } catch {
14078
14451
  return false;
14079
14452
  }
@@ -14087,12 +14460,12 @@ function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
14087
14460
  }
14088
14461
  return false;
14089
14462
  }
14090
- var import_node_fs32, import_node_path40;
14463
+ var import_node_fs33, import_node_path41;
14091
14464
  var init_network_local_daemon = __esm({
14092
14465
  "src/core/network-local-daemon.ts"() {
14093
14466
  "use strict";
14094
- import_node_fs32 = __toESM(require("fs"));
14095
- import_node_path40 = __toESM(require("path"));
14467
+ import_node_fs33 = __toESM(require("fs"));
14468
+ import_node_path41 = __toESM(require("path"));
14096
14469
  }
14097
14470
  });
14098
14471
 
@@ -14120,7 +14493,7 @@ function parseLinuxNetworkRoutes(output2) {
14120
14493
  function collectNetworkRoutes(options) {
14121
14494
  if ((options.platform ?? process.platform) !== "linux" || !hasLocalDockerNetworkNamespace(options.endpoint))
14122
14495
  return { status: "unknown", routes: [] };
14123
- const result = (0, import_node_child_process24.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
14496
+ const result = (0, import_node_child_process25.spawnSync)("ip", ["-j", "-4", "route", "show", "table", "all"], {
14124
14497
  encoding: "utf8",
14125
14498
  timeout: 5e3,
14126
14499
  maxBuffer: 1024 * 1024
@@ -14128,11 +14501,11 @@ function collectNetworkRoutes(options) {
14128
14501
  if (result.error || result.status !== 0) return { status: "unknown", routes: [] };
14129
14502
  return parseLinuxNetworkRoutes(result.stdout);
14130
14503
  }
14131
- var import_node_child_process24, import_node_net3;
14504
+ var import_node_child_process25, import_node_net3;
14132
14505
  var init_network_routes = __esm({
14133
14506
  "src/core/network-routes.ts"() {
14134
14507
  "use strict";
14135
- import_node_child_process24 = require("child_process");
14508
+ import_node_child_process25 = require("child_process");
14136
14509
  import_node_net3 = require("net");
14137
14510
  init_network_capacity();
14138
14511
  init_network_local_daemon();
@@ -14208,14 +14581,14 @@ var init_network_compose = __esm({
14208
14581
  });
14209
14582
 
14210
14583
  // 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;
14584
+ var import_node_child_process26, import_node_crypto19, import_node_fs34, import_node_path42, import_yaml4, MAX_BYTES3;
14212
14585
  var init_network_compose_files = __esm({
14213
14586
  "src/core/network-compose-files.ts"() {
14214
14587
  "use strict";
14215
- import_node_child_process25 = require("child_process");
14588
+ import_node_child_process26 = require("child_process");
14216
14589
  import_node_crypto19 = require("crypto");
14217
- import_node_fs33 = __toESM(require("fs"));
14218
- import_node_path41 = __toESM(require("path"));
14590
+ import_node_fs34 = __toESM(require("fs"));
14591
+ import_node_path42 = __toESM(require("path"));
14219
14592
  import_yaml4 = __toESM(require("yaml"));
14220
14593
  init_atomic_file();
14221
14594
  init_devcontainer_profile();
@@ -14225,11 +14598,11 @@ var init_network_compose_files = __esm({
14225
14598
  });
14226
14599
 
14227
14600
  // src/core/network-connected-route.ts
14228
- var import_node_child_process26;
14601
+ var import_node_child_process27;
14229
14602
  var init_network_connected_route = __esm({
14230
14603
  "src/core/network-connected-route.ts"() {
14231
14604
  "use strict";
14232
- import_node_child_process26 = require("child_process");
14605
+ import_node_child_process27 = require("child_process");
14233
14606
  init_network_capacity();
14234
14607
  }
14235
14608
  });
@@ -14319,19 +14692,20 @@ function resolveLinkedTarget(repoPath) {
14319
14692
  gitCommonDir: resolveGitCommonDir(repoPath)
14320
14693
  };
14321
14694
  }
14322
- var import_node_child_process27, import_node_fs34, import_node_path42;
14695
+ var import_node_child_process28, import_node_fs35, import_node_path43;
14323
14696
  var init_workspace_ensure = __esm({
14324
14697
  "src/core/workspace-ensure.ts"() {
14325
14698
  "use strict";
14326
- import_node_child_process27 = require("child_process");
14327
- import_node_fs34 = __toESM(require("fs"));
14328
- import_node_path42 = __toESM(require("path"));
14699
+ import_node_child_process28 = require("child_process");
14700
+ import_node_fs35 = __toESM(require("fs"));
14701
+ import_node_path43 = __toESM(require("path"));
14329
14702
  init_devcontainer_profile();
14330
14703
  init_devpod_environment();
14331
14704
  init_devpod_mutation();
14332
14705
  init_devpod_workspaces();
14333
14706
  init_devsy_mutation();
14334
14707
  init_file_lock();
14708
+ init_host_port_claims();
14335
14709
  init_host_routes();
14336
14710
  init_http_route_probe();
14337
14711
  init_managed_host_preparation();
@@ -14460,13 +14834,13 @@ function prepareManagedLifecycleOperation(input2) {
14460
14834
  };
14461
14835
  });
14462
14836
  }
14463
- function assertCurrentCapacityController(expected, directory = import_node_path43.default.join(DEVROUTER_HOME, "controller")) {
14837
+ function assertCurrentCapacityController(expected, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller")) {
14464
14838
  const current = new ControllerStore(directory).read();
14465
14839
  if (!expected || current?.store !== expected.store || current.epoch !== expected.epoch)
14466
14840
  throw new Error("Capacity controller incarnation changed.");
14467
14841
  }
14468
14842
  function settlePreparedLifecycleCapacity(input2) {
14469
- const directory = input2.directory ?? import_node_path43.default.join(DEVROUTER_HOME, "controller");
14843
+ const directory = input2.directory ?? import_node_path44.default.join(DEVROUTER_HOME, "controller");
14470
14844
  const store = new CapacityStore(directory);
14471
14845
  for (let attempt = 0; attempt < 3; attempt++) {
14472
14846
  const snapshot = store.read();
@@ -14680,7 +15054,7 @@ async function superviseLifecycle(kind, repoPath, options = {}, command) {
14680
15054
  removeSignals();
14681
15055
  }
14682
15056
  }
14683
- function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs, maxSampleAgeMs, directory = import_node_path43.default.join(DEVROUTER_HOME, "controller"), controller, admission) {
15057
+ function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs, maxSampleAgeMs, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller"), controller, admission) {
14684
15058
  if (request2.kind === "stop") throw new Error("Stop never requires capacity admission.");
14685
15059
  const capacity = new CapacityStore(directory);
14686
15060
  const snapshot = capacity.read();
@@ -14780,7 +15154,7 @@ function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs,
14780
15154
  return decision;
14781
15155
  }
14782
15156
  function capacityPolicyTargets(repoPath) {
14783
- const policy = readCapacityPolicy(import_node_path43.default.join(DEVROUTER_HOME, "controller"));
15157
+ const policy = readCapacityPolicy(import_node_path44.default.join(DEVROUTER_HOME, "controller"));
14784
15158
  return policy?.admissions === "enabled" && policy.enrollments.some((entry) => entry.repoPath === repoPath);
14785
15159
  }
14786
15160
  function controllerWaitSeconds(deadline) {
@@ -14789,7 +15163,7 @@ function controllerWaitSeconds(deadline) {
14789
15163
  async function superviseThroughController(input2) {
14790
15164
  if (input2.kind === "exec" && !input2.command)
14791
15165
  throw new Error("Exec requires a command for controller admission.");
14792
- const directory = import_node_path43.default.join(DEVROUTER_HOME, "controller");
15166
+ const directory = import_node_path44.default.join(DEVROUTER_HOME, "controller");
14793
15167
  const cancellation = { requested: false };
14794
15168
  const removeSignals = installLifecycleWaitSignals(cancellation);
14795
15169
  const cancelled = () => {
@@ -14846,7 +15220,7 @@ async function superviseThroughController(input2) {
14846
15220
  removeSignals();
14847
15221
  }
14848
15222
  }
14849
- function renewLifecycleCapacity(request2, policy, samples, controller, directory = import_node_path43.default.join(DEVROUTER_HOME, "controller"), nowMs = Date.now()) {
15223
+ function renewLifecycleCapacity(request2, policy, samples, controller, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller"), nowMs = Date.now()) {
14850
15224
  return updateReliabilityOperation(request2.identity, (record5) => {
14851
15225
  const binding = record5.capacity;
14852
15226
  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 +15298,7 @@ function retireQueuedLifecycle(request2, supersededOnly = false) {
14924
15298
  return true;
14925
15299
  });
14926
15300
  }
14927
- function bindLifecycleCapacity(request2, binding, directory = import_node_path43.default.join(DEVROUTER_HOME, "controller")) {
15301
+ function bindLifecycleCapacity(request2, binding, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller")) {
14928
15302
  if (request2.kind === "stop") throw new Error("Stop never requires capacity admission.");
14929
15303
  updateReliabilityOperation(request2.identity, (record5) => {
14930
15304
  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 +15313,12 @@ function bindLifecycleCapacity(request2, binding, directory = import_node_path43
14939
15313
  stepRecord(record5, { ...request2.fence, type: "admission", result: "admitted" });
14940
15314
  });
14941
15315
  }
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;
15316
+ 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
15317
  var init_reliability_lifecycle = __esm({
14944
15318
  "src/core/reliability-lifecycle.ts"() {
14945
15319
  "use strict";
14946
15320
  import_node_crypto21 = require("crypto");
14947
- import_node_path43 = __toESM(require("path"));
15321
+ import_node_path44 = __toESM(require("path"));
14948
15322
  import_node_util10 = require("util");
14949
15323
  init_capacity_accounting();
14950
15324
  init_capacity_policy();
@@ -15431,7 +15805,7 @@ function createCapacityController(options) {
15431
15805
  const record5 = readReliabilityOperation(identity2);
15432
15806
  if (!record5?.capacity || !record5.preparation && !record5.capacity.execSteady || record5.worker || !record5.state.operation?.drained || !["ensure", "exec"].includes(record5.state.operation.kind))
15433
15807
  continue;
15434
- const bytes2 = readControllerEvidence(import_node_path44.default.join(enrollment.repoPath, ".devrouter.yml"));
15808
+ const bytes2 = readControllerEvidence(import_node_path45.default.join(enrollment.repoPath, ".devrouter.yml"));
15435
15809
  const estimates = loadRepoConfig(enrollment.repoPath, () => bytes2).capacity;
15436
15810
  if (!estimates) continue;
15437
15811
  settlePreparedLifecycleCapacity({
@@ -15591,12 +15965,12 @@ function createCapacityController(options) {
15591
15965
  }
15592
15966
  };
15593
15967
  }
15594
- var import_node_crypto22, import_node_path44, import_node_util12;
15968
+ var import_node_crypto22, import_node_path45, import_node_util12;
15595
15969
  var init_capacity_controller = __esm({
15596
15970
  "src/core/capacity-controller.ts"() {
15597
15971
  "use strict";
15598
15972
  import_node_crypto22 = require("crypto");
15599
- import_node_path44 = __toESM(require("path"));
15973
+ import_node_path45 = __toESM(require("path"));
15600
15974
  import_node_util12 = require("util");
15601
15975
  init_capacity_docker_probe();
15602
15976
  init_capacity_enrollment();
@@ -15937,12 +16311,12 @@ async function containers(project, signal) {
15937
16311
  }
15938
16312
  return values.sort((a, b) => a.id.localeCompare(b.id));
15939
16313
  }
15940
- var import_node_crypto24, import_node_path45, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
16314
+ var import_node_crypto24, import_node_path46, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
15941
16315
  var init_controller_observation = __esm({
15942
16316
  "src/core/controller-observation.ts"() {
15943
16317
  "use strict";
15944
16318
  import_node_crypto24 = require("crypto");
15945
- import_node_path45 = __toESM(require("path"));
16319
+ import_node_path46 = __toESM(require("path"));
15946
16320
  init_controller_binding();
15947
16321
  init_controller_monitor();
15948
16322
  init_controller_probe();
@@ -15985,14 +16359,14 @@ var init_controller_observation = __esm({
15985
16359
  )).trim().split("\n");
15986
16360
  if (metadata.length !== 2) throw new Error("Observation ownership paths unavailable.");
15987
16361
  const [gitDir, commonDir] = metadata;
15988
- const sourcePath = import_node_path45.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
16362
+ const sourcePath = import_node_path46.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
15989
16363
  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"),
16364
+ import_node_path46.default.join(commonDir, "devrouter/workspaces", `${environment.workspace}.json`),
16365
+ import_node_path46.default.join(environment.repoPath, ".devrouter.yml"),
16366
+ import_node_path46.default.join(gitDir, "devrouter-workspace"),
15993
16367
  managedRuntimeStatePath(environment.repoPath, environment.workspace),
15994
16368
  sourcePath,
15995
- import_node_path45.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
16369
+ import_node_path46.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
15996
16370
  ];
15997
16371
  const persisted = captureControllerEvidence(files);
15998
16372
  if (controllerBindingFingerprint(persisted.contents[0], persisted.contents[1]) !== environment.fingerprint)
@@ -16017,7 +16391,7 @@ var init_controller_observation = __esm({
16017
16391
  if (!journal) throw new Error("Observation journal is unavailable.");
16018
16392
  const routes = readRoutes().filter((route) => route.repoPath === environment.repoPath);
16019
16393
  const before = await containers(state.composeProject, signal);
16020
- const composeDirectory = import_node_path45.default.join(environment.repoPath, ".devcontainer");
16394
+ const composeDirectory = import_node_path46.default.join(environment.repoPath, ".devcontainer");
16021
16395
  const references = typeof source.dockerComposeFile === "string" ? [source.dockerComposeFile] : source.dockerComposeFile;
16022
16396
  if (!Array.isArray(references) || !references.length || references.some((value) => typeof value !== "string"))
16023
16397
  throw new Error("Observation Compose source is unavailable.");
@@ -16034,7 +16408,7 @@ var init_controller_observation = __esm({
16034
16408
  if (!recorded || container.labels["com.docker.compose.project.working_dir"] !== composeDirectory)
16035
16409
  throw new Error("Observation Compose ownership is unavailable.");
16036
16410
  const files2 = recorded.split(",").map((file) => file.trim());
16037
- if (files2.some((file) => !import_node_path45.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
16411
+ if (files2.some((file) => !import_node_path46.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
16038
16412
  repoPath: environment.repoPath,
16039
16413
  service: container.labels["com.docker.compose.service"] ?? "",
16040
16414
  composeProject: state.composeProject,
@@ -16393,26 +16767,26 @@ var init_controller_sessions = __esm({
16393
16767
 
16394
16768
  // src/core/controller-server.ts
16395
16769
  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);
16770
+ const absolute2 = import_node_path47.default.resolve(directory);
16771
+ let current = import_node_path47.default.parse(absolute2).root;
16772
+ for (const part of absolute2.slice(current.length).split(import_node_path47.default.sep)) {
16773
+ current = import_node_path47.default.join(current, part);
16400
16774
  try {
16401
- const stat2 = import_node_fs35.default.lstatSync(current);
16775
+ const stat2 = import_node_fs36.default.lstatSync(current);
16402
16776
  if (!stat2.isDirectory() || stat2.isSymbolicLink())
16403
16777
  throw new Error("Unsafe controller directory.");
16404
16778
  } catch (error) {
16405
16779
  if (error.code !== "ENOENT") throw error;
16406
- import_node_fs35.default.mkdirSync(current, { mode: 448 });
16780
+ import_node_fs36.default.mkdirSync(current, { mode: 448 });
16407
16781
  }
16408
16782
  }
16409
- const stat = import_node_fs35.default.lstatSync(absolute2);
16783
+ const stat = import_node_fs36.default.lstatSync(absolute2);
16410
16784
  if (stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
16411
16785
  throw new Error("Controller directory is not private.");
16412
16786
  }
16413
16787
  function validateOwnedFile(file, socket) {
16414
16788
  try {
16415
- const stat = import_node_fs35.default.lstatSync(file);
16789
+ const stat = import_node_fs36.default.lstatSync(file);
16416
16790
  if (stat.uid !== process.getuid?.() || (socket ? !stat.isSocket() : !stat.isFile()) || (stat.mode & 63) !== 0)
16417
16791
  throw new Error("Unsafe controller artifact.");
16418
16792
  return true;
@@ -16424,19 +16798,19 @@ function validateOwnedFile(file, socket) {
16424
16798
  async function runController(options) {
16425
16799
  if (options.operations && options.createOperations)
16426
16800
  throw new Error("Controller operations have multiple owners.");
16427
- const socketPath = import_node_path46.default.join(options.directory, "control.sock");
16801
+ const socketPath = import_node_path47.default.join(options.directory, "control.sock");
16428
16802
  if (Buffer.byteLength(socketPath) > 103) throw new Error("Controller socket path is too long.");
16429
16803
  privateDirectory(options.directory);
16430
- const lockPath = import_node_path46.default.join(options.directory, "owner.lock");
16804
+ const lockPath = import_node_path47.default.join(options.directory, "owner.lock");
16431
16805
  try {
16432
- const stat = import_node_fs35.default.lstatSync(lockPath);
16806
+ const stat = import_node_fs36.default.lstatSync(lockPath);
16433
16807
  if (!stat.isFile() || stat.uid !== process.getuid?.())
16434
16808
  throw new Error("Unsafe controller owner lock.");
16435
16809
  } catch (error) {
16436
16810
  if (error.code !== "ENOENT") throw error;
16437
16811
  }
16438
16812
  await withFileLock(lockPath, { activity: "controller ownership", waitMs: 0 }, async () => {
16439
- if (validateOwnedFile(socketPath, true)) import_node_fs35.default.unlinkSync(socketPath);
16813
+ if (validateOwnedFile(socketPath, true)) import_node_fs36.default.unlinkSync(socketPath);
16440
16814
  const sessions = new ControllerSessions(new ControllerStore(options.directory));
16441
16815
  const incarnation = sessions.read();
16442
16816
  let startupAvailable = true;
@@ -16815,7 +17189,7 @@ async function runController(options) {
16815
17189
  resolve();
16816
17190
  });
16817
17191
  });
16818
- import_node_fs35.default.chmodSync(socketPath, 384);
17192
+ import_node_fs36.default.chmodSync(socketPath, 384);
16819
17193
  await new Promise((resolve) => {
16820
17194
  finish = resolve;
16821
17195
  options.signal.addEventListener("abort", shutdown, { once: true });
@@ -16834,13 +17208,13 @@ async function runController(options) {
16834
17208
  }
16835
17209
  });
16836
17210
  }
16837
- var import_node_fs35, import_node_net4, import_node_path46, FRAME_BYTES;
17211
+ var import_node_fs36, import_node_net4, import_node_path47, FRAME_BYTES;
16838
17212
  var init_controller_server = __esm({
16839
17213
  "src/core/controller-server.ts"() {
16840
17214
  "use strict";
16841
- import_node_fs35 = __toESM(require("fs"));
17215
+ import_node_fs36 = __toESM(require("fs"));
16842
17216
  import_node_net4 = __toESM(require("net"));
16843
- import_node_path46 = __toESM(require("path"));
17217
+ import_node_path47 = __toESM(require("path"));
16844
17218
  init_controller_monitor();
16845
17219
  init_controller_protocol();
16846
17220
  init_controller_sessions();
@@ -16857,7 +17231,7 @@ __export(controller_exports, {
16857
17231
  runControllerCommand: () => runControllerCommand
16858
17232
  });
16859
17233
  async function runControllerCommand(method, options, repo, dependencies = {}) {
16860
- const directory = import_node_path47.default.join(DEVROUTER_HOME, "controller");
17234
+ const directory = import_node_path48.default.join(DEVROUTER_HOME, "controller");
16861
17235
  try {
16862
17236
  if (method === "run") {
16863
17237
  const controller = new AbortController();
@@ -16892,7 +17266,7 @@ async function runControllerCommand(method, options, repo, dependencies = {}) {
16892
17266
  if (fields2.timeout !== void 0) fields2.timeout = Number(fields2.timeout);
16893
17267
  await controllerRequest(
16894
17268
  directory,
16895
- { method, ...fields2, ...repo ? { path: import_node_path47.default.resolve(repo) } : {} },
17269
+ { method, ...fields2, ...repo ? { path: import_node_path48.default.resolve(repo) } : {} },
16896
17270
  (value) => process.stdout.write(`${JSON.stringify(value)}
16897
17271
  `)
16898
17272
  );
@@ -16907,11 +17281,11 @@ async function runControllerCommand(method, options, repo, dependencies = {}) {
16907
17281
  process.exitCode = 1;
16908
17282
  }
16909
17283
  }
16910
- var import_node_path47;
17284
+ var import_node_path48;
16911
17285
  var init_controller = __esm({
16912
17286
  "src/commands/controller.ts"() {
16913
17287
  "use strict";
16914
- import_node_path47 = __toESM(require("path"));
17288
+ import_node_path48 = __toESM(require("path"));
16915
17289
  init_capacity_collector();
16916
17290
  init_capacity_controller();
16917
17291
  init_capacity_policy();
@@ -16962,28 +17336,28 @@ function replaceDevrouterSection(content) {
16962
17336
  return `${content.slice(0, start)}${buildDevrouterSection()}${content.slice(end)}`;
16963
17337
  }
16964
17338
  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");
17339
+ const absolutePath = (0, import_node_path49.join)(repoPath, relPath);
17340
+ (0, import_node_fs37.mkdirSync)((0, import_node_path49.dirname)(absolutePath), { recursive: true });
17341
+ (0, import_node_fs37.writeFileSync)(absolutePath, content, "utf-8");
16968
17342
  return absolutePath;
16969
17343
  }
16970
17344
  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");
17345
+ const filePath = (0, import_node_path49.join)(repoPath, AGENTS_MD);
17346
+ if ((0, import_node_fs37.existsSync)(filePath)) {
17347
+ const content = (0, import_node_fs37.readFileSync)(filePath, "utf-8");
16974
17348
  if (content.includes(DEVROUTER_SENTINEL)) {
16975
17349
  const updated = replaceDevrouterSection(content);
16976
17350
  if (updated === void 0 || updated === content) return { path: filePath, written: false };
16977
- (0, import_node_fs36.writeFileSync)(filePath, updated, "utf-8");
17351
+ (0, import_node_fs37.writeFileSync)(filePath, updated, "utf-8");
16978
17352
  return { path: filePath, written: true };
16979
17353
  }
16980
- (0, import_node_fs36.writeFileSync)(filePath, `${content.trimEnd()}
17354
+ (0, import_node_fs37.writeFileSync)(filePath, `${content.trimEnd()}
16981
17355
 
16982
17356
  ${buildDevrouterSection()}
16983
17357
  `, "utf-8");
16984
17358
  return { path: filePath, written: true };
16985
17359
  }
16986
- (0, import_node_fs36.writeFileSync)(filePath, `# AGENTS.md
17360
+ (0, import_node_fs37.writeFileSync)(filePath, `# AGENTS.md
16987
17361
 
16988
17362
  ${buildDevrouterSection()}
16989
17363
  `, "utf-8");
@@ -16993,12 +17367,12 @@ function ensureSkillFile(repoPath) {
16993
17367
  const filePath = writeRepoFile(repoPath, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT);
16994
17368
  return { path: filePath, written: true };
16995
17369
  }
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;
17370
+ 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
17371
  var init_agents_md = __esm({
16998
17372
  "src/core/agents-md.ts"() {
16999
17373
  "use strict";
17000
- import_node_fs36 = require("fs");
17001
- import_node_path48 = require("path");
17374
+ import_node_fs37 = require("fs");
17375
+ import_node_path49 = require("path");
17002
17376
  DEVROUTER_SENTINEL = "<!-- devrouter -->";
17003
17377
  DEVROUTER_END_SENTINEL = "<!-- /devrouter -->";
17004
17378
  LEGACY_DEVROUTER_SECTION_END = "- `devrouter ls`";
@@ -17459,6 +17833,7 @@ function buildOnboardingPrompt(options = {}) {
17459
17833
  "- if kind=app and runtime=proxy:",
17460
17834
  " - 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
17835
  " - 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.",
17836
+ " - 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
17837
  ' - 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
17838
  ` - 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
17839
  " - 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 +17940,7 @@ function buildOnboardingPrompt(options = {}) {
17565
17940
  "- Avoid pre-wrapper DB assignments such as `DATABASE_URI=... <wrapper> run -- ...`; wrapper-managed env may override those values.",
17566
17941
  "- `devrouter doctor --repo <REPO_PATH>` warns on risky pre-wrapper DB assignments before `run --` for host apps that depend on postgres.",
17567
17942
  "- With TLS enabled, `devrouter doctor --repo <REPO_PATH>` also warns on cert SAN mismatches for configured hosts (`repo.tls-host-coverage`).",
17943
+ "- `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
17944
  "- 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
17945
  "- Robust one-shot seed example: `devrouter app exec <name> --repo <REPO_PATH> --yes -- infisical run --projectId <id> --env=<env> -- pnpm payload seed`.",
17570
17946
  "- 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 +18651,11 @@ function extractCurrentVersionFromRepoConfig(repoPath) {
18275
18651
  };
18276
18652
  }
18277
18653
  function readPromptDirectory(promptDirPath) {
18278
- if (!import_node_fs37.default.existsSync(promptDirPath)) {
18654
+ if (!import_node_fs38.default.existsSync(promptDirPath)) {
18279
18655
  throw new Error(`Missing ${UPGRADE_PROMPTS_DIR} directory at ${promptDirPath}.`);
18280
18656
  }
18281
18657
  const releases = [];
18282
- const files = import_node_fs37.default.readdirSync(promptDirPath, { withFileTypes: true });
18658
+ const files = import_node_fs38.default.readdirSync(promptDirPath, { withFileTypes: true });
18283
18659
  for (const file of files) {
18284
18660
  if (!file.isFile() || !file.name.endsWith(".md")) {
18285
18661
  continue;
@@ -18289,8 +18665,8 @@ function readPromptDirectory(promptDirPath) {
18289
18665
  continue;
18290
18666
  }
18291
18667
  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();
18668
+ const promptPath = import_node_path50.default.join(promptDirPath, file.name);
18669
+ const prompt = import_node_fs38.default.readFileSync(promptPath, "utf-8").trim();
18294
18670
  releases.push({ version, prompt, promptPath });
18295
18671
  }
18296
18672
  const deduped = /* @__PURE__ */ new Map();
@@ -18311,19 +18687,19 @@ function listAvailableUpgradeTargets(currentVersion, releases) {
18311
18687
  }
18312
18688
  function resolvePromptDirectory(explicitPath) {
18313
18689
  if (explicitPath) {
18314
- return import_node_path49.default.resolve(explicitPath);
18690
+ return import_node_path50.default.resolve(explicitPath);
18315
18691
  }
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;
18692
+ const entryFile = process.argv[1] ? import_node_path50.default.resolve(process.argv[1]) : __filename;
18693
+ const entryDir = import_node_path50.default.dirname(entryFile);
18694
+ const resolvedEntryDir = import_node_fs38.default.existsSync(entryFile) ? import_node_path50.default.dirname(import_node_fs38.default.realpathSync(entryFile)) : entryDir;
18319
18695
  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)
18696
+ import_node_path50.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
18697
+ import_node_path50.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
18698
+ import_node_path50.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
18699
+ import_node_path50.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
18700
+ import_node_path50.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
18325
18701
  ];
18326
- const existing = candidates.find((candidate) => import_node_fs37.default.existsSync(candidate));
18702
+ const existing = candidates.find((candidate) => import_node_fs38.default.existsSync(candidate));
18327
18703
  return existing ?? candidates[0];
18328
18704
  }
18329
18705
  function loadUpgradeCatalog(options = {}) {
@@ -18339,12 +18715,12 @@ function loadUpgradeCatalog(options = {}) {
18339
18715
  releases
18340
18716
  };
18341
18717
  }
18342
- var import_node_fs37, import_node_path49, SEMVER_RE, UPGRADE_PROMPTS_DIR;
18718
+ var import_node_fs38, import_node_path50, SEMVER_RE, UPGRADE_PROMPTS_DIR;
18343
18719
  var init_upgrade = __esm({
18344
18720
  "src/core/upgrade.ts"() {
18345
18721
  "use strict";
18346
- import_node_fs37 = __toESM(require("fs"));
18347
- import_node_path49 = __toESM(require("path"));
18722
+ import_node_fs38 = __toESM(require("fs"));
18723
+ import_node_path50 = __toESM(require("path"));
18348
18724
  init_repo_config();
18349
18725
  SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
18350
18726
  UPGRADE_PROMPTS_DIR = "upgrade-prompts";
@@ -18487,7 +18863,7 @@ function isLoopbackHost(host) {
18487
18863
  return host === "127.0.0.1" || host === "localhost" || host === "host.docker.internal";
18488
18864
  }
18489
18865
  function inspectCompose(composeFile, _workspace) {
18490
- if (!import_node_fs38.default.existsSync(composeFile)) {
18866
+ if (!import_node_fs39.default.existsSync(composeFile)) {
18491
18867
  return {
18492
18868
  aliases: [],
18493
18869
  publishedPorts: [],
@@ -18496,7 +18872,7 @@ function inspectCompose(composeFile, _workspace) {
18496
18872
  };
18497
18873
  }
18498
18874
  try {
18499
- const raw = import_node_fs38.default.readFileSync(composeFile, "utf-8");
18875
+ const raw = import_node_fs39.default.readFileSync(composeFile, "utf-8");
18500
18876
  const parsed = import_yaml5.default.parse(raw);
18501
18877
  const root = asRecord(parsed);
18502
18878
  const services = asRecord(root?.services);
@@ -18552,12 +18928,51 @@ function routedProxyApps(config) {
18552
18928
  (app) => app.kind !== "dependency" && app.runtime === "proxy"
18553
18929
  );
18554
18930
  }
18931
+ function buildHostPortClaimsCheck(repoPath, workspace) {
18932
+ try {
18933
+ const linked = isLinkedWorktree(repoPath);
18934
+ const runtime = loadRuntimeConfig(repoPath);
18935
+ const plan = inspectManagedDevcontainerConfig({
18936
+ repoPath,
18937
+ config: runtime.config,
18938
+ profile: runtime.resolvedProfile,
18939
+ linked
18940
+ });
18941
+ const gitCommonDir = linked ? resolveGitCommonDir(repoPath) : void 0;
18942
+ const workspaceEnv = linked && workspace && gitCommonDir ? { token: workspace, gitCommonDir } : void 0;
18943
+ const conflicts = detectHostPortClaimConflicts({ repoPath, plan, workspace: workspaceEnv });
18944
+ if (conflicts.length === 0) {
18945
+ return {
18946
+ id: "repo.host-port-claims",
18947
+ level: "ok",
18948
+ summary: "No configured fixed host-port binding conflicts with a running container."
18949
+ };
18950
+ }
18951
+ return {
18952
+ id: "repo.host-port-claims",
18953
+ level: "error",
18954
+ summary: `${conflicts.length} configured fixed host-port binding${conflicts.length === 1 ? "" : "s"} conflict${conflicts.length === 1 ? "s" : ""} with running containers.`,
18955
+ details: conflicts.map(
18956
+ (conflict) => `${conflict.service}: ${conflict.hostIp ?? "*"}:${conflict.hostPort}/${conflict.protocol} held by '${conflict.holderContainer}'` + (conflict.holderWorkspace ? ` (workspace '${conflict.holderWorkspace}')` : "")
18957
+ ).join("; "),
18958
+ suggestion: "Stop the holding workspace or change the consumer's own published binding, then run ensure again. Devrouter never rewrites consumer-declared host bindings."
18959
+ };
18960
+ } catch (error) {
18961
+ return {
18962
+ id: "repo.host-port-claims",
18963
+ level: "warn",
18964
+ summary: "Could not compare configured fixed host-port bindings against live holders.",
18965
+ details: error instanceof Error ? error.message : String(error),
18966
+ suggestion: "Check Docker availability; a managed ensure refuses on this evidence before starting."
18967
+ };
18968
+ }
18969
+ }
18555
18970
  function buildDevcontainerChecks(repoPath, config, workspace) {
18556
- const devcontainerDir = import_node_path50.default.join(repoPath, ".devcontainer");
18557
- if (!import_node_fs38.default.existsSync(devcontainerDir)) {
18971
+ const devcontainerDir = import_node_path51.default.join(repoPath, ".devcontainer");
18972
+ if (!import_node_fs39.default.existsSync(devcontainerDir)) {
18558
18973
  return [];
18559
18974
  }
18560
- const compose = inspectCompose(import_node_path50.default.join(devcontainerDir, "docker-compose.yml"), workspace);
18975
+ const compose = inspectCompose(import_node_path51.default.join(devcontainerDir, "docker-compose.yml"), workspace);
18561
18976
  const checks = [];
18562
18977
  if (compose.parseError) {
18563
18978
  checks.push({
@@ -18584,9 +18999,9 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
18584
18999
  details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
18585
19000
  suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
18586
19001
  });
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") : "";
19002
+ const dockerfilePath = import_node_path51.default.join(devcontainerDir, "Dockerfile");
19003
+ const dockerfileExists = import_node_fs39.default.existsSync(dockerfilePath);
19004
+ const dockerfile = dockerfileExists ? import_node_fs39.default.readFileSync(dockerfilePath, "utf-8") : "";
18590
19005
  const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
18591
19006
  (artifact) => dockerfile.includes(artifact)
18592
19007
  );
@@ -18623,6 +19038,9 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
18623
19038
  });
18624
19039
  return checks;
18625
19040
  }
19041
+ if (config.managedRuntime) {
19042
+ checks.push(buildHostPortClaimsCheck(repoPath, workspace));
19043
+ }
18626
19044
  const aliasSet = new Set(
18627
19045
  compose.aliases.flatMap((alias) => expandAliasCandidates(alias, workspace)).map(normalizeWorkspaceToken)
18628
19046
  );
@@ -18641,14 +19059,19 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
18641
19059
  });
18642
19060
  return checks;
18643
19061
  }
18644
- var import_node_fs38, import_node_path50, import_yaml5;
19062
+ var import_node_fs39, import_node_path51, import_yaml5;
18645
19063
  var init_devcontainer_diagnostics = __esm({
18646
19064
  "src/core/devcontainer-diagnostics.ts"() {
18647
19065
  "use strict";
18648
- import_node_fs38 = __toESM(require("fs"));
18649
- import_node_path50 = __toESM(require("path"));
19066
+ import_node_fs39 = __toESM(require("fs"));
19067
+ import_node_path51 = __toESM(require("path"));
18650
19068
  import_yaml5 = __toESM(require("yaml"));
19069
+ init_devcontainer_profile();
19070
+ init_host_port_claims();
18651
19071
  init_managed_post_start();
19072
+ init_repo_config();
19073
+ init_workspace();
19074
+ init_workspace_ownership();
18652
19075
  }
18653
19076
  });
18654
19077
 
@@ -18736,11 +19159,11 @@ function reconcileRouteRunConflict(repoPath, app) {
18736
19159
  }
18737
19160
  }
18738
19161
  }
18739
- var import_node_fs39;
19162
+ var import_node_fs40;
18740
19163
  var init_route_state = __esm({
18741
19164
  "src/core/route-state.ts"() {
18742
19165
  "use strict";
18743
- import_node_fs39 = __toESM(require("fs"));
19166
+ import_node_fs40 = __toESM(require("fs"));
18744
19167
  init_host_routes();
18745
19168
  init_workspace();
18746
19169
  }
@@ -18915,7 +19338,7 @@ function toRepoStatus(repoPath) {
18915
19338
  const resolvedRepoPath = resolveRepoPath(repoPath);
18916
19339
  const configPath = getRepoConfigPath(resolvedRepoPath);
18917
19340
  const explicitRepo = typeof repoPath === "string" && repoPath.trim().length > 0;
18918
- const configExists = import_node_fs40.default.existsSync(configPath);
19341
+ const configExists = import_node_fs41.default.existsSync(configPath);
18919
19342
  if (!explicitRepo && !configExists) {
18920
19343
  return void 0;
18921
19344
  }
@@ -19063,11 +19486,11 @@ async function collectRouterStatus(repoPath) {
19063
19486
  }
19064
19487
  };
19065
19488
  }
19066
- var import_node_fs40;
19489
+ var import_node_fs41;
19067
19490
  var init_status = __esm({
19068
19491
  "src/core/status.ts"() {
19069
19492
  "use strict";
19070
- import_node_fs40 = __toESM(require("fs"));
19493
+ import_node_fs41 = __toESM(require("fs"));
19071
19494
  init_docker();
19072
19495
  init_managed_runtime_state();
19073
19496
  init_managed_runtime_status();
@@ -19084,7 +19507,7 @@ function outputFromResult(result) {
19084
19507
  return output2.length > 0 ? output2 : void 0;
19085
19508
  }
19086
19509
  function runTool(command, args = []) {
19087
- const result = (0, import_node_child_process28.spawnSync)(command, args, {
19510
+ const result = (0, import_node_child_process29.spawnSync)(command, args, {
19088
19511
  encoding: "utf-8"
19089
19512
  });
19090
19513
  if (result.error) {
@@ -19141,12 +19564,12 @@ function parseMinimumNodeMajor(value) {
19141
19564
  return Number(match[1]);
19142
19565
  }
19143
19566
  function readPackageJson(repoPath) {
19144
- const packagePath = import_node_path51.default.join(repoPath, "package.json");
19145
- if (!import_node_fs41.default.existsSync(packagePath)) {
19567
+ const packagePath = import_node_path52.default.join(repoPath, "package.json");
19568
+ if (!import_node_fs42.default.existsSync(packagePath)) {
19146
19569
  return void 0;
19147
19570
  }
19148
19571
  try {
19149
- return JSON.parse(import_node_fs41.default.readFileSync(packagePath, "utf-8"));
19572
+ return JSON.parse(import_node_fs42.default.readFileSync(packagePath, "utf-8"));
19150
19573
  } catch {
19151
19574
  return void 0;
19152
19575
  }
@@ -19330,13 +19753,13 @@ function buildGlobalToolChecksWithoutTelemetry(repoPath) {
19330
19753
  checks.push(nodeToolchainCheck(repoPath));
19331
19754
  return checks;
19332
19755
  }
19333
- var import_node_child_process28, import_node_fs41, import_node_path51, DEVSY_DISABLE_TELEMETRY;
19756
+ var import_node_child_process29, import_node_fs42, import_node_path52, DEVSY_DISABLE_TELEMETRY;
19334
19757
  var init_tool_diagnostics = __esm({
19335
19758
  "src/core/tool-diagnostics.ts"() {
19336
19759
  "use strict";
19337
- import_node_child_process28 = require("child_process");
19338
- import_node_fs41 = __toESM(require("fs"));
19339
- import_node_path51 = __toESM(require("path"));
19760
+ import_node_child_process29 = require("child_process");
19761
+ import_node_fs42 = __toESM(require("fs"));
19762
+ import_node_path52 = __toESM(require("path"));
19340
19763
  init_devsy_agent();
19341
19764
  init_workspace_runtime();
19342
19765
  DEVSY_DISABLE_TELEMETRY = "DEVSY_DISABLE_TELEMETRY";
@@ -19353,10 +19776,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
19353
19776
  if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
19354
19777
  const comparableRepo = comparableWorkspacePath(repoPath);
19355
19778
  const comparableWorktree = comparableWorkspacePath(worktreePath);
19356
- const localRoot = import_node_path52.default.join(comparableRepo, "trees") + import_node_path52.default.sep;
19779
+ const localRoot = import_node_path53.default.join(comparableRepo, "trees") + import_node_path53.default.sep;
19357
19780
  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);
19781
+ const legacyPrefix = `${import_node_path53.default.basename(comparableRepo)}-`;
19782
+ return import_node_path53.default.dirname(comparableWorktree) === import_node_path53.default.dirname(comparableRepo) && import_node_path53.default.basename(comparableWorktree).startsWith(legacyPrefix);
19360
19783
  }
19361
19784
  function previewActions(devpodStatus, routeCount, includeRecord) {
19362
19785
  const actions = [
@@ -19608,11 +20031,11 @@ function applyWorkspaceGc(plan) {
19608
20031
  candidates
19609
20032
  };
19610
20033
  }
19611
- var import_node_path52;
20034
+ var import_node_path53;
19612
20035
  var init_workspace_gc = __esm({
19613
20036
  "src/core/workspace-gc.ts"() {
19614
20037
  "use strict";
19615
- import_node_path52 = __toESM(require("path"));
20038
+ import_node_path53 = __toESM(require("path"));
19616
20039
  init_devpod_mutation();
19617
20040
  init_devpod_workspaces();
19618
20041
  init_host_routes();
@@ -19682,11 +20105,11 @@ function inspectPostgresCredentials(repoPath, config) {
19682
20105
  parseErrors.push(`${app.name}: ${composeFile} (${message})`);
19683
20106
  continue;
19684
20107
  }
19685
- if (!import_node_fs42.default.existsSync(absolutePath)) {
20108
+ if (!import_node_fs43.default.existsSync(absolutePath)) {
19686
20109
  continue;
19687
20110
  }
19688
20111
  try {
19689
- const raw = import_node_fs42.default.readFileSync(absolutePath, "utf-8");
20112
+ const raw = import_node_fs43.default.readFileSync(absolutePath, "utf-8");
19690
20113
  const parsed = import_yaml6.default.parse(raw);
19691
20114
  const root = asRecord2(parsed);
19692
20115
  const services = asRecord2(root?.services);
@@ -19966,7 +20389,7 @@ async function buildDoctorReport(options = {}) {
19966
20389
  const config = runtimeConfig.config;
19967
20390
  loadedConfig = config;
19968
20391
  loadedWorkspace = runtimeConfig.workspace;
19969
- const cliVersion = true ? "0.0.68" : "0.0.0-dev";
20392
+ const cliVersion = true ? "0.0.70" : "0.0.0-dev";
19970
20393
  const configVersion = config.devrouter?.version;
19971
20394
  if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
19972
20395
  addCheck(checks, {
@@ -19996,9 +20419,9 @@ async function buildDoctorReport(options = {}) {
19996
20419
  (app) => app.docker.composeFiles.map((filePath) => ({
19997
20420
  app: app.name,
19998
20421
  filePath,
19999
- absolutePath: import_node_path53.default.resolve(repo.path, filePath)
20422
+ absolutePath: import_node_path54.default.resolve(repo.path, filePath)
20000
20423
  }))
20001
- ).filter((entry) => !import_node_fs42.default.existsSync(entry.absolutePath));
20424
+ ).filter((entry) => !import_node_fs43.default.existsSync(entry.absolutePath));
20002
20425
  addCheck(checks, {
20003
20426
  id: "repo.compose-files",
20004
20427
  level: missingComposeFiles.length === 0 ? "ok" : "error",
@@ -20041,8 +20464,8 @@ async function buildDoctorReport(options = {}) {
20041
20464
  const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
20042
20465
  app: app.name,
20043
20466
  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));
20467
+ absolutePath: import_node_path54.default.resolve(repo.path, app.hostRun.cwd)
20468
+ })).filter((entry) => !import_node_fs43.default.existsSync(entry.absolutePath));
20046
20469
  addCheck(checks, {
20047
20470
  id: "repo.host-cwd",
20048
20471
  level: missingHostCwds.length === 0 ? "ok" : "error",
@@ -20186,12 +20609,12 @@ async function buildDoctorReport(options = {}) {
20186
20609
  nextSteps
20187
20610
  };
20188
20611
  }
20189
- var import_node_fs42, import_node_path53, import_yaml6, POSTGRES_DEFAULTS;
20612
+ var import_node_fs43, import_node_path54, import_yaml6, POSTGRES_DEFAULTS;
20190
20613
  var init_doctor = __esm({
20191
20614
  "src/core/doctor.ts"() {
20192
20615
  "use strict";
20193
- import_node_fs42 = __toESM(require("fs"));
20194
- import_node_path53 = __toESM(require("path"));
20616
+ import_node_fs43 = __toESM(require("fs"));
20617
+ import_node_path54 = __toESM(require("path"));
20195
20618
  import_yaml6 = __toESM(require("yaml"));
20196
20619
  init_docker();
20197
20620
  init_host_routes();
@@ -20507,12 +20930,12 @@ function parseSsPortListeners(stdout, port) {
20507
20930
  return listeners;
20508
20931
  }
20509
20932
  function findPortListeners(port) {
20510
- const result = (0, import_node_child_process29.spawnSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
20933
+ const result = (0, import_node_child_process30.spawnSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
20511
20934
  encoding: "utf-8"
20512
20935
  });
20513
20936
  if (result.error && result.error.code === "ENOENT") {
20514
20937
  if (process.platform === "linux") {
20515
- const ssResult = (0, import_node_child_process29.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
20938
+ const ssResult = (0, import_node_child_process30.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
20516
20939
  if (ssResult.status === 0 && ssResult.stdout) {
20517
20940
  return parseSsPortListeners(ssResult.stdout, port);
20518
20941
  }
@@ -20541,11 +20964,11 @@ function findPortListeners(port) {
20541
20964
  };
20542
20965
  });
20543
20966
  }
20544
- var import_node_child_process29;
20967
+ var import_node_child_process30;
20545
20968
  var init_ports = __esm({
20546
20969
  "src/util/ports.ts"() {
20547
20970
  "use strict";
20548
- import_node_child_process29 = require("child_process");
20971
+ import_node_child_process30 = require("child_process");
20549
20972
  }
20550
20973
  });
20551
20974
 
@@ -20632,14 +21055,33 @@ async function runEnsureCommand(options) {
20632
21055
  profile: options.profile,
20633
21056
  ...options.repair ? { repair: true } : {}
20634
21057
  });
21058
+ const conflicts = result.hostPortConflicts ?? [];
20635
21059
  const applicationFailed = result.applicationReadiness?.status === "application-error";
20636
- if (applicationFailed) process.exitCode = 1;
21060
+ if (applicationFailed || conflicts.length > 0) process.exitCode = 1;
20637
21061
  if (options.json) {
20638
21062
  process.stdout.write(`${JSON.stringify(result, null, 2)}
20639
21063
  `);
20640
21064
  return;
20641
21065
  }
20642
21066
  const label2 = result.kind === "primary" ? `Primary checkout [profile: ${result.profile}]` : `Workspace '${result.workspace}' [profile: ${result.profile}]`;
21067
+ if (conflicts.length > 0) {
21068
+ const lines = conflicts.map((conflict) => {
21069
+ const desired = `${conflict.hostIp ?? "*"}:${conflict.hostPort}/${conflict.protocol}`;
21070
+ const holder = [
21071
+ `'${conflict.holderContainer}'`,
21072
+ conflict.holderComposeProject ? `compose project '${conflict.holderComposeProject}'` : void 0,
21073
+ conflict.holderWorkspace ? `workspace '${conflict.holderWorkspace}'` : void 0
21074
+ ].filter(Boolean).join(", ");
21075
+ return ` - service '${conflict.service}' needs ${desired}, held by ${holder}
21076
+ ${conflict.remediation}`;
21077
+ });
21078
+ process.stdout.write(
21079
+ `${label2} was not started: ${conflicts.length} fixed host-port claim${conflicts.length === 1 ? "" : "s"} conflict${conflicts.length === 1 ? "s" : ""} with running containers.
21080
+ ${lines.join("\n")}
21081
+ `
21082
+ );
21083
+ return;
21084
+ }
20643
21085
  const routes = result.urls.map((url) => ` ${url}`).join("\n");
20644
21086
  process.stdout.write(
20645
21087
  `${label2} ${applicationFailed ? "has available infrastructure but failed its application readiness contract" : "is ready"} (${result.devpodId}).
@@ -20891,7 +21333,7 @@ async function runOpenCommand(name) {
20891
21333
  );
20892
21334
  return;
20893
21335
  }
20894
- const result = (0, import_node_child_process30.spawnSync)("open", [url], { encoding: "utf-8" });
21336
+ const result = (0, import_node_child_process31.spawnSync)("open", [url], { encoding: "utf-8" });
20895
21337
  if (result.status !== 0) {
20896
21338
  const details = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
20897
21339
  throw new Error(`Unable to open '${url}': ${details || "unknown error"}`);
@@ -20899,11 +21341,11 @@ async function runOpenCommand(name) {
20899
21341
  process.stdout.write(`Opened ${url}
20900
21342
  `);
20901
21343
  }
20902
- var import_node_child_process30;
21344
+ var import_node_child_process31;
20903
21345
  var init_open = __esm({
20904
21346
  "src/commands/open.ts"() {
20905
21347
  "use strict";
20906
- import_node_child_process30 = require("child_process");
21348
+ import_node_child_process31 = require("child_process");
20907
21349
  init_docker();
20908
21350
  init_host_routes();
20909
21351
  init_repo_config();
@@ -20926,7 +21368,7 @@ async function runLogsCommand(options) {
20926
21368
  const args = ["logs", "--tail", tail, ROUTER_CONTAINER_NAME];
20927
21369
  if (options.follow) {
20928
21370
  args.splice(1, 0, "-f");
20929
- const child = (0, import_node_child_process31.spawn)("docker", args, { stdio: "inherit" });
21371
+ const child = (0, import_node_child_process32.spawn)("docker", args, { stdio: "inherit" });
20930
21372
  const onSignal = () => {
20931
21373
  child.kill("SIGTERM");
20932
21374
  };
@@ -20940,17 +21382,17 @@ async function runLogsCommand(options) {
20940
21382
  });
20941
21383
  });
20942
21384
  } else {
20943
- const result = (0, import_node_child_process31.spawnSync)("docker", args, { stdio: "inherit" });
21385
+ const result = (0, import_node_child_process32.spawnSync)("docker", args, { stdio: "inherit" });
20944
21386
  if (result.status !== 0) {
20945
21387
  throw new Error("Failed to retrieve router logs.");
20946
21388
  }
20947
21389
  }
20948
21390
  }
20949
- var import_node_child_process31;
21391
+ var import_node_child_process32;
20950
21392
  var init_logs = __esm({
20951
21393
  "src/commands/logs.ts"() {
20952
21394
  "use strict";
20953
- import_node_child_process31 = require("child_process");
21395
+ import_node_child_process32 = require("child_process");
20954
21396
  init_docker();
20955
21397
  init_router();
20956
21398
  }
@@ -20990,17 +21432,17 @@ function asRecord3(value) {
20990
21432
  return value;
20991
21433
  }
20992
21434
  function readJson(filePath) {
20993
- if (!import_node_fs43.default.existsSync(filePath)) {
21435
+ if (!import_node_fs44.default.existsSync(filePath)) {
20994
21436
  return void 0;
20995
21437
  }
20996
21438
  try {
20997
- return JSON.parse(import_node_fs43.default.readFileSync(filePath, "utf-8"));
21439
+ return JSON.parse(import_node_fs44.default.readFileSync(filePath, "utf-8"));
20998
21440
  } catch {
20999
21441
  return void 0;
21000
21442
  }
21001
21443
  }
21002
21444
  function relative(repoPath, filePath) {
21003
- return import_node_path54.default.relative(repoPath, filePath) || ".";
21445
+ return import_node_path55.default.relative(repoPath, filePath) || ".";
21004
21446
  }
21005
21447
  function redactEnvAssignments(value) {
21006
21448
  return value.replace(
@@ -21041,7 +21483,7 @@ function inspectPackageManager(repoPath, pkg) {
21041
21483
  ["bun.lock", "bun"]
21042
21484
  ];
21043
21485
  for (const [fileName, name] of lockfiles) {
21044
- if (import_node_fs43.default.existsSync(import_node_path54.default.join(repoPath, fileName))) {
21486
+ if (import_node_fs44.default.existsSync(import_node_path55.default.join(repoPath, fileName))) {
21045
21487
  return { name, source: fileName };
21046
21488
  }
21047
21489
  }
@@ -21056,9 +21498,9 @@ function inspectNode(repoPath, pkg) {
21056
21498
  if (typeof engines?.node === "string") {
21057
21499
  return { version: engines.node, source: "package.json:engines.node" };
21058
21500
  }
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();
21501
+ const nvmrc = import_node_path55.default.join(repoPath, ".nvmrc");
21502
+ if (import_node_fs44.default.existsSync(nvmrc)) {
21503
+ const version = import_node_fs44.default.readFileSync(nvmrc, "utf-8").trim();
21062
21504
  return { version, source: ".nvmrc" };
21063
21505
  }
21064
21506
  return void 0;
@@ -21119,7 +21561,7 @@ function configuredComposeFiles(repoPath) {
21119
21561
  const files = config.apps.filter(
21120
21562
  (app) => app.runtime === "docker"
21121
21563
  ).flatMap((app) => app.docker.composeFiles).filter(
21122
- (fileName) => !import_node_path54.default.isAbsolute(fileName) && !import_node_path54.default.normalize(fileName).startsWith("..")
21564
+ (fileName) => !import_node_path55.default.isAbsolute(fileName) && !import_node_path55.default.normalize(fileName).startsWith("..")
21123
21565
  );
21124
21566
  return Array.from(new Set(files));
21125
21567
  } catch {
@@ -21137,7 +21579,7 @@ function composeFiles(repoPath) {
21137
21579
  ...configuredComposeFiles(repoPath)
21138
21580
  ];
21139
21581
  return Array.from(new Set(candidates)).filter(
21140
- (fileName) => import_node_fs43.default.existsSync(import_node_path54.default.join(repoPath, fileName))
21582
+ (fileName) => import_node_fs44.default.existsSync(import_node_path55.default.join(repoPath, fileName))
21141
21583
  );
21142
21584
  }
21143
21585
  function stringArray2(value) {
@@ -21175,7 +21617,7 @@ function inspectServices(repoPath) {
21175
21617
  const services = [];
21176
21618
  for (const fileName of composeFiles(repoPath)) {
21177
21619
  try {
21178
- const parsed = import_yaml7.default.parse(import_node_fs43.default.readFileSync(import_node_path54.default.join(repoPath, fileName), "utf-8"));
21620
+ const parsed = import_yaml7.default.parse(import_node_fs44.default.readFileSync(import_node_path55.default.join(repoPath, fileName), "utf-8"));
21179
21621
  const serviceMap = asRecord3(asRecord3(parsed)?.services);
21180
21622
  for (const [name, value] of Object.entries(serviceMap ?? {})) {
21181
21623
  const service = asRecord3(value);
@@ -21210,8 +21652,8 @@ function inspectServices(repoPath) {
21210
21652
  return services;
21211
21653
  }
21212
21654
  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");
21655
+ const files = import_node_fs44.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
21656
+ const content = import_node_fs44.default.readFileSync(import_node_path55.default.join(repoPath, fileName), "utf-8");
21215
21657
  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
21658
  return { path: fileName, names };
21217
21659
  });
@@ -21225,18 +21667,18 @@ function inspectEnvFiles(repoPath) {
21225
21667
  };
21226
21668
  }
21227
21669
  function inspectDevcontainer(repoPath) {
21228
- const dir = import_node_path54.default.join(repoPath, ".devcontainer");
21229
- if (!import_node_fs43.default.existsSync(dir)) {
21670
+ const dir = import_node_path55.default.join(repoPath, ".devcontainer");
21671
+ if (!import_node_fs44.default.existsSync(dir)) {
21230
21672
  return { exists: false, files: [] };
21231
21673
  }
21232
21674
  return {
21233
21675
  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}`)
21676
+ 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
21677
  };
21236
21678
  }
21237
21679
  function inspectDevrouter(repoPath) {
21238
21680
  const configPath = getRepoConfigPath(repoPath);
21239
- if (!import_node_fs43.default.existsSync(configPath)) {
21681
+ if (!import_node_fs44.default.existsSync(configPath)) {
21240
21682
  return {
21241
21683
  exists: false,
21242
21684
  configPath,
@@ -21280,15 +21722,15 @@ function inspectAgentGuidance(repoPath) {
21280
21722
  ["AGENTS.md", "agents"],
21281
21723
  ["CLAUDE.md", "claude"]
21282
21724
  ]) {
21283
- if (import_node_fs43.default.existsSync(import_node_path54.default.join(repoPath, fileName))) {
21725
+ if (import_node_fs44.default.existsSync(import_node_path55.default.join(repoPath, fileName))) {
21284
21726
  results.push({ path: fileName, kind });
21285
21727
  }
21286
21728
  }
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)) {
21729
+ const skillsDir = import_node_path55.default.join(repoPath, ".agents", "skills");
21730
+ if (import_node_fs44.default.existsSync(skillsDir)) {
21731
+ for (const name of import_node_fs44.default.readdirSync(skillsDir).sort()) {
21732
+ const skillPath = import_node_path55.default.join(skillsDir, name, "SKILL.md");
21733
+ if (import_node_fs44.default.existsSync(skillPath)) {
21292
21734
  results.push({ path: relative(repoPath, skillPath), kind: "skill" });
21293
21735
  }
21294
21736
  }
@@ -21331,7 +21773,7 @@ function buildIssues(report) {
21331
21773
  }
21332
21774
  function inspectRepo(options = {}) {
21333
21775
  const repoPath = resolveRepoPath(options.repo);
21334
- const pkg = readJson(import_node_path54.default.join(repoPath, "package.json"));
21776
+ const pkg = readJson(import_node_path55.default.join(repoPath, "package.json"));
21335
21777
  const scripts = inspectScripts(pkg);
21336
21778
  const reportWithoutIssues = {
21337
21779
  repoPath,
@@ -21350,12 +21792,12 @@ function inspectRepo(options = {}) {
21350
21792
  issues: buildIssues(reportWithoutIssues)
21351
21793
  };
21352
21794
  }
21353
- var import_node_fs43, import_node_path54, import_yaml7;
21795
+ var import_node_fs44, import_node_path55, import_yaml7;
21354
21796
  var init_repo_inspect = __esm({
21355
21797
  "src/core/repo-inspect.ts"() {
21356
21798
  "use strict";
21357
- import_node_fs43 = __toESM(require("fs"));
21358
- import_node_path54 = __toESM(require("path"));
21799
+ import_node_fs44 = __toESM(require("fs"));
21800
+ import_node_path55 = __toESM(require("path"));
21359
21801
  import_yaml7 = __toESM(require("yaml"));
21360
21802
  init_repo_config();
21361
21803
  }
@@ -21460,7 +21902,7 @@ function requiredFileChecks(repoPath) {
21460
21902
  ".devcontainer/docker-compose.yml",
21461
21903
  ".devrouter.yml"
21462
21904
  ];
21463
- const missing = required.filter((fileName) => !import_node_fs44.default.existsSync(import_node_path55.default.join(repoPath, fileName)));
21905
+ const missing = required.filter((fileName) => !import_node_fs45.default.existsSync(import_node_path56.default.join(repoPath, fileName)));
21464
21906
  return {
21465
21907
  id: "repo.devcontainer.verify-files",
21466
21908
  level: missing.length === 0 ? "ok" : "error",
@@ -21685,12 +22127,12 @@ async function verifyDevcontainer(options = {}) {
21685
22127
  nextSteps: collectNextSteps3(checks)
21686
22128
  };
21687
22129
  }
21688
- var import_node_fs44, import_node_path55;
22130
+ var import_node_fs45, import_node_path56;
21689
22131
  var init_devcontainer_verify = __esm({
21690
22132
  "src/core/devcontainer-verify.ts"() {
21691
22133
  "use strict";
21692
- import_node_fs44 = __toESM(require("fs"));
21693
- import_node_path55 = __toESM(require("path"));
22134
+ import_node_fs45 = __toESM(require("fs"));
22135
+ import_node_path56 = __toESM(require("path"));
21694
22136
  init_capabilities();
21695
22137
  init_doctor();
21696
22138
  init_host_routes();
@@ -21987,7 +22429,7 @@ function packageManagerIssues(repo) {
21987
22429
  }
21988
22430
  function plannedFiles(repoPath, version) {
21989
22431
  const repo = inspectRepo({ repo: repoPath });
21990
- const projectName = sanitizeProjectName(import_node_path56.default.basename(repo.repoPath));
22432
+ const projectName = sanitizeProjectName(import_node_path57.default.basename(repo.repoPath));
21991
22433
  const nodeMajor = majorVersion(repo.node?.version, "24");
21992
22434
  const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
21993
22435
  const port = inferPort2(repo);
@@ -22032,8 +22474,8 @@ function plannedFiles(repoPath, version) {
22032
22474
  };
22033
22475
  }
22034
22476
  function classifyFile(repoPath, file) {
22035
- const absolutePath = import_node_path56.default.join(repoPath, file.relativePath);
22036
- if (!import_node_fs45.default.existsSync(absolutePath)) {
22477
+ const absolutePath = import_node_path57.default.join(repoPath, file.relativePath);
22478
+ if (!import_node_fs46.default.existsSync(absolutePath)) {
22037
22479
  return {
22038
22480
  path: file.relativePath,
22039
22481
  action: "create",
@@ -22041,7 +22483,7 @@ function classifyFile(repoPath, file) {
22041
22483
  bytes: Buffer.byteLength(file.content)
22042
22484
  };
22043
22485
  }
22044
- const current = import_node_fs45.default.readFileSync(absolutePath, "utf-8");
22486
+ const current = import_node_fs46.default.readFileSync(absolutePath, "utf-8");
22045
22487
  if (!current.includes(MANAGED_MARKER2)) {
22046
22488
  return {
22047
22489
  path: file.relativePath,
@@ -22098,11 +22540,11 @@ function buildPlan(repoPath, dryRun, version) {
22098
22540
  };
22099
22541
  }
22100
22542
  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");
22543
+ const absolutePath = import_node_path57.default.join(repoPath, file.relativePath);
22544
+ import_node_fs46.default.mkdirSync(import_node_path57.default.dirname(absolutePath), { recursive: true });
22545
+ import_node_fs46.default.writeFileSync(absolutePath, file.content, "utf-8");
22104
22546
  if (file.executable) {
22105
- import_node_fs45.default.chmodSync(absolutePath, 493);
22547
+ import_node_fs46.default.chmodSync(absolutePath, 493);
22106
22548
  }
22107
22549
  }
22108
22550
  function writeDevcontainer(options = {}) {
@@ -22140,12 +22582,12 @@ function writeDevcontainer(options = {}) {
22140
22582
  nextSteps: postWriteNextSteps(repoPath)
22141
22583
  };
22142
22584
  }
22143
- var import_node_fs45, import_node_path56, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
22585
+ var import_node_fs46, import_node_path57, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
22144
22586
  var init_devcontainer_write = __esm({
22145
22587
  "src/core/devcontainer-write.ts"() {
22146
22588
  "use strict";
22147
- import_node_fs45 = __toESM(require("fs"));
22148
- import_node_path56 = __toESM(require("path"));
22589
+ import_node_fs46 = __toESM(require("fs"));
22590
+ import_node_path57 = __toESM(require("path"));
22149
22591
  init_repo_config();
22150
22592
  init_repo_inspect();
22151
22593
  MANAGED_MARKER2 = "devrouter:managed devcontainer";
@@ -22258,11 +22700,11 @@ var init_repo_devcontainer = __esm({
22258
22700
  function fail3(message) {
22259
22701
  throw new Error(message);
22260
22702
  }
22261
- function isRecord6(value) {
22703
+ function isRecord7(value) {
22262
22704
  return typeof value === "object" && value !== null && !Array.isArray(value);
22263
22705
  }
22264
22706
  function requireRecord(value, label2) {
22265
- if (!isRecord6(value)) fail3(`${label2} must be a mapping.`);
22707
+ if (!isRecord7(value)) fail3(`${label2} must be a mapping.`);
22266
22708
  return value;
22267
22709
  }
22268
22710
  function requireExactKeys(value, allowed, label2) {
@@ -22479,13 +22921,13 @@ function buildProfilePlanReport(options) {
22479
22921
  return { ...report, contractPath, bindings };
22480
22922
  }
22481
22923
  function loadContract(repoPath, contractPath) {
22482
- if (import_node_path57.default.isAbsolute(contractPath)) {
22924
+ if (import_node_path58.default.isAbsolute(contractPath)) {
22483
22925
  fail3("profile plan contract path must be relative to the repository.");
22484
22926
  }
22485
22927
  const absolutePath = assertPathWithinRepo(contractPath, repoPath, "Profile plan contract");
22486
22928
  let metadata;
22487
22929
  try {
22488
- metadata = import_node_fs46.default.lstatSync(absolutePath);
22930
+ metadata = import_node_fs47.default.lstatSync(absolutePath);
22489
22931
  } catch (error) {
22490
22932
  fail3(`could not inspect profile plan contract '${contractPath}': ${error.message}`);
22491
22933
  }
@@ -22495,16 +22937,16 @@ function loadContract(repoPath, contractPath) {
22495
22937
  if (metadata.size > MAX_CONTRACT_BYTES) {
22496
22938
  fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
22497
22939
  }
22498
- const realRepo = import_node_fs46.default.realpathSync(repoPath);
22499
- const realContract = import_node_fs46.default.realpathSync(absolutePath);
22940
+ const realRepo = import_node_fs47.default.realpathSync(repoPath);
22941
+ const realContract = import_node_fs47.default.realpathSync(absolutePath);
22500
22942
  assertPathWithinRepo(realContract, realRepo, "Profile plan contract");
22501
- const source = import_node_fs46.default.readFileSync(absolutePath, "utf-8");
22943
+ const source = import_node_fs47.default.readFileSync(absolutePath, "utf-8");
22502
22944
  if (Buffer.byteLength(source, "utf-8") > MAX_CONTRACT_BYTES) {
22503
22945
  fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
22504
22946
  }
22505
22947
  return {
22506
22948
  contract: parseProfilePlanContract(source),
22507
- contractPath: import_node_path57.default.relative(repoPath, absolutePath).split(import_node_path57.default.sep).join("/")
22949
+ contractPath: import_node_path58.default.relative(repoPath, absolutePath).split(import_node_path58.default.sep).join("/")
22508
22950
  };
22509
22951
  }
22510
22952
  function resolveProfilePlan(options) {
@@ -22518,12 +22960,12 @@ function resolveProfilePlan(options) {
22518
22960
  contractPath: loaded.contractPath
22519
22961
  });
22520
22962
  }
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;
22963
+ 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
22964
  var init_profile_plan = __esm({
22523
22965
  "src/core/profile-plan.ts"() {
22524
22966
  "use strict";
22525
- import_node_fs46 = __toESM(require("fs"));
22526
- import_node_path57 = __toESM(require("path"));
22967
+ import_node_fs47 = __toESM(require("fs"));
22968
+ import_node_path58 = __toESM(require("path"));
22527
22969
  import_yaml8 = require("yaml");
22528
22970
  init_paths();
22529
22971
  init_profile_resolution();
@@ -22555,7 +22997,7 @@ async function runProfileResolveCommand(options) {
22555
22997
  async function runProfilePlanCommand(options) {
22556
22998
  const report = resolveProfilePlan(options);
22557
22999
  if (options.output) {
22558
- writeFileAtomically(import_node_path58.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
23000
+ writeFileAtomically(import_node_path59.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
22559
23001
  `);
22560
23002
  }
22561
23003
  if (options.json) {
@@ -22591,14 +23033,14 @@ function printProfilePlanSummary(report, output2) {
22591
23033
  process.stdout.write(`Binding ${key}: ${renderValues(report.bindings[key] ?? [])}
22592
23034
  `);
22593
23035
  }
22594
- if (output2) process.stdout.write(`Output: ${import_node_path58.default.resolve(output2)}
23036
+ if (output2) process.stdout.write(`Output: ${import_node_path59.default.resolve(output2)}
22595
23037
  `);
22596
23038
  }
22597
- var import_node_path58;
23039
+ var import_node_path59;
22598
23040
  var init_profile = __esm({
22599
23041
  "src/commands/profile.ts"() {
22600
23042
  "use strict";
22601
- import_node_path58 = __toESM(require("path"));
23043
+ import_node_path59 = __toESM(require("path"));
22602
23044
  init_atomic_file();
22603
23045
  init_output();
22604
23046
  init_profile_plan();
@@ -22888,7 +23330,7 @@ function sanitizeRouterId(value) {
22888
23330
  return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
22889
23331
  }
22890
23332
  function repoHash(repoPath) {
22891
- return (0, import_node_crypto26.createHash)("sha1").update(import_node_path59.default.resolve(repoPath)).digest("hex").slice(0, 12);
23333
+ return (0, import_node_crypto26.createHash)("sha1").update(import_node_path60.default.resolve(repoPath)).digest("hex").slice(0, 12);
22892
23334
  }
22893
23335
  function asDockerApp(app) {
22894
23336
  return app.runtime === "docker";
@@ -22967,11 +23409,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
22967
23409
  if (dockerApps.length === 0) {
22968
23410
  throw new Error("No docker apps selected to prepare compose overlay.");
22969
23411
  }
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");
23412
+ const cachePath = import_node_path60.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
23413
+ import_node_fs48.default.mkdirSync(cachePath, { recursive: true });
23414
+ const overlayPath = import_node_path60.default.join(cachePath, "compose.devrouter.yml");
22973
23415
  const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
22974
- import_node_fs47.default.writeFileSync(overlayPath, import_yaml9.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
23416
+ import_node_fs48.default.writeFileSync(overlayPath, import_yaml9.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
22975
23417
  return {
22976
23418
  overlayPath,
22977
23419
  composeFiles: ensureComposeFiles(dockerApps),
@@ -22985,7 +23427,7 @@ function runDockerComposeUp(repoPath, composeFiles2, overlayPath, services) {
22985
23427
  fileArgs.push("-f", resolved);
22986
23428
  }
22987
23429
  const args = ["compose", ...fileArgs, "-f", overlayPath, "up", "-d", "--wait", ...services];
22988
- const result = (0, import_node_child_process32.spawnSync)("docker", args, {
23430
+ const result = (0, import_node_child_process33.spawnSync)("docker", args, {
22989
23431
  encoding: "utf-8",
22990
23432
  cwd: repoPath
22991
23433
  });
@@ -23013,7 +23455,7 @@ function queryRunningComposeServices(repoPath, composeFiles2, overlayPath, servi
23013
23455
  "--services",
23014
23456
  ...services
23015
23457
  ];
23016
- const result = (0, import_node_child_process32.spawnSync)("docker", args, {
23458
+ const result = (0, import_node_child_process33.spawnSync)("docker", args, {
23017
23459
  encoding: "utf-8",
23018
23460
  cwd: repoPath
23019
23461
  });
@@ -23039,7 +23481,7 @@ function runDockerComposeStop(repoPath, composeFiles2, overlayPath, services) {
23039
23481
  fileArgs.push("-f", resolved);
23040
23482
  }
23041
23483
  const args = ["compose", ...fileArgs, "-f", overlayPath, "stop", ...services];
23042
- const result = (0, import_node_child_process32.spawnSync)("docker", args, {
23484
+ const result = (0, import_node_child_process33.spawnSync)("docker", args, {
23043
23485
  encoding: "utf-8",
23044
23486
  cwd: repoPath
23045
23487
  });
@@ -23065,7 +23507,7 @@ function runDockerComposeLogs(repoPath, composeFiles2, overlayPath, services, ta
23065
23507
  String(tail),
23066
23508
  ...services
23067
23509
  ];
23068
- (0, import_node_child_process32.spawnSync)("docker", args, {
23510
+ (0, import_node_child_process33.spawnSync)("docker", args, {
23069
23511
  stdio: "inherit",
23070
23512
  cwd: repoPath
23071
23513
  });
@@ -23077,7 +23519,7 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
23077
23519
  fileArgs.push("-f", resolved);
23078
23520
  }
23079
23521
  const args = ["compose", ...fileArgs, "-f", overlayPath, "port", service, String(internalPort)];
23080
- const result = (0, import_node_child_process32.spawnSync)("docker", args, {
23522
+ const result = (0, import_node_child_process33.spawnSync)("docker", args, {
23081
23523
  encoding: "utf-8",
23082
23524
  cwd: repoPath
23083
23525
  });
@@ -23091,14 +23533,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
23091
23533
  const port = Number(match[1]);
23092
23534
  return Number.isInteger(port) && port > 0 ? port : void 0;
23093
23535
  }
23094
- var import_node_child_process32, import_node_crypto26, import_node_fs47, import_node_path59, import_yaml9;
23536
+ var import_node_child_process33, import_node_crypto26, import_node_fs48, import_node_path60, import_yaml9;
23095
23537
  var init_docker_run = __esm({
23096
23538
  "src/core/docker-run.ts"() {
23097
23539
  "use strict";
23098
- import_node_child_process32 = require("child_process");
23540
+ import_node_child_process33 = require("child_process");
23099
23541
  import_node_crypto26 = require("crypto");
23100
- import_node_fs47 = __toESM(require("fs"));
23101
- import_node_path59 = __toESM(require("path"));
23542
+ import_node_fs48 = __toESM(require("fs"));
23543
+ import_node_path60 = __toESM(require("path"));
23102
23544
  import_yaml9 = __toESM(require("yaml"));
23103
23545
  init_docker_error_guidance();
23104
23546
  init_paths();
@@ -23188,7 +23630,7 @@ function isProcessRunning(pid) {
23188
23630
  }
23189
23631
  }
23190
23632
  function readProcessTree(rootPid) {
23191
- const result = (0, import_node_child_process33.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
23633
+ const result = (0, import_node_child_process34.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
23192
23634
  if (result.status !== 0) {
23193
23635
  return [rootPid];
23194
23636
  }
@@ -23299,12 +23741,12 @@ function detectListeningPorts(pids) {
23299
23741
  if (pids.length === 0) {
23300
23742
  return [];
23301
23743
  }
23302
- const result = (0, import_node_child_process33.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
23744
+ const result = (0, import_node_child_process34.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
23303
23745
  encoding: "utf-8"
23304
23746
  });
23305
23747
  if (result.error && result.error.code === "ENOENT") {
23306
23748
  if (process.platform === "linux") {
23307
- const ssResult = (0, import_node_child_process33.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
23749
+ const ssResult = (0, import_node_child_process34.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
23308
23750
  if (ssResult.status === 0 && ssResult.stdout) {
23309
23751
  return parseSsListeningPorts(ssResult.stdout, new Set(pids));
23310
23752
  }
@@ -23363,7 +23805,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
23363
23805
  app.hostRun.command,
23364
23806
  true
23365
23807
  ) : app.hostRun.command;
23366
- const child = (0, import_node_child_process33.spawn)(spawnCommand, {
23808
+ const child = (0, import_node_child_process34.spawn)(spawnCommand, {
23367
23809
  cwd: commandCwd,
23368
23810
  stdio: "inherit",
23369
23811
  shell: true,
@@ -23731,7 +24173,7 @@ async function execWithAppEnv(options) {
23731
24173
  options.command[0],
23732
24174
  true
23733
24175
  );
23734
- child = (0, import_node_child_process33.spawn)(wrapped, {
24176
+ child = (0, import_node_child_process34.spawn)(wrapped, {
23735
24177
  cwd: deps.repoPath,
23736
24178
  stdio: "inherit",
23737
24179
  shell: true,
@@ -23745,7 +24187,7 @@ async function execWithAppEnv(options) {
23745
24187
  false
23746
24188
  );
23747
24189
  const [cmd, ...wrappedArgs] = wrapped;
23748
- child = (0, import_node_child_process33.spawn)(cmd, wrappedArgs, {
24190
+ child = (0, import_node_child_process34.spawn)(cmd, wrappedArgs, {
23749
24191
  cwd: deps.repoPath,
23750
24192
  stdio: "inherit",
23751
24193
  shell: false,
@@ -23753,7 +24195,7 @@ async function execWithAppEnv(options) {
23753
24195
  });
23754
24196
  }
23755
24197
  } else if (options.shell) {
23756
- child = (0, import_node_child_process33.spawn)(options.command[0], {
24198
+ child = (0, import_node_child_process34.spawn)(options.command[0], {
23757
24199
  cwd: deps.repoPath,
23758
24200
  stdio: "inherit",
23759
24201
  shell: true,
@@ -23761,7 +24203,7 @@ async function execWithAppEnv(options) {
23761
24203
  });
23762
24204
  } else {
23763
24205
  const [command, ...args] = options.command;
23764
- child = (0, import_node_child_process33.spawn)(command, args, {
24206
+ child = (0, import_node_child_process34.spawn)(command, args, {
23765
24207
  cwd: deps.repoPath,
23766
24208
  stdio: "inherit",
23767
24209
  shell: false,
@@ -23782,11 +24224,11 @@ async function execWithAppEnv(options) {
23782
24224
  deps.stopDeps();
23783
24225
  }
23784
24226
  }
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;
24227
+ 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
24228
  var init_app_run = __esm({
23787
24229
  "src/core/app-run.ts"() {
23788
24230
  "use strict";
23789
- import_node_child_process33 = require("child_process");
24231
+ import_node_child_process34 = require("child_process");
23790
24232
  import_node_net5 = __toESM(require("net"));
23791
24233
  import_node_process = require("process");
23792
24234
  import_promises = require("readline/promises");
@@ -23920,13 +24362,13 @@ function measureWorktreeConsumption(worktreePath, options) {
23920
24362
  const startedAt = Date.now();
23921
24363
  let rootStat;
23922
24364
  try {
23923
- rootStat = import_node_fs48.default.lstatSync(worktreePath);
24365
+ rootStat = import_node_fs49.default.lstatSync(worktreePath);
23924
24366
  } catch (error) {
23925
24367
  return { status: "unknown", reason: describeError(error, worktreePath) };
23926
24368
  }
23927
24369
  let rootEntries;
23928
24370
  try {
23929
- rootEntries = import_node_fs48.default.readdirSync(worktreePath, { withFileTypes: true });
24371
+ rootEntries = import_node_fs49.default.readdirSync(worktreePath, { withFileTypes: true });
23930
24372
  } catch (error) {
23931
24373
  return { status: "unknown", reason: describeError(error, worktreePath) };
23932
24374
  }
@@ -23952,10 +24394,10 @@ function measureWorktreeConsumption(worktreePath, options) {
23952
24394
  timedOut = true;
23953
24395
  break;
23954
24396
  }
23955
- const entryPath = import_node_path60.default.join(dirPath, entry.name);
24397
+ const entryPath = import_node_path61.default.join(dirPath, entry.name);
23956
24398
  let entryStat;
23957
24399
  try {
23958
- entryStat = import_node_fs48.default.lstatSync(entryPath);
24400
+ entryStat = import_node_fs49.default.lstatSync(entryPath);
23959
24401
  } catch (error) {
23960
24402
  if (error?.code === "ENOENT") continue;
23961
24403
  unreadableReason = describeIncompleteWalk(error);
@@ -23965,7 +24407,7 @@ function measureWorktreeConsumption(worktreePath, options) {
23965
24407
  if (!entryStat.isDirectory()) continue;
23966
24408
  let childEntries;
23967
24409
  try {
23968
- childEntries = import_node_fs48.default.readdirSync(entryPath, { withFileTypes: true });
24410
+ childEntries = import_node_fs49.default.readdirSync(entryPath, { withFileTypes: true });
23969
24411
  } catch (error) {
23970
24412
  unreadableReason = describeIncompleteWalk(error);
23971
24413
  break;
@@ -24029,12 +24471,12 @@ function describeError(error, worktreePath) {
24029
24471
  }
24030
24472
  return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
24031
24473
  }
24032
- var import_node_fs48, import_node_path60, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
24474
+ var import_node_fs49, import_node_path61, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
24033
24475
  var init_workspace_consumption = __esm({
24034
24476
  "src/core/workspace-consumption.ts"() {
24035
24477
  "use strict";
24036
- import_node_fs48 = __toESM(require("fs"));
24037
- import_node_path60 = __toESM(require("path"));
24478
+ import_node_fs49 = __toESM(require("fs"));
24479
+ import_node_path61 = __toESM(require("path"));
24038
24480
  init_devpod_environment();
24039
24481
  DEFAULT_DEADLINE_MS = 1e4;
24040
24482
  BLOCK_SIZE_BYTES = 512;
@@ -24042,7 +24484,7 @@ var init_workspace_consumption = __esm({
24042
24484
  });
24043
24485
 
24044
24486
  // src/core/workspace-cleanup.ts
24045
- function isRecord7(value) {
24487
+ function isRecord8(value) {
24046
24488
  return value !== null && typeof value === "object" && !Array.isArray(value);
24047
24489
  }
24048
24490
  function isSha(value) {
@@ -24135,7 +24577,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
24135
24577
  }
24136
24578
  function readGitSnapshot(worktree, commandRunner) {
24137
24579
  const comparablePath = comparableWorkspacePath(worktree.path);
24138
- if (worktree.prunable || !import_node_fs49.default.existsSync(comparablePath)) {
24580
+ if (worktree.prunable || !import_node_fs50.default.existsSync(comparablePath)) {
24139
24581
  return { worktree, checkout: "missing", head: null, committerDate: null };
24140
24582
  }
24141
24583
  const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
@@ -24186,14 +24628,14 @@ function parseGitHubChanges(value, project, branch) {
24186
24628
  if (!Array.isArray(value)) return void 0;
24187
24629
  const changes = [];
24188
24630
  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;
24631
+ if (!isRecord8(item)) return void 0;
24632
+ const repository = isRecord8(item.repository) ? item.repository.nameWithOwner : void 0;
24633
+ const headRepository = isRecord8(item.headRepository) ? item.headRepository.nameWithOwner : void 0;
24192
24634
  if (repository !== project || headRepository !== project || item.headRefName !== branch || !isSha(item.headRefOid) || typeof item.baseRefName !== "string") {
24193
24635
  continue;
24194
24636
  }
24195
24637
  const merged = item.state === "MERGED" && validTimestamp(item.mergedAt);
24196
- const mergeCommit = isRecord7(item.mergeCommit) && isSha(item.mergeCommit.oid) ? item.mergeCommit.oid : void 0;
24638
+ const mergeCommit = isRecord8(item.mergeCommit) && isSha(item.mergeCommit.oid) ? item.mergeCommit.oid : void 0;
24197
24639
  const baseSha = isSha(item.baseRefOid) ? item.baseRefOid : void 0;
24198
24640
  changes.push({
24199
24641
  sourceBranch: branch,
@@ -24210,14 +24652,14 @@ function parseGitLabChanges(value, _project, branch) {
24210
24652
  if (!Array.isArray(value)) return void 0;
24211
24653
  const changes = [];
24212
24654
  for (const item of value) {
24213
- if (!isRecord7(item)) return void 0;
24655
+ if (!isRecord8(item)) return void 0;
24214
24656
  const sourceProjectId = item.source_project_id;
24215
24657
  const targetProjectId = item.target_project_id;
24216
24658
  const sameProject = typeof sourceProjectId === "number" && typeof targetProjectId === "number" && sourceProjectId === targetProjectId;
24217
24659
  if (!sameProject || item.source_branch !== branch || !isSha(item.sha) || typeof item.target_branch !== "string") {
24218
24660
  continue;
24219
24661
  }
24220
- const diffRefs = isRecord7(item.diff_refs) ? item.diff_refs : void 0;
24662
+ const diffRefs = isRecord8(item.diff_refs) ? item.diff_refs : void 0;
24221
24663
  const mergeCommitSha = isSha(item.merge_commit_sha) ? item.merge_commit_sha : void 0;
24222
24664
  const baseSha = diffRefs && isSha(diffRefs.base_sha) ? diffRefs.base_sha : void 0;
24223
24665
  changes.push({
@@ -24660,7 +25102,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
24660
25102
  containers2 = measureContainersFn(worktreePaths);
24661
25103
  } catch (error) {
24662
25104
  const reason = `container measurement failed: ${describeCause(error)}`;
24663
- containers2 = new Map(worktreePaths.map((path61) => [path61, unknownContainers(reason)]));
25105
+ containers2 = new Map(worktreePaths.map((path62) => [path62, unknownContainers(reason)]));
24664
25106
  }
24665
25107
  }
24666
25108
  const rows2 = records.map((record5) => {
@@ -24710,12 +25152,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
24710
25152
  networkCapacity: (dependencies.inspectNetworkCapacity ?? inspectNetworkCapacity)()
24711
25153
  };
24712
25154
  }
24713
- var import_node_child_process34, import_node_fs49, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
25155
+ var import_node_child_process35, import_node_fs50, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
24714
25156
  var init_workspace_cleanup = __esm({
24715
25157
  "src/core/workspace-cleanup.ts"() {
24716
25158
  "use strict";
24717
- import_node_child_process34 = require("child_process");
24718
- import_node_fs49 = __toESM(require("fs"));
25159
+ import_node_child_process35 = require("child_process");
25160
+ import_node_fs50 = __toESM(require("fs"));
24719
25161
  init_devpod_workspaces();
24720
25162
  init_host_routes();
24721
25163
  init_network_diagnostics();
@@ -24733,7 +25175,7 @@ var init_workspace_cleanup = __esm({
24733
25175
  ];
24734
25176
  defaultCommandRunner = (command, args, input2) => {
24735
25177
  const environment = command === "git" ? READ_ONLY_GIT_ENV2 : { ...process.env, LC_ALL: "C" };
24736
- const result = (0, import_node_child_process34.spawnSync)(command, args, {
25178
+ const result = (0, import_node_child_process35.spawnSync)(command, args, {
24737
25179
  encoding: "utf-8",
24738
25180
  env: environment,
24739
25181
  ...input2 === void 0 ? {} : { input: input2 }
@@ -24827,20 +25269,20 @@ function warnMissingWorkspaceOwnership(repoPath) {
24827
25269
  );
24828
25270
  }
24829
25271
  function defaultWorktreePath(mainRepo, ws) {
24830
- return import_node_path61.default.join(mainRepo, "trees", ws);
25272
+ return import_node_path62.default.join(mainRepo, "trees", ws);
24831
25273
  }
24832
25274
  function assertDefaultWorktreeRootIgnored(mainRepo) {
24833
- const ignored = (0, import_node_child_process35.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
25275
+ const ignored = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
24834
25276
  encoding: "utf-8"
24835
25277
  });
24836
25278
  if (ignored.status !== 0) {
24837
25279
  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.`
25280
+ `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
25281
  );
24840
25282
  }
24841
25283
  }
24842
25284
  function legacyDefaultWorktreePath(mainRepo, ws) {
24843
- return import_node_path61.default.join(import_node_path61.default.dirname(mainRepo), `${import_node_path61.default.basename(mainRepo)}-${ws}`);
25285
+ return import_node_path62.default.join(import_node_path62.default.dirname(mainRepo), `${import_node_path62.default.basename(mainRepo)}-${ws}`);
24844
25286
  }
24845
25287
  function teardownFallbackPath(mainRepo, workspace) {
24846
25288
  const candidates = [
@@ -24952,13 +25394,13 @@ function assertFullDownPreflight(mainRepo, target) {
24952
25394
  if (sameWorkspacePath(target.worktreePath, mainRepo)) {
24953
25395
  throw new Error("Refusing to remove the primary Git checkout.");
24954
25396
  }
24955
- if (!target.worktree || target.worktree.prunable || !import_node_fs50.default.existsSync(target.worktreePath)) return;
25397
+ if (!target.worktree || target.worktree.prunable || !import_node_fs51.default.existsSync(target.worktreePath)) return;
24956
25398
  if (target.worktree.locked) {
24957
25399
  throw new Error(
24958
25400
  `Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
24959
25401
  );
24960
25402
  }
24961
- const status = (0, import_node_child_process35.spawnSync)(
25403
+ const status = (0, import_node_child_process36.spawnSync)(
24962
25404
  "git",
24963
25405
  ["-C", target.worktreePath, "status", "--porcelain", "--untracked-files=normal"],
24964
25406
  { encoding: "utf-8" }
@@ -24992,9 +25434,9 @@ async function workspaceUp(branch, opts = {}) {
24992
25434
  (candidate) => defaultWorktreePath(mainRepo, candidate)
24993
25435
  );
24994
25436
  const generatedPath = candidatePaths.find(
24995
- (candidate) => !import_node_fs50.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
25437
+ (candidate) => !import_node_fs51.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
24996
25438
  );
24997
- const selectedPath = opts.path ? import_node_path61.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
25439
+ const selectedPath = opts.path ? import_node_path62.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
24998
25440
  if (!selectedPath) {
24999
25441
  throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
25000
25442
  }
@@ -25003,7 +25445,7 @@ async function workspaceUp(branch, opts = {}) {
25003
25445
  `Branch '${branch}' already uses worktree '${existingBranch.path}', not '${selectedPath}'.`
25004
25446
  );
25005
25447
  }
25006
- if (import_node_fs50.default.existsSync(selectedPath)) {
25448
+ if (import_node_fs51.default.existsSync(selectedPath)) {
25007
25449
  const registered = worktrees.find(
25008
25450
  (worktree) => sameWorkspacePath(worktree.path, selectedPath)
25009
25451
  );
@@ -25022,11 +25464,11 @@ async function workspaceUp(branch, opts = {}) {
25022
25464
  return selectedPath;
25023
25465
  }
25024
25466
  if (!opts.path) assertDefaultWorktreeRootIgnored(mainRepo);
25025
- const add = (0, import_node_child_process35.spawnSync)("git", ["-C", mainRepo, "worktree", "add", selectedPath, branch], {
25467
+ const add = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "worktree", "add", selectedPath, branch], {
25026
25468
  encoding: "utf-8"
25027
25469
  });
25028
25470
  if (add.status !== 0) {
25029
- const addNew = (0, import_node_child_process35.spawnSync)(
25471
+ const addNew = (0, import_node_child_process36.spawnSync)(
25030
25472
  "git",
25031
25473
  ["-C", mainRepo, "worktree", "add", "-b", branch, selectedPath],
25032
25474
  { encoding: "utf-8" }
@@ -25160,8 +25602,8 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
25160
25602
  }
25161
25603
  const result = await mutateWorkspaceRuntime("delete", resolved, worktrees, opts.quiet);
25162
25604
  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], {
25605
+ if (resolved.worktree && !resolved.worktree.prunable && import_node_fs51.default.existsSync(resolved.worktreePath)) {
25606
+ const rm = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
25165
25607
  encoding: "utf-8"
25166
25608
  });
25167
25609
  if (rm.status !== 0) {
@@ -25179,7 +25621,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
25179
25621
  }
25180
25622
  return result;
25181
25623
  };
25182
- return resolved.worktree && !resolved.worktree.prunable && import_node_fs50.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
25624
+ return resolved.worktree && !resolved.worktree.prunable && import_node_fs51.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
25183
25625
  }
25184
25626
  async function workspaceStop(target, opts = {}) {
25185
25627
  return runWorkspaceLifecycle("stop", target, opts);
@@ -25187,13 +25629,13 @@ async function workspaceStop(target, opts = {}) {
25187
25629
  async function workspaceDown(target, opts = {}) {
25188
25630
  return runWorkspaceLifecycle("down", target, opts);
25189
25631
  }
25190
- var import_node_child_process35, import_node_fs50, import_node_path61, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
25632
+ var import_node_child_process36, import_node_fs51, import_node_path62, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
25191
25633
  var init_workspace_lifecycle = __esm({
25192
25634
  "src/core/workspace-lifecycle.ts"() {
25193
25635
  "use strict";
25194
- import_node_child_process35 = require("child_process");
25195
- import_node_fs50 = __toESM(require("fs"));
25196
- import_node_path61 = __toESM(require("path"));
25636
+ import_node_child_process36 = require("child_process");
25637
+ import_node_fs51 = __toESM(require("fs"));
25638
+ import_node_path62 = __toESM(require("path"));
25197
25639
  init_devpod_mutation();
25198
25640
  init_devpod_workspaces();
25199
25641
  init_devsy_mutation();
@@ -25377,7 +25819,7 @@ var init_version = __esm({
25377
25819
 
25378
25820
  // src/cli.ts
25379
25821
  var import_commander = require("commander");
25380
- var CLI_VERSION = true ? "0.0.68" : "0.0.0-dev";
25822
+ var CLI_VERSION = true ? "0.0.70" : "0.0.0-dev";
25381
25823
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
25382
25824
  function withErrorHandling(action2) {
25383
25825
  return async (...args) => {
@@ -25747,13 +26189,13 @@ workspaceCommand2.command("down").description("Delete a workspace's runtime and
25747
26189
  await runWorkspaceDownCommand2(target, options);
25748
26190
  })
25749
26191
  );
25750
- workspaceCommand2.command("journal settle").description(
26192
+ workspaceCommand2.command("journal").description("Inspect and recover workspace lifecycle journals").command("settle").description(
25751
26193
  "Settle an interrupted lifecycle operation as unobservable so ensure and stop can proceed"
25752
26194
  ).argument("[path]", "Checkout path (defaults to current directory)").option("--json", "Output JSON").action(
25753
- withErrorHandling(async (path61, _options, command) => {
26195
+ withErrorHandling(async (path62, _options, command) => {
25754
26196
  const options = command.opts();
25755
26197
  const { runWorkspaceJournalSettleCommand: runWorkspaceJournalSettleCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
25756
- await runWorkspaceJournalSettleCommand2({ ...path61 ? { path: path61 } : {}, ...options });
26198
+ await runWorkspaceJournalSettleCommand2({ ...path62 ? { path: path62 } : {}, ...options });
25757
26199
  })
25758
26200
  );
25759
26201
  function parseVersionRequest(argv) {