@devrouter/cli 0.0.73 → 0.0.74

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
@@ -1177,6 +1177,55 @@ function inspectManagedStopWorkspaceIds(endpoint, composeDirectory) {
1177
1177
  assertUniqueContainerIds(ids2);
1178
1178
  return ids2;
1179
1179
  }
1180
+ function assertManagedStopCheckoutAbsent(endpoint, repoPath) {
1181
+ const list2 = () => {
1182
+ const ids3 = parseDockerLines(
1183
+ runManagedStopDocker(["ps", "-a", "--no-trunc", "--format", "{{.ID}}"], endpoint)
1184
+ );
1185
+ if (ids3.length > 256) throw new Error("Managed stop population exceeds its bound.");
1186
+ ids3.forEach(assertFullContainerId);
1187
+ assertUniqueContainerIds(ids3);
1188
+ return ids3;
1189
+ };
1190
+ const ids2 = list2();
1191
+ const seen = [];
1192
+ const labels = [
1193
+ "com.docker.compose.project.working_dir",
1194
+ "devcontainer.local_folder",
1195
+ "vsch.local.folder"
1196
+ ];
1197
+ const template = '{"id":{{json .Id}},"labels":{"com.docker.compose.project.working_dir":{{json (index .Config.Labels "com.docker.compose.project.working_dir")}},"devcontainer.local_folder":{{json (index .Config.Labels "devcontainer.local_folder")}},"vsch.local.folder":{{json (index .Config.Labels "vsch.local.folder")}}},"mounts":[{{range $i, $m := .Mounts}}{{if $i}},{{end}}{"Type":{{json $m.Type}},"Source":{{json $m.Source}}}{{end}}]}';
1198
+ if (ids2.length) {
1199
+ for (const line of parseDockerLines(
1200
+ runManagedStopDocker(["inspect", "--format", template, ...ids2], endpoint)
1201
+ )) {
1202
+ const value = parseManagedStopJson(line);
1203
+ if (!isRecord(value) || !isRecord(value.labels) || !Array.isArray(value.mounts))
1204
+ throw new Error("Managed stop checkout inspection is malformed.");
1205
+ assertFullContainerId(value.id);
1206
+ seen.push(value.id);
1207
+ for (const label2 of labels) {
1208
+ const field = value.labels[label2];
1209
+ if (field !== null && typeof field !== "string")
1210
+ throw new Error("Managed stop checkout labels are incomplete.");
1211
+ if (typeof field === "string" && sameWorkspacePath(
1212
+ field,
1213
+ label2 === labels[0] ? import_node_path4.default.join(repoPath, ".devcontainer") : repoPath
1214
+ ))
1215
+ throw new Error("Managed stop observed a remaining checkout container.");
1216
+ }
1217
+ for (const mount of value.mounts) {
1218
+ if (!isRecord(mount) || typeof mount.Type !== "string" || typeof mount.Source !== "string")
1219
+ throw new Error("Managed stop checkout mounts are incomplete.");
1220
+ if (mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath))
1221
+ throw new Error("Managed stop observed a remaining checkout mount.");
1222
+ }
1223
+ }
1224
+ }
1225
+ assertUniqueContainerIds(seen);
1226
+ requireSameContainerPopulation(ids2, seen);
1227
+ requireSameContainerPopulation(ids2, list2());
1228
+ }
1180
1229
  function hasExactManagedStopMissingObjectError(stderr, id5) {
1181
1230
  if (typeof stderr !== "string") return false;
1182
1231
  const line = stderr.endsWith("\r\n") ? stderr.slice(0, -2) : stderr.endsWith("\n") ? stderr.slice(0, -1) : stderr;
@@ -4207,7 +4256,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs7.default.read
4207
4256
  const config = parseConfig(parsed ?? {}, configPath);
4208
4257
  const requiredVersion = config.devrouter?.version;
4209
4258
  if (requiredVersion && !hasWarnedVersionMismatch) {
4210
- const cliVersion = true ? "0.0.73" : "0.0.0-dev";
4259
+ const cliVersion = true ? "0.0.74" : "0.0.0-dev";
4211
4260
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
4212
4261
  hasWarnedVersionMismatch = true;
4213
4262
  process.stderr.write(
@@ -5460,7 +5509,7 @@ var init_reliability_contract = __esm({
5460
5509
 
5461
5510
  // src/core/reliability-operation-store.ts
5462
5511
  function installedCliVersion() {
5463
- return true ? "0.0.73" : "0.0.0-dev";
5512
+ return true ? "0.0.74" : "0.0.0-dev";
5464
5513
  }
5465
5514
  function compareCliVersions(left, right) {
5466
5515
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -7671,10 +7720,11 @@ function inspectDevsyRuntimeAbsence(devsyId) {
7671
7720
  encoding: "utf-8",
7672
7721
  timeout: 1e4
7673
7722
  });
7674
- if (!result.error && result.status === 0) {
7723
+ if (result.error || result.status === null) return false;
7724
+ if (result.status === 0) {
7675
7725
  return parseDevsyRuntimeStatus(result.stdout, devsyId) === "not-found";
7676
7726
  }
7677
- const output2 = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n");
7727
+ const output2 = [result.stdout, result.stderr].filter(Boolean).join("\n");
7678
7728
  return /workspace not found/i.test(output2);
7679
7729
  }
7680
7730
  function inspectDevsyWorkspaceOwnership(workspaces, devsyId, worktreePath) {
@@ -8517,8 +8567,10 @@ function listDevpodWorkspacesRaw(options = {}) {
8517
8567
  const result = (0, import_node_child_process9.spawnSync)("devpod", ["list", "--output", "json", "--skip-pro"], {
8518
8568
  encoding: "utf-8"
8519
8569
  });
8520
- if (options.allowMissingExecutable && result.error?.code === "ENOENT")
8521
- return [];
8570
+ if (result.error?.code === "ENOENT") {
8571
+ if (options.readLocalWhenMissing) return readLocalDevpodRegistry();
8572
+ if (options.allowMissingExecutable) return [];
8573
+ }
8522
8574
  if (result.error || result.status !== 0) {
8523
8575
  const details = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
8524
8576
  throw new Error(`devpod list failed: ${details || "devpod is not installed or unavailable"}`);
@@ -8551,11 +8603,110 @@ function listDevpodWorkspacesRaw(options = {}) {
8551
8603
  return workspace;
8552
8604
  });
8553
8605
  }
8554
- var import_node_child_process9;
8606
+ function devpodRegistryRoot() {
8607
+ return import_node_path26.default.resolve(process.env.DEVPOD_HOME || import_node_path26.default.join(import_node_os3.default.homedir(), ".devpod"));
8608
+ }
8609
+ function readLocalDevpodRegistry() {
8610
+ const fail4 = () => new Error("Local DevPod registry evidence is incomplete, unreadable, or changed.");
8611
+ const stamps = /* @__PURE__ */ new Map();
8612
+ const stamp = (stat, identityOnly = false) => JSON.stringify([
8613
+ stat.dev,
8614
+ stat.ino,
8615
+ stat.mode,
8616
+ stat.uid,
8617
+ ...identityOnly ? [] : [stat.size, stat.mtimeMs, stat.ctimeMs]
8618
+ ]);
8619
+ const inspect = (file) => {
8620
+ try {
8621
+ return import_node_fs19.default.lstatSync(file);
8622
+ } catch (error) {
8623
+ if (error.code === "ENOENT") return void 0;
8624
+ throw fail4();
8625
+ }
8626
+ };
8627
+ const directory = (dir, optional = false, identityOnly = false) => {
8628
+ const stat = inspect(dir);
8629
+ if (!stat && !optional || stat && !stat.isDirectory()) throw fail4();
8630
+ stamps.set(dir, { value: stat && stamp(stat, identityOnly), identityOnly });
8631
+ return !!stat;
8632
+ };
8633
+ const entries = (dir) => {
8634
+ const names = import_node_fs19.default.readdirSync(dir).sort();
8635
+ if (names.length > 256) throw fail4();
8636
+ return names;
8637
+ };
8638
+ const result = [];
8639
+ try {
8640
+ const root = devpodRegistryRoot();
8641
+ let ancestor = import_node_path26.default.parse(root).root;
8642
+ let present = directory(ancestor, false, ancestor !== root);
8643
+ for (const part of root.slice(ancestor.length).split(import_node_path26.default.sep).filter(Boolean)) {
8644
+ if (!present) break;
8645
+ ancestor = import_node_path26.default.join(ancestor, part);
8646
+ present = directory(ancestor, true, ancestor !== root);
8647
+ }
8648
+ const contexts = import_node_path26.default.join(root, "contexts");
8649
+ if (present && directory(contexts, true)) {
8650
+ for (const context of entries(contexts)) {
8651
+ const contextDir = import_node_path26.default.join(contexts, context);
8652
+ directory(contextDir);
8653
+ const workspaces = import_node_path26.default.join(contextDir, "workspaces");
8654
+ if (!directory(workspaces, true)) continue;
8655
+ for (const id5 of entries(workspaces)) {
8656
+ if (result.length >= 256) throw fail4();
8657
+ const workspace = import_node_path26.default.join(workspaces, id5);
8658
+ directory(workspace);
8659
+ const file = import_node_path26.default.join(workspace, "workspace.json");
8660
+ const stat = inspect(file);
8661
+ if (!stat?.isFile() || stat.size > 1024 * 1024) throw fail4();
8662
+ stamps.set(file, { value: stamp(stat), identityOnly: false });
8663
+ const fd = import_node_fs19.default.openSync(file, import_node_fs19.default.constants.O_RDONLY | import_node_fs19.default.constants.O_NOFOLLOW);
8664
+ let raw;
8665
+ try {
8666
+ if (stamp(import_node_fs19.default.fstatSync(fd)) !== stamp(stat)) throw fail4();
8667
+ const buffer = Buffer.alloc(stat.size + 1);
8668
+ const bytes2 = import_node_fs19.default.readSync(fd, buffer, 0, buffer.length, 0);
8669
+ if (bytes2 !== stat.size || stamp(import_node_fs19.default.fstatSync(fd)) !== stamp(stat)) throw fail4();
8670
+ raw = JSON.parse(buffer.subarray(0, bytes2).toString("utf8"));
8671
+ } finally {
8672
+ import_node_fs19.default.closeSync(fd);
8673
+ }
8674
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw fail4();
8675
+ const value = raw;
8676
+ if (value.id !== id5 || !value.source || typeof value.source !== "object" || Array.isArray(value.source))
8677
+ throw fail4();
8678
+ const source = value.source;
8679
+ if (source.localFolder !== void 0 && (typeof source.localFolder !== "string" || source.localFolder !== "" && !import_node_path26.default.isAbsolute(source.localFolder)))
8680
+ throw fail4();
8681
+ const remote = ["gitRepository", "image", "container"];
8682
+ if (remote.some((key) => source[key] !== void 0 && typeof source[key] !== "string"))
8683
+ throw fail4();
8684
+ if (!source.localFolder && !remote.some((key) => typeof source[key] === "string" && source[key]))
8685
+ throw fail4();
8686
+ result.push({
8687
+ id: id5,
8688
+ source: { localFolder: source.localFolder ?? "" }
8689
+ });
8690
+ }
8691
+ }
8692
+ }
8693
+ for (const [file, expected] of stamps) {
8694
+ const actual = inspect(file);
8695
+ if ((actual && stamp(actual, expected.identityOnly)) !== expected.value) throw fail4();
8696
+ }
8697
+ return result;
8698
+ } catch {
8699
+ throw fail4();
8700
+ }
8701
+ }
8702
+ var import_node_child_process9, import_node_fs19, import_node_os3, import_node_path26;
8555
8703
  var init_devpod_registry = __esm({
8556
8704
  "src/core/devpod-registry.ts"() {
8557
8705
  "use strict";
8558
8706
  import_node_child_process9 = require("child_process");
8707
+ import_node_fs19 = __toESM(require("fs"));
8708
+ import_node_os3 = __toESM(require("os"));
8709
+ import_node_path26 = __toESM(require("path"));
8559
8710
  }
8560
8711
  });
8561
8712
 
@@ -8573,13 +8724,13 @@ function proveManagedComposePopulation(options) {
8573
8724
  }
8574
8725
  services.add(service);
8575
8726
  const files = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim());
8576
- if (files.some((file) => !file || !import_node_path26.default.isAbsolute(file)) || new Set(files.map((file) => import_node_path26.default.resolve(file))).size !== files.length || files.length < plan.composeFiles.length || plan.composeFiles.some((file, index) => !sameWorkspacePath(file, files[index]))) {
8727
+ if (files.some((file) => !file || !import_node_path27.default.isAbsolute(file)) || new Set(files.map((file) => import_node_path27.default.resolve(file))).size !== files.length || files.length < plan.composeFiles.length || plan.composeFiles.some((file, index) => !sameWorkspacePath(file, files[index]))) {
8577
8728
  throw new Error("Managed stop Compose file identity changed.");
8578
8729
  }
8579
8730
  for (const file of files.slice(plan.composeFiles.length)) {
8580
- if (import_node_path26.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
8581
- import_node_path26.default.basename(file)
8582
- ) || import_node_fs19.default.realpathSync(file) !== import_node_path26.default.join(import_node_fs19.default.realpathSync(providerRoot), import_node_path26.default.relative(providerRoot, file))) {
8731
+ if (import_node_path27.default.dirname(file) !== featureDirectory || !/^docker-compose\.devcontainer\.containerFeatures-[a-zA-Z0-9_-]+\.yml$/.test(
8732
+ import_node_path27.default.basename(file)
8733
+ ) || import_node_fs20.default.realpathSync(file) !== import_node_path27.default.join(import_node_fs20.default.realpathSync(providerRoot), import_node_path27.default.relative(providerRoot, file))) {
8583
8734
  throw new Error("Managed stop refuses a foreign or escaped provider Compose file.");
8584
8735
  }
8585
8736
  }
@@ -8597,12 +8748,12 @@ function proveManagedComposePopulation(options) {
8597
8748
  }
8598
8749
  return primary;
8599
8750
  }
8600
- var import_node_fs19, import_node_path26;
8751
+ var import_node_fs20, import_node_path27;
8601
8752
  var init_managed_compose_population = __esm({
8602
8753
  "src/core/managed-compose-population.ts"() {
8603
8754
  "use strict";
8604
- import_node_fs19 = __toESM(require("fs"));
8605
- import_node_path26 = __toESM(require("path"));
8755
+ import_node_fs20 = __toESM(require("fs"));
8756
+ import_node_path27 = __toESM(require("path"));
8606
8757
  init_workspace();
8607
8758
  }
8608
8759
  });
@@ -8638,7 +8789,7 @@ function isRuntimeInstalled(runtime) {
8638
8789
  function readWorkspaceRuntimeConfig() {
8639
8790
  let raw;
8640
8791
  try {
8641
- raw = JSON.parse(import_node_fs20.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8"));
8792
+ raw = JSON.parse(import_node_fs21.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8"));
8642
8793
  } catch {
8643
8794
  return {};
8644
8795
  }
@@ -8659,7 +8810,7 @@ function readWorkspaceRuntimeConfig() {
8659
8810
  function inspectWorkspaceRuntimeConfig() {
8660
8811
  let rawText;
8661
8812
  try {
8662
- rawText = import_node_fs20.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8");
8813
+ rawText = import_node_fs21.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8");
8663
8814
  } catch {
8664
8815
  return { exists: false, config: {}, problems: [] };
8665
8816
  }
@@ -8710,7 +8861,7 @@ function writeWorkspaceRuntimeConfig(config) {
8710
8861
  }
8711
8862
  next.devsyInactivityTimeout = timeout;
8712
8863
  }
8713
- import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
8864
+ import_node_fs21.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
8714
8865
  writeFileAtomically(RUNTIME_CONFIG_FILE, `${JSON.stringify(next, null, 2)}
8715
8866
  `);
8716
8867
  }
@@ -8818,20 +8969,20 @@ function resetWorkspaceRuntimeCaches() {
8818
8969
  cachedRuntime = void 0;
8819
8970
  cachedSnapshots = void 0;
8820
8971
  }
8821
- var import_node_child_process10, import_node_fs20, import_node_path27, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
8972
+ var import_node_child_process10, import_node_fs21, import_node_path28, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
8822
8973
  var init_workspace_runtime = __esm({
8823
8974
  "src/core/workspace-runtime.ts"() {
8824
8975
  "use strict";
8825
8976
  import_node_child_process10 = require("child_process");
8826
- import_node_fs20 = __toESM(require("fs"));
8827
- import_node_path27 = __toESM(require("path"));
8977
+ import_node_fs21 = __toESM(require("fs"));
8978
+ import_node_path28 = __toESM(require("path"));
8828
8979
  init_atomic_file();
8829
8980
  init_devpod_registry();
8830
8981
  init_devsy_workspaces();
8831
8982
  init_router();
8832
8983
  init_workspace();
8833
8984
  SUPPORTED_RUNTIMES2 = ["devpod", "devsy"];
8834
- RUNTIME_CONFIG_FILE = import_node_path27.default.join(DEVROUTER_HOME, "workspace-runtime.json");
8985
+ RUNTIME_CONFIG_FILE = import_node_path28.default.join(DEVROUTER_HOME, "workspace-runtime.json");
8835
8986
  INACTIVITY_TIMEOUT_PATTERN = /^(?:\d+(?:ms|s|m|h))+$/;
8836
8987
  UnsupportedWorkspaceRuntimeError = class extends Error {
8837
8988
  };
@@ -8958,7 +9109,7 @@ function resolveGitCommonDir(repoPath) {
8958
9109
  if (result.status !== 0 || !output2) {
8959
9110
  throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
8960
9111
  }
8961
- return comparableWorkspacePath(import_node_path28.default.isAbsolute(output2) ? output2 : import_node_path28.default.resolve(repoPath, output2));
9112
+ return comparableWorkspacePath(import_node_path29.default.isAbsolute(output2) ? output2 : import_node_path29.default.resolve(repoPath, output2));
8962
9113
  }
8963
9114
  function resolveGitTopLevel(repoPath) {
8964
9115
  const result = (0, import_node_child_process12.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
@@ -9009,7 +9160,7 @@ function listGitWorktrees(repoPath) {
9009
9160
  return worktrees;
9010
9161
  }
9011
9162
  function ownershipDirectory(repoPath) {
9012
- return import_node_path28.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
9163
+ return import_node_path29.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
9013
9164
  }
9014
9165
  function validateWorkspace(value, label2) {
9015
9166
  if (typeof value !== "string" || wsFromBranch(value) !== value) {
@@ -9037,7 +9188,7 @@ function validateRecord(value, expectedWorkspace) {
9037
9188
  `workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
9038
9189
  );
9039
9190
  }
9040
- if (typeof candidate.worktreePath !== "string" || !import_node_path28.default.isAbsolute(candidate.worktreePath)) {
9191
+ if (typeof candidate.worktreePath !== "string" || !import_node_path29.default.isAbsolute(candidate.worktreePath)) {
9041
9192
  throw new Error("invalid workspace ownership worktreePath");
9042
9193
  }
9043
9194
  if (candidate.branch !== null && typeof candidate.branch !== "string") {
@@ -9055,7 +9206,7 @@ function validateRecord(value, expectedWorkspace) {
9055
9206
  };
9056
9207
  }
9057
9208
  function recordPath(repoPath, workspace) {
9058
- return import_node_path28.default.join(
9209
+ return import_node_path29.default.join(
9059
9210
  ownershipDirectory(repoPath),
9060
9211
  `${validateWorkspace(workspace, "workspace")}.json`
9061
9212
  );
@@ -9063,7 +9214,7 @@ function recordPath(repoPath, workspace) {
9063
9214
  function readRecordFile(filePath, expectedWorkspace) {
9064
9215
  let parsed;
9065
9216
  try {
9066
- parsed = JSON.parse(import_node_fs21.default.readFileSync(filePath, "utf-8"));
9217
+ parsed = JSON.parse(import_node_fs22.default.readFileSync(filePath, "utf-8"));
9067
9218
  } catch (error) {
9068
9219
  if (error instanceof SyntaxError) {
9069
9220
  throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
@@ -9088,7 +9239,7 @@ function listWorkspaceOwnership(repoPath) {
9088
9239
  function listWorkspaceOwnershipInDirectory(directory) {
9089
9240
  let entries;
9090
9241
  try {
9091
- entries = import_node_fs21.default.readdirSync(directory, { withFileTypes: true });
9242
+ entries = import_node_fs22.default.readdirSync(directory, { withFileTypes: true });
9092
9243
  } catch (error) {
9093
9244
  if (error.code === "ENOENT") return [];
9094
9245
  throw error;
@@ -9096,14 +9247,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
9096
9247
  return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
9097
9248
  const workspace = entry.name.slice(0, -".json".length);
9098
9249
  validateWorkspace(workspace, "filename");
9099
- return readRecordFile(import_node_path28.default.join(directory, entry.name), workspace);
9250
+ return readRecordFile(import_node_path29.default.join(directory, entry.name), workspace);
9100
9251
  });
9101
9252
  }
9102
9253
  function writeWorkspaceOwnershipInDirectory(directory, input2) {
9103
9254
  const workspace = validateWorkspace(input2.workspace, "workspace");
9104
9255
  const devpodId = validateWorkspace(input2.devpodId, "devpodId");
9105
9256
  const worktreePath = comparableWorkspacePath(input2.worktreePath);
9106
- const filePath = import_node_path28.default.join(directory, `${workspace}.json`);
9257
+ const filePath = import_node_path29.default.join(directory, `${workspace}.json`);
9107
9258
  const now = (/* @__PURE__ */ new Date()).toISOString();
9108
9259
  const records = listWorkspaceOwnershipInDirectory(directory);
9109
9260
  const existing = records.find((record6) => record6.workspace === workspace);
@@ -9139,9 +9290,9 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
9139
9290
  return record5;
9140
9291
  }
9141
9292
  function removeWorkspaceOwnershipInDirectory(directory, workspace) {
9142
- const filePath = import_node_path28.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
9293
+ const filePath = import_node_path29.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
9143
9294
  try {
9144
- import_node_fs21.default.rmSync(filePath);
9295
+ import_node_fs22.default.rmSync(filePath);
9145
9296
  return true;
9146
9297
  } catch (error) {
9147
9298
  if (error.code === "ENOENT") return false;
@@ -9152,7 +9303,7 @@ function sameOwnershipRecord(left, right) {
9152
9303
  return left.version === right.version && left.workspace === right.workspace && sameWorkspacePath(left.worktreePath, right.worktreePath) && left.branch === right.branch && left.devpodId === right.devpodId && left.createdAt === right.createdAt && left.updatedAt === right.updatedAt;
9153
9304
  }
9154
9305
  function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
9155
- const filePath = import_node_path28.default.join(
9306
+ const filePath = import_node_path29.default.join(
9156
9307
  directory,
9157
9308
  `${validateWorkspace(expected.workspace, "workspace")}.json`
9158
9309
  );
@@ -9164,14 +9315,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
9164
9315
  throw error;
9165
9316
  }
9166
9317
  if (!sameOwnershipRecord(current, expected)) return "changed";
9167
- import_node_fs21.default.rmSync(filePath);
9318
+ import_node_fs22.default.rmSync(filePath);
9168
9319
  return "removed";
9169
9320
  }
9170
9321
  function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
9171
9322
  const directory = ownershipDirectory(repoPath);
9172
- import_node_fs21.default.mkdirSync(directory, { recursive: true });
9323
+ import_node_fs22.default.mkdirSync(directory, { recursive: true });
9173
9324
  return withFileLockSync(
9174
- import_node_path28.default.join(directory, ".lock"),
9325
+ import_node_path29.default.join(directory, ".lock"),
9175
9326
  {
9176
9327
  activity: "workspace ownership transaction",
9177
9328
  target: `'${repoPath}'`,
@@ -9204,7 +9355,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
9204
9355
  try {
9205
9356
  workspace = readPersistedWorkspace(worktree.path);
9206
9357
  } catch (error) {
9207
- if (!import_node_fs21.default.existsSync(worktree.path)) continue;
9358
+ if (!import_node_fs22.default.existsSync(worktree.path)) continue;
9208
9359
  throw error;
9209
9360
  }
9210
9361
  if (!workspace) continue;
@@ -9367,7 +9518,7 @@ function inspectWorkspaceOwnership(record5, worktrees, devpods) {
9367
9518
  if (worktree?.locked) {
9368
9519
  return { ownerStatus: "locked", devpodStatus, worktree };
9369
9520
  }
9370
- if ((!worktree || worktree.prunable) && import_node_fs21.default.existsSync(record5.worktreePath)) {
9521
+ if ((!worktree || worktree.prunable) && import_node_fs22.default.existsSync(record5.worktreePath)) {
9371
9522
  return { ownerStatus: "conflict", devpodStatus, worktree };
9372
9523
  }
9373
9524
  if (!worktree || worktree.prunable) {
@@ -9391,20 +9542,20 @@ function listMissingWorkspaceOwnership(repoPath) {
9391
9542
  (record5) => inspectWorkspaceOwnership(record5, worktrees, void 0).ownerStatus === "missing"
9392
9543
  );
9393
9544
  }
9394
- var import_node_child_process12, import_node_fs21, import_node_path28, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
9545
+ var import_node_child_process12, import_node_fs22, import_node_path29, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
9395
9546
  var init_workspace_ownership = __esm({
9396
9547
  "src/core/workspace-ownership.ts"() {
9397
9548
  "use strict";
9398
9549
  import_node_child_process12 = require("child_process");
9399
- import_node_fs21 = __toESM(require("fs"));
9400
- import_node_path28 = __toESM(require("path"));
9550
+ import_node_fs22 = __toESM(require("fs"));
9551
+ import_node_path29 = __toESM(require("path"));
9401
9552
  init_atomic_file();
9402
9553
  init_devpod_workspaces();
9403
9554
  init_file_lock();
9404
9555
  init_workspace();
9405
9556
  READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
9406
9557
  OWNERSHIP_VERSION = 1;
9407
- OWNERSHIP_DIR = import_node_path28.default.join("devrouter", "workspaces");
9558
+ OWNERSHIP_DIR = import_node_path29.default.join("devrouter", "workspaces");
9408
9559
  }
9409
9560
  });
9410
9561
 
@@ -9653,12 +9804,12 @@ function managedStopRouteReferences(state) {
9653
9804
  (name) => ["http", "tcp"].map((protocol) => ({ repoPath: state.repoPath, name, protocol }))
9654
9805
  );
9655
9806
  }
9656
- var import_node_os3, import_node_path29, import_node_util8;
9807
+ var import_node_os4, import_node_path30, import_node_util8;
9657
9808
  var init_managed_stop_recovery = __esm({
9658
9809
  "src/core/managed-stop-recovery.ts"() {
9659
9810
  "use strict";
9660
- import_node_os3 = __toESM(require("os"));
9661
- import_node_path29 = __toESM(require("path"));
9811
+ import_node_os4 = __toESM(require("os"));
9812
+ import_node_path30 = __toESM(require("path"));
9662
9813
  import_node_util8 = require("util");
9663
9814
  init_devpod_environment();
9664
9815
  init_devpod_registry();
@@ -9805,8 +9956,8 @@ function tlsSetupCommand(repoPath) {
9805
9956
  }
9806
9957
  function getMkcertRootCAPath(options = {}) {
9807
9958
  ensureMkcert();
9808
- const rootCAPath = import_node_path30.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
9809
- if (!import_node_fs22.default.existsSync(rootCAPath)) {
9959
+ const rootCAPath = import_node_path31.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
9960
+ if (!import_node_fs23.default.existsSync(rootCAPath)) {
9810
9961
  throw new Error(
9811
9962
  `mkcert root CA was not found at '${rootCAPath}'. Run: ${tlsSetupCommand(options.repoPath)}`
9812
9963
  );
@@ -9907,11 +10058,11 @@ function compactTLSCertificateHosts(hosts) {
9907
10058
  );
9908
10059
  }
9909
10060
  function readCurrentCertificateHosts(options = {}) {
9910
- if (!import_node_fs22.default.existsSync(CERT_FILE)) {
10061
+ if (!import_node_fs23.default.existsSync(CERT_FILE)) {
9911
10062
  return [];
9912
10063
  }
9913
10064
  try {
9914
- const pem = import_node_fs22.default.readFileSync(CERT_FILE, "utf-8");
10065
+ const pem = import_node_fs23.default.readFileSync(CERT_FILE, "utf-8");
9915
10066
  return parseCertificateDnsHosts(pem);
9916
10067
  } catch (error) {
9917
10068
  if (options.replaceMalformed) {
@@ -10039,20 +10190,20 @@ Run: ${tlsSetupCommand(options.repoPath)}`
10039
10190
  );
10040
10191
  }
10041
10192
  }
10042
- var import_node_child_process14, import_node_crypto12, import_node_fs22, import_node_path30, DEFAULT_TLS_CERT_HOSTS, TLS_CERTIFICATE_LOCK_FILE, TLS_CERTIFICATE_LOCK_WAIT_MS, TLS_CERTIFICATE_WRITE_ATTEMPTS;
10193
+ var import_node_child_process14, import_node_crypto12, import_node_fs23, import_node_path31, DEFAULT_TLS_CERT_HOSTS, TLS_CERTIFICATE_LOCK_FILE, TLS_CERTIFICATE_LOCK_WAIT_MS, TLS_CERTIFICATE_WRITE_ATTEMPTS;
10043
10194
  var init_tls = __esm({
10044
10195
  "src/core/tls.ts"() {
10045
10196
  "use strict";
10046
10197
  import_node_child_process14 = require("child_process");
10047
10198
  import_node_crypto12 = require("crypto");
10048
- import_node_fs22 = __toESM(require("fs"));
10049
- import_node_path30 = __toESM(require("path"));
10199
+ import_node_fs23 = __toESM(require("fs"));
10200
+ import_node_path31 = __toESM(require("path"));
10050
10201
  init_docker();
10051
10202
  init_file_lock();
10052
10203
  init_host_routes();
10053
10204
  init_router();
10054
10205
  DEFAULT_TLS_CERT_HOSTS = ["localhost", "*.localhost"];
10055
- TLS_CERTIFICATE_LOCK_FILE = import_node_path30.default.join(DEVROUTER_HOME, "tls-certificate.lock");
10206
+ TLS_CERTIFICATE_LOCK_FILE = import_node_path31.default.join(DEVROUTER_HOME, "tls-certificate.lock");
10056
10207
  TLS_CERTIFICATE_LOCK_WAIT_MS = 6e4;
10057
10208
  TLS_CERTIFICATE_WRITE_ATTEMPTS = 2;
10058
10209
  }
@@ -10133,6 +10284,174 @@ var init_route_publication = __esm({
10133
10284
  }
10134
10285
  });
10135
10286
 
10287
+ // src/core/traefik-route-health.ts
10288
+ function sleep(ms) {
10289
+ return new Promise((resolve) => setTimeout(resolve, ms));
10290
+ }
10291
+ function inspectRouterApi(protocol) {
10292
+ const result = (0, import_node_child_process15.spawnSync)(
10293
+ "curl",
10294
+ [
10295
+ "--silent",
10296
+ "--show-error",
10297
+ "--fail",
10298
+ "--max-time",
10299
+ ROUTER_API_REQUEST_TIMEOUT_SECONDS,
10300
+ `${ROUTER_API_BASE}/${protocol}/routers?per_page=${ROUTER_API_PAGE_SIZE}`
10301
+ ],
10302
+ { encoding: "utf-8" }
10303
+ );
10304
+ if (result.status !== 0) {
10305
+ return {
10306
+ ok: false,
10307
+ details: result.stderr?.trim() || `curl exited with status ${result.status ?? "unknown"}`
10308
+ };
10309
+ }
10310
+ let value;
10311
+ try {
10312
+ value = JSON.parse(result.stdout);
10313
+ } catch {
10314
+ return { ok: false, details: `${protocol.toUpperCase()} router API returned invalid JSON` };
10315
+ }
10316
+ if (!Array.isArray(value)) {
10317
+ return { ok: false, details: `${protocol.toUpperCase()} router API returned a non-array` };
10318
+ }
10319
+ if (value.some((item) => !isRecord5(item) || typeof item.name !== "string" || !item.name))
10320
+ return {
10321
+ ok: false,
10322
+ details: `${protocol.toUpperCase()} router API returned malformed entries`
10323
+ };
10324
+ return {
10325
+ ok: true,
10326
+ reachedPageLimit: value.length >= ROUTER_API_PAGE_SIZE,
10327
+ names: new Set(
10328
+ value.flatMap((item) => {
10329
+ if (!item || typeof item !== "object") return [];
10330
+ const name = item.name;
10331
+ return typeof name === "string" ? [name] : [];
10332
+ })
10333
+ )
10334
+ };
10335
+ }
10336
+ function isRecord5(value) {
10337
+ return value !== null && typeof value === "object" && !Array.isArray(value);
10338
+ }
10339
+ function inspectExpectedRoutes(routes, expectation) {
10340
+ const mismatched = [];
10341
+ const failures = [];
10342
+ for (const protocol of ["http", "tcp"]) {
10343
+ const expected = routes.filter((route) => (route.protocol ?? "http") === protocol).map((route) => buildHostRouteRouterName(route.repoPath, route.name));
10344
+ if (expected.length === 0) continue;
10345
+ const result = inspectRouterApi(protocol);
10346
+ if (!result.ok) {
10347
+ failures.push(result.details);
10348
+ mismatched.push(...expected);
10349
+ continue;
10350
+ }
10351
+ const unexpected = expected.filter(
10352
+ (name) => expectation === "loaded" ? !result.names.has(name) : result.names.has(name)
10353
+ );
10354
+ const boundedAbsenceIsUncertain = result.reachedPageLimit && (expectation === "loaded" ? unexpected.length > 0 : unexpected.length === 0);
10355
+ if (boundedAbsenceIsUncertain) {
10356
+ failures.push(
10357
+ `${protocol.toUpperCase()} router API reached the ${ROUTER_API_PAGE_SIZE}-entry safety limit`
10358
+ );
10359
+ }
10360
+ mismatched.push(...unexpected);
10361
+ if (expectation === "removed" && unexpected.length === 0 && result.reachedPageLimit) {
10362
+ mismatched.push(...expected);
10363
+ }
10364
+ }
10365
+ return mismatched.length === 0 ? { ok: true } : {
10366
+ ok: false,
10367
+ routes: mismatched,
10368
+ details: failures.length > 0 ? failures.join("; ") : expectation === "loaded" ? "router names are absent" : "router names are still present"
10369
+ };
10370
+ }
10371
+ async function waitForExpectedRoutes(routes, expectation, timeoutMs, pollIntervalMs) {
10372
+ const deadline = Date.now() + timeoutMs;
10373
+ let result;
10374
+ do {
10375
+ result = inspectExpectedRoutes(routes, expectation);
10376
+ if (result.ok) return result;
10377
+ if (Date.now() < deadline) {
10378
+ await sleep(Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
10379
+ }
10380
+ } while (Date.now() < deadline);
10381
+ return result;
10382
+ }
10383
+ async function ensureTraefikRouteExpectation(routes, expectation, options) {
10384
+ if (routes.length === 0) return { restarted: false };
10385
+ const initialTimeoutMs = options.initialTimeoutMs ?? DEFAULT_INITIAL_TIMEOUT_MS;
10386
+ const recoveryTimeoutMs = options.recoveryTimeoutMs ?? DEFAULT_RECOVERY_TIMEOUT_MS;
10387
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
10388
+ const initial = await waitForExpectedRoutes(
10389
+ routes,
10390
+ expectation,
10391
+ initialTimeoutMs,
10392
+ pollIntervalMs
10393
+ );
10394
+ if (initial.ok) return { restarted: false };
10395
+ if (options.allowRestart === false)
10396
+ throw new Error(
10397
+ "Traefik routes did not reload; shared router restart is disabled for this operation."
10398
+ );
10399
+ import_node_fs24.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
10400
+ return withFileLock(
10401
+ ROUTER_RELOAD_LOCK_FILE,
10402
+ {
10403
+ activity: "Traefik route reload recovery",
10404
+ waitMs: initialTimeoutMs + recoveryTimeoutMs + 1e4,
10405
+ fair: true,
10406
+ onWait: createStderrWaitReporter("Traefik route reload recovery", "shared router")
10407
+ },
10408
+ async () => {
10409
+ const recheck = await waitForExpectedRoutes(routes, expectation, 0, pollIntervalMs);
10410
+ if (recheck.ok) return { restarted: false };
10411
+ claimLifecycleEffect();
10412
+ restartRouterStack();
10413
+ const recovered = await waitForExpectedRoutes(
10414
+ routes,
10415
+ expectation,
10416
+ recoveryTimeoutMs,
10417
+ pollIntervalMs
10418
+ );
10419
+ if (recovered.ok) return { restarted: true };
10420
+ const action2 = expectation === "loaded" ? "load" : "remove";
10421
+ throw new Error(
10422
+ `Traefik did not ${action2} file-provider routes after one restart: ${recovered.routes.join(", ")} (${recovered.details}). Inspect: devrouter logs --tail 100`
10423
+ );
10424
+ }
10425
+ );
10426
+ }
10427
+ async function ensureTraefikRoutesRemoved(routes, options = {}) {
10428
+ return ensureTraefikRouteExpectation(routes, "removed", options);
10429
+ }
10430
+ function assertTraefikRoutesRemoved(routes) {
10431
+ const result = inspectExpectedRoutes(routes, "removed");
10432
+ if (!result.ok) throw new Error(`Workspace route cessation is not proven: ${result.details}`);
10433
+ }
10434
+ var import_node_child_process15, import_node_fs24, import_node_path32, 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;
10435
+ var init_traefik_route_health = __esm({
10436
+ "src/core/traefik-route-health.ts"() {
10437
+ "use strict";
10438
+ import_node_child_process15 = require("child_process");
10439
+ import_node_fs24 = __toESM(require("fs"));
10440
+ import_node_path32 = __toESM(require("path"));
10441
+ init_file_lock();
10442
+ init_host_routes();
10443
+ init_reliability_context();
10444
+ init_router();
10445
+ ROUTER_API_BASE = "http://127.0.0.1:8080/api";
10446
+ ROUTER_RELOAD_LOCK_FILE = import_node_path32.default.join(DEVROUTER_HOME, "router-reload.lock");
10447
+ ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
10448
+ DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
10449
+ DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
10450
+ DEFAULT_POLL_INTERVAL_MS = 250;
10451
+ ROUTER_API_PAGE_SIZE = 1e3;
10452
+ }
10453
+ });
10454
+
10136
10455
  // src/core/managed-devsy-stop.ts
10137
10456
  function sameSet(left, right) {
10138
10457
  return JSON.stringify([...left].sort()) === JSON.stringify([...right].sort());
@@ -10145,8 +10464,71 @@ function containerIdentity(container) {
10145
10464
  mounts: container.mounts.map((mount) => JSON.stringify(mount)).sort()
10146
10465
  });
10147
10466
  }
10467
+ function proveInitialManagedDevsyAbsence(repoPath, expectedId) {
10468
+ if (!isLinkedWorktree(repoPath)) return void 0;
10469
+ const workspace = resolveWorktreeWorkspace(repoPath);
10470
+ if (!workspace || readManagedRuntimeState(repoPath, workspace)) return void 0;
10471
+ if (!import_node_fs25.default.existsSync(import_node_path33.default.join(repoPath, ".devrouter.yml"))) return void 0;
10472
+ const runtime = loadRuntimeConfig(repoPath, workspace);
10473
+ if (!runtime.config.managedRuntime) return void 0;
10474
+ const routes = proxyAppsFromConfig(runtime.config).flatMap(
10475
+ (app) => ["http", "tcp"].map((protocol) => ({ repoPath, name: app.name, protocol }))
10476
+ );
10477
+ const record5 = readWorkspaceOwnership(repoPath, workspace);
10478
+ if (!record5 || expectedId !== void 0 && record5.devpodId !== expectedId || record5.workspace !== workspace || !sameWorkspacePath(record5.worktreePath, repoPath))
10479
+ throw new Error("Initial managed stop requires exact linked ownership.");
10480
+ const devsyId = record5.devpodId;
10481
+ const ownership = inspectDevsyWorkspaceOwnership(listDevsyWorkspaces(), devsyId, repoPath);
10482
+ if (ownership.status === "conflict") throw new Error(ownership.reason);
10483
+ if (ownership.status !== "absent") return void 0;
10484
+ const gitCommonDir = resolveGitCommonDir(repoPath);
10485
+ const endpoint = resolveManagedStopEndpoint();
10486
+ if (!supportsManagedStopBaseline(endpoint))
10487
+ throw new Error("Initial managed stop requires a local Docker endpoint.");
10488
+ const daemon = inspectManagedStopDaemon(endpoint);
10489
+ const legacyHome = devpodRegistryRoot();
10490
+ const readLegacy = () => listDevpodWorkspacesRaw({ readLocalWhenMissing: true }).map(({ id: id5, source }) => ({ id: id5, source: { localFolder: source.localFolder } })).sort(
10491
+ (a, b) => a.id.localeCompare(b.id) || a.source.localFolder.localeCompare(b.source.localFolder)
10492
+ );
10493
+ const legacyWorkspaces = readLegacy();
10494
+ const observe = () => {
10495
+ resetWorkspaceRuntimeCaches();
10496
+ if (resolveWorkspaceRuntimeOrDefault(repoPath) !== "devsy" || !isLinkedWorktree(repoPath) || resolveWorktreeWorkspace(repoPath) !== workspace || resolveGitCommonDir(repoPath) !== gitCommonDir || !(0, import_node_util9.isDeepStrictEqual)(readWorkspaceOwnership(repoPath, workspace), record5) || inspectWorkspaceOwnership(record5, listGitWorktrees(repoPath), void 0).ownerStatus !== "present" || readManagedRuntimeState(repoPath, workspace))
10497
+ throw new Error("Initial managed stop ownership or retained state changed.");
10498
+ if (devpodRegistryRoot() !== legacyHome || !(0, import_node_util9.isDeepStrictEqual)(readLegacy(), legacyWorkspaces))
10499
+ throw new Error("Initial managed stop legacy registry evidence changed.");
10500
+ if (inspectDevsyWorkspaceOwnership(listDevsyWorkspaces(), devsyId, repoPath).status !== "absent" || legacyWorkspaces.some(
10501
+ (entry) => entry.id === devsyId || entry.source.localFolder !== "" && sameWorkspacePath(entry.source.localFolder, repoPath)
10502
+ ))
10503
+ throw new Error("Initial managed stop requires both provider registrations absent.");
10504
+ if (!inspectDevsyRuntimeAbsence(devsyId))
10505
+ throw new Error("Initial managed stop requires positive runtime not-found.");
10506
+ if (resolveManagedStopEndpoint() !== endpoint || inspectManagedStopDaemon(endpoint) !== daemon)
10507
+ throw new Error("Initial managed stop Docker identity changed.");
10508
+ if (inspectProviderRunnerContainers(endpoint, devsyId).length)
10509
+ throw new Error("Initial managed stop observed a remaining provider runner.");
10510
+ assertManagedStopCheckoutAbsent(endpoint, repoPath);
10511
+ if (listHostRouteState().some((route) => sameWorkspacePath(route.repoPath, repoPath)))
10512
+ throw new Error("Initial managed stop observed remaining workspace routes.");
10513
+ assertTraefikRoutesRemoved(routes);
10514
+ if (resolveManagedStopEndpoint() !== endpoint || inspectManagedStopDaemon(endpoint) !== daemon)
10515
+ throw new Error("Initial managed stop Docker identity changed.");
10516
+ };
10517
+ observe();
10518
+ observe();
10519
+ return {
10520
+ workspace,
10521
+ record: record5,
10522
+ gitCommonDir,
10523
+ endpoint,
10524
+ daemon,
10525
+ routes,
10526
+ legacyHome,
10527
+ legacyWorkspaces
10528
+ };
10529
+ }
10148
10530
  function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
10149
- if (!import_node_fs23.default.existsSync(import_node_path31.default.join(repoPath, ".devrouter.yml"))) return false;
10531
+ if (!import_node_fs25.default.existsSync(import_node_path33.default.join(repoPath, ".devrouter.yml"))) return false;
10150
10532
  const linked = isLinkedWorktree(repoPath);
10151
10533
  const workspace = linked ? resolveWorktreeWorkspace(repoPath) : void 0;
10152
10534
  const registration2 = () => {
@@ -10174,8 +10556,8 @@ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
10174
10556
  profile: runtime.resolvedProfile,
10175
10557
  linked
10176
10558
  });
10177
- const providerRoot = import_node_path31.default.resolve(process.env.DEVSY_HOME || import_node_path31.default.join(import_node_os4.default.homedir(), ".devsy"));
10178
- const featureDirectory = import_node_path31.default.join(
10559
+ const providerRoot = import_node_path33.default.resolve(process.env.DEVSY_HOME || import_node_path33.default.join(import_node_os5.default.homedir(), ".devsy"));
10560
+ const featureDirectory = import_node_path33.default.join(
10179
10561
  providerRoot,
10180
10562
  "contexts",
10181
10563
  owner.context,
@@ -10246,7 +10628,10 @@ function stopRetainedManagedDevsyWorkspace(options) {
10246
10628
  const linked = isLinkedWorktree(repoPath);
10247
10629
  const workspace = linked ? resolveWorktreeWorkspace(repoPath) : void 0;
10248
10630
  const retainedState = readManagedRuntimeState(repoPath, workspace);
10249
- if (!retainedState) return stopInitialManagedDevsyWorkspace(repoPath, devsyId);
10631
+ if (!retainedState) {
10632
+ if (proveInitialManagedDevsyAbsence(repoPath, devsyId)) return "proven-absent";
10633
+ return stopInitialManagedDevsyWorkspace(repoPath, devsyId);
10634
+ }
10250
10635
  const state = retainedState;
10251
10636
  if (state.devpodId !== devsyId || linked && !workspace) {
10252
10637
  throw new Error("Managed stop requires the exact retained workspace identity.");
@@ -10307,7 +10692,7 @@ function stopRetainedManagedDevsyWorkspace(options) {
10307
10692
  const owned = inspectWorkspaceContainers().filter(
10308
10693
  (container) => sameWorkspacePath(
10309
10694
  container.labels["com.docker.compose.project.working_dir"] ?? "",
10310
- import_node_path31.default.join(repoPath, ".devcontainer")
10695
+ import_node_path33.default.join(repoPath, ".devcontainer")
10311
10696
  )
10312
10697
  );
10313
10698
  if (owned.length !== 0) {
@@ -10336,8 +10721,8 @@ function stopRetainedManagedDevsyWorkspace(options) {
10336
10721
  if (!context || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(context) || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(devsyId)) {
10337
10722
  throw new Error("Managed stop requires a valid exact Devsy context and workspace ID.");
10338
10723
  }
10339
- const devsyRoot = import_node_path31.default.resolve(process.env.DEVSY_HOME || import_node_path31.default.join(import_node_os4.default.homedir(), ".devsy"));
10340
- const featureDirectory = import_node_path31.default.join(
10724
+ const devsyRoot = import_node_path33.default.resolve(process.env.DEVSY_HOME || import_node_path33.default.join(import_node_os5.default.homedir(), ".devsy"));
10725
+ const featureDirectory = import_node_path33.default.join(
10341
10726
  devsyRoot,
10342
10727
  "contexts",
10343
10728
  context,
@@ -10441,23 +10826,25 @@ function stopRetainedManagedDevsyWorkspace(options) {
10441
10826
  if (providerFailed) throw providerError;
10442
10827
  return true;
10443
10828
  }
10444
- var import_node_fs23, import_node_os4, import_node_path31, import_node_util9;
10829
+ var import_node_fs25, import_node_os5, import_node_path33, import_node_util9;
10445
10830
  var init_managed_devsy_stop = __esm({
10446
10831
  "src/core/managed-devsy-stop.ts"() {
10447
10832
  "use strict";
10448
- import_node_fs23 = __toESM(require("fs"));
10449
- import_node_os4 = __toESM(require("os"));
10450
- import_node_path31 = __toESM(require("path"));
10833
+ import_node_fs25 = __toESM(require("fs"));
10834
+ import_node_os5 = __toESM(require("os"));
10835
+ import_node_path33 = __toESM(require("path"));
10451
10836
  import_node_util9 = require("util");
10452
10837
  init_devcontainer_profile();
10453
10838
  init_devpod_environment();
10454
10839
  init_devpod_registry();
10455
10840
  init_devsy_workspaces();
10841
+ init_host_routes();
10456
10842
  init_managed_compose_population();
10457
10843
  init_managed_runtime_state();
10458
10844
  init_managed_stop_recovery();
10459
10845
  init_repo_config();
10460
10846
  init_route_publication();
10847
+ init_traefik_route_health();
10461
10848
  init_workspace();
10462
10849
  init_workspace_ownership();
10463
10850
  init_workspace_runtime();
@@ -11003,7 +11390,7 @@ function hasControlCharacter2(value) {
11003
11390
  function readPolicyFile(filePath) {
11004
11391
  let initialStats;
11005
11392
  try {
11006
- initialStats = import_node_fs24.default.lstatSync(filePath);
11393
+ initialStats = import_node_fs26.default.lstatSync(filePath);
11007
11394
  } catch (error) {
11008
11395
  if (isErrno(error, "ENOENT")) return { status: "absent" };
11009
11396
  return { status: "invalid", error: "network policy could not be read" };
@@ -11016,16 +11403,16 @@ function readPolicyFile(filePath) {
11016
11403
  }
11017
11404
  let fileDescriptor;
11018
11405
  try {
11019
- fileDescriptor = import_node_fs24.default.openSync(
11406
+ fileDescriptor = import_node_fs26.default.openSync(
11020
11407
  filePath,
11021
- import_node_fs24.default.constants.O_RDONLY | import_node_fs24.default.constants.O_NONBLOCK | (import_node_fs24.default.constants.O_NOFOLLOW ?? 0)
11408
+ import_node_fs26.default.constants.O_RDONLY | import_node_fs26.default.constants.O_NONBLOCK | (import_node_fs26.default.constants.O_NOFOLLOW ?? 0)
11022
11409
  );
11023
11410
  } catch (error) {
11024
11411
  if (isErrno(error, "ENOENT")) return { status: "absent" };
11025
11412
  return { status: "invalid", error: "network policy could not be read" };
11026
11413
  }
11027
11414
  try {
11028
- const openedStats = import_node_fs24.default.fstatSync(fileDescriptor);
11415
+ const openedStats = import_node_fs26.default.fstatSync(fileDescriptor);
11029
11416
  if (!openedStats.isFile()) {
11030
11417
  return { status: "invalid", error: "network policy must be a regular file" };
11031
11418
  }
@@ -11038,12 +11425,12 @@ function readPolicyFile(filePath) {
11038
11425
  const buffer = Buffer.alloc(openedStats.size);
11039
11426
  let offset = 0;
11040
11427
  while (offset < buffer.length) {
11041
- const bytesRead = import_node_fs24.default.readSync(fileDescriptor, buffer, offset, buffer.length - offset, null);
11428
+ const bytesRead = import_node_fs26.default.readSync(fileDescriptor, buffer, offset, buffer.length - offset, null);
11042
11429
  if (bytesRead === 0)
11043
11430
  return { status: "invalid", error: "network policy changed during read" };
11044
11431
  offset += bytesRead;
11045
11432
  }
11046
- const finalStats = import_node_fs24.default.fstatSync(fileDescriptor);
11433
+ const finalStats = import_node_fs26.default.fstatSync(fileDescriptor);
11047
11434
  if (!finalStats.isFile() || finalStats.size !== openedStats.size) {
11048
11435
  return { status: "invalid", error: "network policy changed during read" };
11049
11436
  }
@@ -11052,7 +11439,7 @@ function readPolicyFile(filePath) {
11052
11439
  return { status: "invalid", error: "network policy could not be read" };
11053
11440
  } finally {
11054
11441
  try {
11055
- import_node_fs24.default.closeSync(fileDescriptor);
11442
+ import_node_fs26.default.closeSync(fileDescriptor);
11056
11443
  } catch {
11057
11444
  }
11058
11445
  }
@@ -11069,16 +11456,16 @@ function assertDisjoint(cidrs, message) {
11069
11456
  function isErrno(error, code) {
11070
11457
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
11071
11458
  }
11072
- 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;
11459
+ var import_node_fs26, import_node_path34, 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;
11073
11460
  var init_network_policy = __esm({
11074
11461
  "src/core/network-policy.ts"() {
11075
11462
  "use strict";
11076
- import_node_fs24 = __toESM(require("fs"));
11077
- import_node_path32 = __toESM(require("path"));
11463
+ import_node_fs26 = __toESM(require("fs"));
11464
+ import_node_path34 = __toESM(require("path"));
11078
11465
  init_network_capacity();
11079
11466
  init_router();
11080
11467
  NETWORK_POLICY_VERSION = 1;
11081
- NETWORK_POLICY_FILE = import_node_path32.default.join(DEVROUTER_HOME, "network-policy.json");
11468
+ NETWORK_POLICY_FILE = import_node_path34.default.join(DEVROUTER_HOME, "network-policy.json");
11082
11469
  DEFAULT_NETWORK_POLICY_ENDPOINT_RESERVE = 8;
11083
11470
  NETWORK_POLICY_PREFIX_LENGTHS = [24, 25, 26];
11084
11471
  MAX_NETWORK_POLICY_FILE_BYTES = 64 * 1024;
@@ -11179,27 +11566,27 @@ function assertExactKeys(value, keys2) {
11179
11566
  function readDocument(store) {
11180
11567
  let descriptor;
11181
11568
  try {
11182
- descriptor = import_node_fs25.default.openSync(
11569
+ descriptor = import_node_fs27.default.openSync(
11183
11570
  store.filePath,
11184
- import_node_fs25.default.constants.O_RDONLY | import_node_fs25.default.constants.O_NONBLOCK | (import_node_fs25.default.constants.O_NOFOLLOW ?? 0)
11571
+ import_node_fs27.default.constants.O_RDONLY | import_node_fs27.default.constants.O_NONBLOCK | (import_node_fs27.default.constants.O_NOFOLLOW ?? 0)
11185
11572
  );
11186
11573
  } catch (error) {
11187
11574
  if (isErrno2(error, "ENOENT")) return void 0;
11188
11575
  throw claimError("invalid-state", "network claim state could not be read");
11189
11576
  }
11190
11577
  try {
11191
- const stat = import_node_fs25.default.fstatSync(descriptor);
11578
+ const stat = import_node_fs27.default.fstatSync(descriptor);
11192
11579
  if (!stat.isFile() || stat.size > NETWORK_CLAIMS_MAX_FILE_BYTES || (stat.mode & 63) !== 0) {
11193
11580
  throw claimError("invalid-state", "network claim state is not a bounded private file");
11194
11581
  }
11195
11582
  const buffer = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
11196
11583
  let count = 0;
11197
11584
  while (count < buffer.length) {
11198
- const bytesRead = import_node_fs25.default.readSync(descriptor, buffer, count, buffer.length - count, null);
11585
+ const bytesRead = import_node_fs27.default.readSync(descriptor, buffer, count, buffer.length - count, null);
11199
11586
  if (bytesRead === 0) break;
11200
11587
  count += bytesRead;
11201
11588
  }
11202
- const finalStat = import_node_fs25.default.fstatSync(descriptor);
11589
+ const finalStat = import_node_fs27.default.fstatSync(descriptor);
11203
11590
  if (count > NETWORK_CLAIMS_MAX_FILE_BYTES || finalStat.size !== stat.size) {
11204
11591
  throw claimError("invalid-state", "network claim state changed during read");
11205
11592
  }
@@ -11211,7 +11598,7 @@ function readDocument(store) {
11211
11598
  }
11212
11599
  return validateDocument(parsed, store.daemonId);
11213
11600
  } finally {
11214
- import_node_fs25.default.closeSync(descriptor);
11601
+ import_node_fs27.default.closeSync(descriptor);
11215
11602
  }
11216
11603
  }
11217
11604
  function validateDocument(value, daemonId) {
@@ -11278,16 +11665,16 @@ function createStore(daemonId, options) {
11278
11665
  return {
11279
11666
  root,
11280
11667
  daemonId,
11281
- filePath: import_node_path33.default.join(root, `${key}.json`),
11282
- lockPath: import_node_path33.default.join(root, `${key}.lock`),
11668
+ filePath: import_node_path35.default.join(root, `${key}.json`),
11669
+ lockPath: import_node_path35.default.join(root, `${key}.lock`),
11283
11670
  writeAtomic: options.writeAtomic ?? writeFileAtomically
11284
11671
  };
11285
11672
  }
11286
11673
  function validateRoot(root) {
11287
- if (typeof root !== "string" || !import_node_path33.default.isAbsolute(root) || root.includes("\0")) {
11674
+ if (typeof root !== "string" || !import_node_path35.default.isAbsolute(root) || root.includes("\0")) {
11288
11675
  throw claimError("invalid-input", "network claim root is invalid");
11289
11676
  }
11290
- const resolved = import_node_path33.default.resolve(root);
11677
+ const resolved = import_node_path35.default.resolve(root);
11291
11678
  if (resolved !== root) throw claimError("invalid-input", "network claim root is invalid");
11292
11679
  return resolved;
11293
11680
  }
@@ -11315,20 +11702,20 @@ function isErrno2(error, code) {
11315
11702
  function claimError(code, message) {
11316
11703
  return new NetworkClaimError(code, message);
11317
11704
  }
11318
- 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;
11705
+ var import_node_crypto13, import_node_fs27, import_node_path35, 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;
11319
11706
  var init_network_claims = __esm({
11320
11707
  "src/core/network-claims.ts"() {
11321
11708
  "use strict";
11322
11709
  import_node_crypto13 = require("crypto");
11323
- import_node_fs25 = __toESM(require("fs"));
11324
- import_node_path33 = __toESM(require("path"));
11710
+ import_node_fs27 = __toESM(require("fs"));
11711
+ import_node_path35 = __toESM(require("path"));
11325
11712
  init_atomic_file();
11326
11713
  init_file_lock();
11327
11714
  init_network_capacity();
11328
11715
  init_network_policy();
11329
11716
  init_router();
11330
11717
  NETWORK_CLAIMS_VERSION = 1;
11331
- NETWORK_CLAIMS_ROOT = import_node_path33.default.join(DEVROUTER_HOME, "networks");
11718
+ NETWORK_CLAIMS_ROOT = import_node_path35.default.join(DEVROUTER_HOME, "networks");
11332
11719
  NETWORK_CLAIMS_MAX_RECORDS = 256;
11333
11720
  NETWORK_CLAIMS_MAX_FILE_BYTES = 512 * 1024;
11334
11721
  NetworkClaimError = class extends Error {
@@ -11370,14 +11757,14 @@ var init_network_claims = __esm({
11370
11757
 
11371
11758
  // src/core/network-claim-lookup.ts
11372
11759
  function networkOwnerKey(repoPath) {
11373
- if (!import_node_path34.default.isAbsolute(repoPath) || import_node_path34.default.resolve(repoPath) !== repoPath)
11760
+ if (!import_node_path36.default.isAbsolute(repoPath) || import_node_path36.default.resolve(repoPath) !== repoPath)
11374
11761
  throw new Error("Invalid network owner path.");
11375
11762
  return (0, import_node_crypto14.createHash)("sha256").update(repoPath).digest("hex");
11376
11763
  }
11377
11764
  function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
11378
11765
  let names;
11379
11766
  try {
11380
- names = import_node_fs26.default.readdirSync(root);
11767
+ names = import_node_fs28.default.readdirSync(root);
11381
11768
  } catch (error) {
11382
11769
  if (error.code === "ENOENT") return void 0;
11383
11770
  throw new Error("Network claims cannot be inspected; repair is required.");
@@ -11386,21 +11773,21 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
11386
11773
  if (files.length > 128) throw new Error("Network daemon inventory exceeds its bound.");
11387
11774
  let found;
11388
11775
  for (const file of files) {
11389
- const full = import_node_path34.default.join(root, file);
11390
- const stat = import_node_fs26.default.lstatSync(full);
11776
+ const full = import_node_path36.default.join(root, file);
11777
+ const stat = import_node_fs28.default.lstatSync(full);
11391
11778
  if (!stat.isFile() || stat.size > NETWORK_CLAIMS_MAX_FILE_BYTES)
11392
11779
  throw new Error("Invalid retained network claim file.");
11393
11780
  let value;
11394
- const descriptor = import_node_fs26.default.openSync(
11781
+ const descriptor = import_node_fs28.default.openSync(
11395
11782
  full,
11396
- import_node_fs26.default.constants.O_RDONLY | import_node_fs26.default.constants.O_NONBLOCK | import_node_fs26.default.constants.O_NOFOLLOW
11783
+ import_node_fs28.default.constants.O_RDONLY | import_node_fs28.default.constants.O_NONBLOCK | import_node_fs28.default.constants.O_NOFOLLOW
11397
11784
  );
11398
11785
  try {
11399
- if (!import_node_fs26.default.fstatSync(descriptor).isFile()) throw new Error();
11786
+ if (!import_node_fs28.default.fstatSync(descriptor).isFile()) throw new Error();
11400
11787
  const bytes2 = Buffer.alloc(NETWORK_CLAIMS_MAX_FILE_BYTES + 1);
11401
11788
  let size = 0;
11402
11789
  while (size < bytes2.length) {
11403
- const count = import_node_fs26.default.readSync(descriptor, bytes2, size, bytes2.length - size, null);
11790
+ const count = import_node_fs28.default.readSync(descriptor, bytes2, size, bytes2.length - size, null);
11404
11791
  if (!count) break;
11405
11792
  size += count;
11406
11793
  }
@@ -11410,7 +11797,7 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
11410
11797
  } catch {
11411
11798
  throw new Error("Retained network claim could not be read safely.");
11412
11799
  } finally {
11413
- import_node_fs26.default.closeSync(descriptor);
11800
+ import_node_fs28.default.closeSync(descriptor);
11414
11801
  }
11415
11802
  if (typeof value.daemonId !== "string" || `${(0, import_node_crypto14.createHash)("sha256").update(value.daemonId).digest("hex")}.json` !== file)
11416
11803
  throw new Error("Retained network daemon identity is invalid.");
@@ -11426,13 +11813,13 @@ function findOwnedNetworkClaim(repoPath, root = NETWORK_CLAIMS_ROOT) {
11426
11813
  }
11427
11814
  return found;
11428
11815
  }
11429
- var import_node_crypto14, import_node_fs26, import_node_path34;
11816
+ var import_node_crypto14, import_node_fs28, import_node_path36;
11430
11817
  var init_network_claim_lookup = __esm({
11431
11818
  "src/core/network-claim-lookup.ts"() {
11432
11819
  "use strict";
11433
11820
  import_node_crypto14 = require("crypto");
11434
- import_node_fs26 = __toESM(require("fs"));
11435
- import_node_path34 = __toESM(require("path"));
11821
+ import_node_fs28 = __toESM(require("fs"));
11822
+ import_node_path36 = __toESM(require("path"));
11436
11823
  init_network_claims();
11437
11824
  }
11438
11825
  });
@@ -11460,7 +11847,7 @@ function qualifyNetworkProviderDefinition(value, provider) {
11460
11847
  return (0, import_node_crypto15.createHash)("sha256").update(JSON.stringify({ name: definition.name, agent, options, exec })).digest("hex");
11461
11848
  }
11462
11849
  function read(provider, args) {
11463
- const result = (0, import_node_child_process15.spawnSync)(provider, args, {
11850
+ const result = (0, import_node_child_process16.spawnSync)(provider, args, {
11464
11851
  encoding: "utf8",
11465
11852
  timeout: 5e3,
11466
11853
  maxBuffer: 2 * 1024 * 1024
@@ -11548,11 +11935,11 @@ function inspectNetworkProviderBinding(input2) {
11548
11935
  );
11549
11936
  }
11550
11937
  }
11551
- var import_node_child_process15, import_node_crypto15;
11938
+ var import_node_child_process16, import_node_crypto15;
11552
11939
  var init_network_provider_inspect = __esm({
11553
11940
  "src/core/network-provider-inspect.ts"() {
11554
11941
  "use strict";
11555
- import_node_child_process15 = require("child_process");
11942
+ import_node_child_process16 = require("child_process");
11556
11943
  import_node_crypto15 = require("crypto");
11557
11944
  init_devpod_environment();
11558
11945
  }
@@ -11591,7 +11978,7 @@ var init_network_provider_effect = __esm({
11591
11978
 
11592
11979
  // src/core/devsy-mutation.ts
11593
11980
  function withMutationLock(activity, target, operation) {
11594
- import_node_fs27.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
11981
+ import_node_fs29.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
11595
11982
  return withFileLockSync(
11596
11983
  DEVSY_MUTATION_LOCK_FILE,
11597
11984
  {
@@ -11611,7 +11998,7 @@ function runDevsyAction(action2, devsyId, force = false, repoPath) {
11611
11998
  const args = action2 === "delete" ? ["delete", devsyId, ...force ? ["--force"] : [], "--ignore-not-found"] : ["stop", devsyId];
11612
11999
  claimLifecycleEffect();
11613
12000
  const binding = repoPath ? networkProviderEffectOptions("devsy", devsyId, repoPath) : { args: [] };
11614
- const result = (0, import_node_child_process16.spawnSync)("devsy", ["workspace", ...args, ...binding.args], {
12001
+ const result = (0, import_node_child_process17.spawnSync)("devsy", ["workspace", ...args, ...binding.args], {
11615
12002
  encoding: "utf-8",
11616
12003
  ...binding.env ? { env: binding.env } : {}
11617
12004
  });
@@ -11672,13 +12059,13 @@ function stopOwnedDevsyWorkspace(devsyId, worktreePath) {
11672
12059
  function deleteOwnedDevsyWorkspace(devsyId, worktreePath) {
11673
12060
  return mutateOwnedDevsyWorkspace("delete", devsyId, worktreePath);
11674
12061
  }
11675
- var import_node_child_process16, import_node_fs27, import_node_path35, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
12062
+ var import_node_child_process17, import_node_fs29, import_node_path37, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS;
11676
12063
  var init_devsy_mutation = __esm({
11677
12064
  "src/core/devsy-mutation.ts"() {
11678
12065
  "use strict";
11679
- import_node_child_process16 = require("child_process");
11680
- import_node_fs27 = __toESM(require("fs"));
11681
- import_node_path35 = __toESM(require("path"));
12066
+ import_node_child_process17 = require("child_process");
12067
+ import_node_fs29 = __toESM(require("fs"));
12068
+ import_node_path37 = __toESM(require("path"));
11682
12069
  init_devsy_agent();
11683
12070
  init_devsy_workspaces();
11684
12071
  init_file_lock();
@@ -11687,7 +12074,7 @@ var init_devsy_mutation = __esm({
11687
12074
  init_network_provider_effect();
11688
12075
  init_reliability_context();
11689
12076
  init_router();
11690
- DEVSY_MUTATION_LOCK_FILE = import_node_path35.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
12077
+ DEVSY_MUTATION_LOCK_FILE = import_node_path37.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
11691
12078
  DEVSY_MUTATION_WAIT_MS = 18e5;
11692
12079
  }
11693
12080
  });
@@ -12459,10 +12846,10 @@ async function runLifecycleWorker(request2, supervision, beforeAdmission) {
12459
12846
  throw new Error("Lifecycle invocation was cancelled before dispatch.");
12460
12847
  if (process.platform === "win32")
12461
12848
  throw new Error("Lifecycle workers require POSIX process-group ownership.");
12462
- const workerPath = import_node_path36.default.join(__dirname, "devrouter-lifecycle-worker.js");
12463
- if (!import_node_fs28.default.existsSync(workerPath))
12849
+ const workerPath = import_node_path38.default.join(__dirname, "devrouter-lifecycle-worker.js");
12850
+ if (!import_node_fs30.default.existsSync(workerPath))
12464
12851
  throw new Error("The packaged lifecycle worker is missing; rebuild the CLI.");
12465
- const child = (0, import_node_child_process17.fork)(workerPath, [], {
12852
+ const child = (0, import_node_child_process18.fork)(workerPath, [], {
12466
12853
  detached: true,
12467
12854
  stdio: supervision ? ["ignore", "pipe", "pipe", "ipc"] : ["inherit", "inherit", "inherit", "ipc"],
12468
12855
  execArgv: []
@@ -12689,14 +13076,14 @@ async function runLifecycleWorker(request2, supervision, beforeAdmission) {
12689
13076
  function newLifecycleIds() {
12690
13077
  return { requestId: (0, import_node_crypto16.randomUUID)(), operationId: (0, import_node_crypto16.randomUUID)(), workerId: (0, import_node_crypto16.randomUUID)() };
12691
13078
  }
12692
- var import_node_child_process17, import_node_crypto16, import_node_fs28, import_node_path36, OUTPUT_BUFFER_LIMIT, DEFAULT_OUTPUT_PAGE_BYTES, LifecycleOutput, LifecycleWorkerAdmissionBusyError;
13079
+ var import_node_child_process18, import_node_crypto16, import_node_fs30, import_node_path38, OUTPUT_BUFFER_LIMIT, DEFAULT_OUTPUT_PAGE_BYTES, LifecycleOutput, LifecycleWorkerAdmissionBusyError;
12693
13080
  var init_reliability_worker = __esm({
12694
13081
  "src/core/reliability-worker.ts"() {
12695
13082
  "use strict";
12696
- import_node_child_process17 = require("child_process");
12697
- import_node_crypto16 = require("crypto");
12698
- import_node_fs28 = __toESM(require("fs"));
12699
- import_node_path36 = __toESM(require("path"));
13083
+ import_node_child_process18 = require("child_process");
13084
+ import_node_crypto16 = require("crypto");
13085
+ import_node_fs30 = __toESM(require("fs"));
13086
+ import_node_path38 = __toESM(require("path"));
12700
13087
  init_file_lock();
12701
13088
  init_reliability_contract();
12702
13089
  init_reliability_model();
@@ -12811,173 +13198,9 @@ var init_reliability_worker = __esm({
12811
13198
  }
12812
13199
  });
12813
13200
 
12814
- // src/core/traefik-route-health.ts
12815
- function sleep(ms) {
12816
- return new Promise((resolve) => setTimeout(resolve, ms));
12817
- }
12818
- function inspectRouterApi(protocol) {
12819
- const result = (0, import_node_child_process18.spawnSync)(
12820
- "curl",
12821
- [
12822
- "--silent",
12823
- "--show-error",
12824
- "--fail",
12825
- "--max-time",
12826
- ROUTER_API_REQUEST_TIMEOUT_SECONDS,
12827
- `${ROUTER_API_BASE}/${protocol}/routers?per_page=${ROUTER_API_PAGE_SIZE}`
12828
- ],
12829
- { encoding: "utf-8" }
12830
- );
12831
- if (result.status !== 0) {
12832
- return {
12833
- ok: false,
12834
- details: result.stderr?.trim() || `curl exited with status ${result.status ?? "unknown"}`
12835
- };
12836
- }
12837
- let value;
12838
- try {
12839
- value = JSON.parse(result.stdout);
12840
- } catch {
12841
- return { ok: false, details: `${protocol.toUpperCase()} router API returned invalid JSON` };
12842
- }
12843
- if (!Array.isArray(value)) {
12844
- return { ok: false, details: `${protocol.toUpperCase()} router API returned a non-array` };
12845
- }
12846
- if (value.some((item) => !isRecord5(item) || typeof item.name !== "string" || !item.name))
12847
- return {
12848
- ok: false,
12849
- details: `${protocol.toUpperCase()} router API returned malformed entries`
12850
- };
12851
- return {
12852
- ok: true,
12853
- reachedPageLimit: value.length >= ROUTER_API_PAGE_SIZE,
12854
- names: new Set(
12855
- value.flatMap((item) => {
12856
- if (!item || typeof item !== "object") return [];
12857
- const name = item.name;
12858
- return typeof name === "string" ? [name] : [];
12859
- })
12860
- )
12861
- };
12862
- }
12863
- function isRecord5(value) {
12864
- return value !== null && typeof value === "object" && !Array.isArray(value);
12865
- }
12866
- function inspectExpectedRoutes(routes, expectation) {
12867
- const mismatched = [];
12868
- const failures = [];
12869
- for (const protocol of ["http", "tcp"]) {
12870
- const expected = routes.filter((route) => (route.protocol ?? "http") === protocol).map((route) => buildHostRouteRouterName(route.repoPath, route.name));
12871
- if (expected.length === 0) continue;
12872
- const result = inspectRouterApi(protocol);
12873
- if (!result.ok) {
12874
- failures.push(result.details);
12875
- mismatched.push(...expected);
12876
- continue;
12877
- }
12878
- const unexpected = expected.filter(
12879
- (name) => expectation === "loaded" ? !result.names.has(name) : result.names.has(name)
12880
- );
12881
- const boundedAbsenceIsUncertain = result.reachedPageLimit && (expectation === "loaded" ? unexpected.length > 0 : unexpected.length === 0);
12882
- if (boundedAbsenceIsUncertain) {
12883
- failures.push(
12884
- `${protocol.toUpperCase()} router API reached the ${ROUTER_API_PAGE_SIZE}-entry safety limit`
12885
- );
12886
- }
12887
- mismatched.push(...unexpected);
12888
- if (expectation === "removed" && unexpected.length === 0 && result.reachedPageLimit) {
12889
- mismatched.push(...expected);
12890
- }
12891
- }
12892
- return mismatched.length === 0 ? { ok: true } : {
12893
- ok: false,
12894
- routes: mismatched,
12895
- details: failures.length > 0 ? failures.join("; ") : expectation === "loaded" ? "router names are absent" : "router names are still present"
12896
- };
12897
- }
12898
- async function waitForExpectedRoutes(routes, expectation, timeoutMs, pollIntervalMs) {
12899
- const deadline = Date.now() + timeoutMs;
12900
- let result;
12901
- do {
12902
- result = inspectExpectedRoutes(routes, expectation);
12903
- if (result.ok) return result;
12904
- if (Date.now() < deadline) {
12905
- await sleep(Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
12906
- }
12907
- } while (Date.now() < deadline);
12908
- return result;
12909
- }
12910
- async function ensureTraefikRouteExpectation(routes, expectation, options) {
12911
- if (routes.length === 0) return { restarted: false };
12912
- const initialTimeoutMs = options.initialTimeoutMs ?? DEFAULT_INITIAL_TIMEOUT_MS;
12913
- const recoveryTimeoutMs = options.recoveryTimeoutMs ?? DEFAULT_RECOVERY_TIMEOUT_MS;
12914
- const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
12915
- const initial = await waitForExpectedRoutes(
12916
- routes,
12917
- expectation,
12918
- initialTimeoutMs,
12919
- pollIntervalMs
12920
- );
12921
- if (initial.ok) return { restarted: false };
12922
- if (options.allowRestart === false)
12923
- throw new Error(
12924
- "Traefik routes did not reload; shared router restart is disabled for this operation."
12925
- );
12926
- import_node_fs29.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12927
- return withFileLock(
12928
- ROUTER_RELOAD_LOCK_FILE,
12929
- {
12930
- activity: "Traefik route reload recovery",
12931
- waitMs: initialTimeoutMs + recoveryTimeoutMs + 1e4,
12932
- fair: true,
12933
- onWait: createStderrWaitReporter("Traefik route reload recovery", "shared router")
12934
- },
12935
- async () => {
12936
- const recheck = await waitForExpectedRoutes(routes, expectation, 0, pollIntervalMs);
12937
- if (recheck.ok) return { restarted: false };
12938
- claimLifecycleEffect();
12939
- restartRouterStack();
12940
- const recovered = await waitForExpectedRoutes(
12941
- routes,
12942
- expectation,
12943
- recoveryTimeoutMs,
12944
- pollIntervalMs
12945
- );
12946
- if (recovered.ok) return { restarted: true };
12947
- const action2 = expectation === "loaded" ? "load" : "remove";
12948
- throw new Error(
12949
- `Traefik did not ${action2} file-provider routes after one restart: ${recovered.routes.join(", ")} (${recovered.details}). Inspect: devrouter logs --tail 100`
12950
- );
12951
- }
12952
- );
12953
- }
12954
- async function ensureTraefikRoutesRemoved(routes, options = {}) {
12955
- return ensureTraefikRouteExpectation(routes, "removed", options);
12956
- }
12957
- 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;
12958
- var init_traefik_route_health = __esm({
12959
- "src/core/traefik-route-health.ts"() {
12960
- "use strict";
12961
- import_node_child_process18 = require("child_process");
12962
- import_node_fs29 = __toESM(require("fs"));
12963
- import_node_path37 = __toESM(require("path"));
12964
- init_file_lock();
12965
- init_host_routes();
12966
- init_reliability_context();
12967
- init_router();
12968
- ROUTER_API_BASE = "http://127.0.0.1:8080/api";
12969
- ROUTER_RELOAD_LOCK_FILE = import_node_path37.default.join(DEVROUTER_HOME, "router-reload.lock");
12970
- ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
12971
- DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
12972
- DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
12973
- DEFAULT_POLL_INTERVAL_MS = 250;
12974
- ROUTER_API_PAGE_SIZE = 1e3;
12975
- }
12976
- });
12977
-
12978
13201
  // src/core/devpod-mutation.ts
12979
13202
  function withMutationLock2(activity, target, operation) {
12980
- import_node_fs30.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
13203
+ import_node_fs31.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12981
13204
  return withFileLockSync(
12982
13205
  DEVPOD_MUTATION_LOCK_FILE,
12983
13206
  {
@@ -13067,13 +13290,13 @@ function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
13067
13290
  resetWorkspaceRuntimeCaches();
13068
13291
  return result;
13069
13292
  }
13070
- var import_node_child_process19, import_node_fs30, import_node_path38, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
13293
+ var import_node_child_process19, import_node_fs31, import_node_path39, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS;
13071
13294
  var init_devpod_mutation = __esm({
13072
13295
  "src/core/devpod-mutation.ts"() {
13073
13296
  "use strict";
13074
13297
  import_node_child_process19 = require("child_process");
13075
- import_node_fs30 = __toESM(require("fs"));
13076
- import_node_path38 = __toESM(require("path"));
13298
+ import_node_fs31 = __toESM(require("fs"));
13299
+ import_node_path39 = __toESM(require("path"));
13077
13300
  init_devpod_workspaces();
13078
13301
  init_devsy_mutation();
13079
13302
  init_file_lock();
@@ -13082,7 +13305,7 @@ var init_devpod_mutation = __esm({
13082
13305
  init_reliability_context();
13083
13306
  init_router();
13084
13307
  init_workspace_runtime();
13085
- DEVPOD_MUTATION_LOCK_FILE = import_node_path38.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
13308
+ DEVPOD_MUTATION_LOCK_FILE = import_node_path39.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
13086
13309
  DEVPOD_MUTATION_WAIT_MS = 18e5;
13087
13310
  }
13088
13311
  });
@@ -13288,8 +13511,8 @@ function renderManagedComposeModel(plan, workspace) {
13288
13511
  }
13289
13512
  }
13290
13513
  function attributeHolderWorktree(workingDir) {
13291
- if (import_node_path39.default.basename(workingDir) !== ".devcontainer") return void 0;
13292
- return comparableWorkspacePath(import_node_path39.default.dirname(workingDir));
13514
+ if (import_node_path40.default.basename(workingDir) !== ".devcontainer") return void 0;
13515
+ return comparableWorkspacePath(import_node_path40.default.dirname(workingDir));
13293
13516
  }
13294
13517
  function holderAttribution(holder, repoPath) {
13295
13518
  const attribution = {};
@@ -13321,7 +13544,7 @@ function detectHostPortClaimConflicts(options) {
13321
13544
  );
13322
13545
  if (desired.length === 0) return [];
13323
13546
  const holders = listHolderSnapshots();
13324
- const devcontainerDir = import_node_path39.default.join(options.repoPath, ".devcontainer");
13547
+ const devcontainerDir = import_node_path40.default.join(options.repoPath, ".devcontainer");
13325
13548
  const conflicts = [];
13326
13549
  for (const binding of desired) {
13327
13550
  for (const holder of holders) {
@@ -13343,12 +13566,12 @@ function detectHostPortClaimConflicts(options) {
13343
13566
  }
13344
13567
  return conflicts;
13345
13568
  }
13346
- 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;
13569
+ var import_node_child_process20, import_node_path40, 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;
13347
13570
  var init_host_port_claims = __esm({
13348
13571
  "src/core/host-port-claims.ts"() {
13349
13572
  "use strict";
13350
13573
  import_node_child_process20 = require("child_process");
13351
- import_node_path39 = __toESM(require("path"));
13574
+ import_node_path40 = __toESM(require("path"));
13352
13575
  init_devcontainer_profile();
13353
13576
  init_network_effect_scope();
13354
13577
  init_workspace();
@@ -13522,20 +13745,20 @@ var init_http_route_probe = __esm({
13522
13745
  });
13523
13746
 
13524
13747
  // src/core/managed-host-preparation.ts
13525
- var import_node_child_process22, import_node_fs31;
13748
+ var import_node_child_process22, import_node_fs32;
13526
13749
  var init_managed_host_preparation = __esm({
13527
13750
  "src/core/managed-host-preparation.ts"() {
13528
13751
  "use strict";
13529
13752
  import_node_child_process22 = require("child_process");
13530
- import_node_fs31 = __toESM(require("fs"));
13753
+ import_node_fs32 = __toESM(require("fs"));
13531
13754
  init_repo_config();
13532
13755
  }
13533
13756
  });
13534
13757
 
13535
13758
  // src/core/managed-post-start.ts
13536
13759
  function readRegularFileBytes(filePath) {
13537
- if (!import_node_fs32.default.existsSync(filePath) || !import_node_fs32.default.lstatSync(filePath).isFile()) return void 0;
13538
- return import_node_fs32.default.readFileSync(filePath);
13760
+ if (!import_node_fs33.default.existsSync(filePath) || !import_node_fs33.default.lstatSync(filePath).isFile()) return void 0;
13761
+ return import_node_fs33.default.readFileSync(filePath);
13539
13762
  }
13540
13763
  function readRegularFile(filePath) {
13541
13764
  return readRegularFileBytes(filePath)?.toString("utf-8");
@@ -13544,11 +13767,11 @@ function adapterFingerprint(adapter) {
13544
13767
  return (0, import_node_crypto17.createHash)("sha256").update(adapter).digest("hex");
13545
13768
  }
13546
13769
  function resolveManagedPostStartPlan(repoPath) {
13547
- const adapterPath = import_node_path40.default.join(repoPath, MANAGED_ADAPTER_PATH);
13770
+ const adapterPath = import_node_path41.default.join(repoPath, MANAGED_ADAPTER_PATH);
13548
13771
  const adapterBytes = readRegularFileBytes(adapterPath);
13549
13772
  const adapterText = adapterBytes?.toString("utf-8");
13550
- const dockerfilePath = import_node_path40.default.join(repoPath, ".devcontainer", "Dockerfile");
13551
- const devcontainerPath = import_node_path40.default.join(repoPath, ".devcontainer", "devcontainer.json");
13773
+ const dockerfilePath = import_node_path41.default.join(repoPath, ".devcontainer", "Dockerfile");
13774
+ const devcontainerPath = import_node_path41.default.join(repoPath, ".devcontainer", "devcontainer.json");
13552
13775
  const dockerfile = readRegularFile(dockerfilePath) ?? "";
13553
13776
  const devcontainer = readRegularFile(devcontainerPath) ?? "";
13554
13777
  const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
@@ -13631,14 +13854,14 @@ function runManagedProcessAction(options) {
13631
13854
  }
13632
13855
  return "drifted";
13633
13856
  }
13634
- var import_node_child_process23, import_node_crypto17, import_node_fs32, import_node_path40, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
13857
+ var import_node_child_process23, import_node_crypto17, import_node_fs33, import_node_path41, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH;
13635
13858
  var init_managed_post_start = __esm({
13636
13859
  "src/core/managed-post-start.ts"() {
13637
13860
  "use strict";
13638
13861
  import_node_child_process23 = require("child_process");
13639
13862
  import_node_crypto17 = require("crypto");
13640
- import_node_fs32 = __toESM(require("fs"));
13641
- import_node_path40 = __toESM(require("path"));
13863
+ import_node_fs33 = __toESM(require("fs"));
13864
+ import_node_path41 = __toESM(require("path"));
13642
13865
  init_devcontainer_config();
13643
13866
  init_network_effect_scope();
13644
13867
  MANAGED_MARKER = "devrouter:managed devcontainer";
@@ -14577,26 +14800,26 @@ function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
14577
14800
  try {
14578
14801
  if (!endpoint.startsWith("unix:///")) return false;
14579
14802
  const socket = endpoint.slice("unix://".length);
14580
- const table = import_node_fs33.default.readFileSync(import_node_path41.default.join(procRoot, "net/unix"), "utf8");
14803
+ const table = import_node_fs34.default.readFileSync(import_node_path42.default.join(procRoot, "net/unix"), "utf8");
14581
14804
  if (Buffer.byteLength(table) > 1024 * 1024) return false;
14582
14805
  const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
14583
14806
  if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
14584
14807
  const inode = `socket:[${rows2[0][6]}]`;
14585
- const ownNamespace = import_node_fs33.default.readlinkSync(import_node_path41.default.join(procRoot, "self/ns/net"));
14586
- const pids = import_node_fs33.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
14808
+ const ownNamespace = import_node_fs34.default.readlinkSync(import_node_path42.default.join(procRoot, "self/ns/net"));
14809
+ const pids = import_node_fs34.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
14587
14810
  if (pids.length > 4096) return false;
14588
14811
  for (const pid of pids) {
14589
14812
  if (Date.now() > deadline) return false;
14590
14813
  try {
14591
- const root = import_node_path41.default.join(procRoot, pid);
14592
- if (import_node_path41.default.basename(import_node_fs33.default.readlinkSync(import_node_path41.default.join(root, "exe"))) !== "dockerd") continue;
14593
- if (import_node_fs33.default.readlinkSync(import_node_path41.default.join(root, "ns/net")) !== ownNamespace) continue;
14594
- const descriptors = import_node_fs33.default.readdirSync(import_node_path41.default.join(root, "fd"));
14814
+ const root = import_node_path42.default.join(procRoot, pid);
14815
+ if (import_node_path42.default.basename(import_node_fs34.default.readlinkSync(import_node_path42.default.join(root, "exe"))) !== "dockerd") continue;
14816
+ if (import_node_fs34.default.readlinkSync(import_node_path42.default.join(root, "ns/net")) !== ownNamespace) continue;
14817
+ const descriptors = import_node_fs34.default.readdirSync(import_node_path42.default.join(root, "fd"));
14595
14818
  if (descriptors.length > 4096) continue;
14596
14819
  if (descriptors.some((fd) => {
14597
14820
  if (Date.now() > deadline) return false;
14598
14821
  try {
14599
- return import_node_fs33.default.readlinkSync(import_node_path41.default.join(root, "fd", fd)) === inode;
14822
+ return import_node_fs34.default.readlinkSync(import_node_path42.default.join(root, "fd", fd)) === inode;
14600
14823
  } catch {
14601
14824
  return false;
14602
14825
  }
@@ -14610,12 +14833,12 @@ function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
14610
14833
  }
14611
14834
  return false;
14612
14835
  }
14613
- var import_node_fs33, import_node_path41;
14836
+ var import_node_fs34, import_node_path42;
14614
14837
  var init_network_local_daemon = __esm({
14615
14838
  "src/core/network-local-daemon.ts"() {
14616
14839
  "use strict";
14617
- import_node_fs33 = __toESM(require("fs"));
14618
- import_node_path41 = __toESM(require("path"));
14840
+ import_node_fs34 = __toESM(require("fs"));
14841
+ import_node_path42 = __toESM(require("path"));
14619
14842
  }
14620
14843
  });
14621
14844
 
@@ -14731,14 +14954,14 @@ var init_network_compose = __esm({
14731
14954
  });
14732
14955
 
14733
14956
  // src/core/network-compose-files.ts
14734
- var import_node_child_process26, import_node_crypto19, import_node_fs34, import_node_path42, import_yaml4, MAX_BYTES3;
14957
+ var import_node_child_process26, import_node_crypto19, import_node_fs35, import_node_path43, import_yaml4, MAX_BYTES3;
14735
14958
  var init_network_compose_files = __esm({
14736
14959
  "src/core/network-compose-files.ts"() {
14737
14960
  "use strict";
14738
14961
  import_node_child_process26 = require("child_process");
14739
14962
  import_node_crypto19 = require("crypto");
14740
- import_node_fs34 = __toESM(require("fs"));
14741
- import_node_path42 = __toESM(require("path"));
14963
+ import_node_fs35 = __toESM(require("fs"));
14964
+ import_node_path43 = __toESM(require("path"));
14742
14965
  import_yaml4 = __toESM(require("yaml"));
14743
14966
  init_atomic_file();
14744
14967
  init_devcontainer_profile();
@@ -14842,13 +15065,13 @@ function resolveLinkedTarget(repoPath) {
14842
15065
  gitCommonDir: resolveGitCommonDir(repoPath)
14843
15066
  };
14844
15067
  }
14845
- var import_node_child_process28, import_node_fs35, import_node_path43;
15068
+ var import_node_child_process28, import_node_fs36, import_node_path44;
14846
15069
  var init_workspace_ensure = __esm({
14847
15070
  "src/core/workspace-ensure.ts"() {
14848
15071
  "use strict";
14849
15072
  import_node_child_process28 = require("child_process");
14850
- import_node_fs35 = __toESM(require("fs"));
14851
- import_node_path43 = __toESM(require("path"));
15073
+ import_node_fs36 = __toESM(require("fs"));
15074
+ import_node_path44 = __toESM(require("path"));
14852
15075
  init_devcontainer_profile();
14853
15076
  init_devpod_environment();
14854
15077
  init_devpod_mutation();
@@ -14984,7 +15207,7 @@ function prepareManagedLifecycleOperation(input2) {
14984
15207
  };
14985
15208
  });
14986
15209
  }
14987
- function assertCurrentCapacityController(expected, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller")) {
15210
+ function assertCurrentCapacityController(expected, directory = import_node_path45.default.join(DEVROUTER_HOME, "controller")) {
14988
15211
  const current = new ControllerStore(directory).read();
14989
15212
  if (!expected || current?.store !== expected.store || current.epoch !== expected.epoch)
14990
15213
  throw new Error("Capacity controller incarnation changed.");
@@ -15036,7 +15259,7 @@ function prepareRecoveryLifecycleOperation(input2) {
15036
15259
  });
15037
15260
  }
15038
15261
  function settlePreparedLifecycleCapacity(input2) {
15039
- const directory = input2.directory ?? import_node_path44.default.join(DEVROUTER_HOME, "controller");
15262
+ const directory = input2.directory ?? import_node_path45.default.join(DEVROUTER_HOME, "controller");
15040
15263
  const store = new CapacityStore(directory);
15041
15264
  for (let attempt = 0; attempt < 3; attempt++) {
15042
15265
  const snapshot = store.read();
@@ -15250,7 +15473,7 @@ async function superviseLifecycle(kind, repoPath, options = {}, command) {
15250
15473
  removeSignals();
15251
15474
  }
15252
15475
  }
15253
- function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs, maxSampleAgeMs, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller"), controller, admission) {
15476
+ function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs, maxSampleAgeMs, directory = import_node_path45.default.join(DEVROUTER_HOME, "controller"), controller, admission) {
15254
15477
  if (request2.kind === "stop") throw new Error("Stop never requires capacity admission.");
15255
15478
  const capacity = new CapacityStore(directory);
15256
15479
  const snapshot = capacity.read();
@@ -15350,7 +15573,7 @@ function admitLifecycleCapacity(request2, reservation, budgets, samples, nowMs,
15350
15573
  return decision;
15351
15574
  }
15352
15575
  function capacityPolicyTargets(repoPath) {
15353
- const policy = readCapacityPolicy(import_node_path44.default.join(DEVROUTER_HOME, "controller"));
15576
+ const policy = readCapacityPolicy(import_node_path45.default.join(DEVROUTER_HOME, "controller"));
15354
15577
  return policy?.admissions === "enabled" && policy.enrollments.some((entry) => entry.repoPath === repoPath);
15355
15578
  }
15356
15579
  function controllerWaitSeconds(deadline) {
@@ -15359,7 +15582,7 @@ function controllerWaitSeconds(deadline) {
15359
15582
  async function superviseThroughController(input2) {
15360
15583
  if (input2.kind === "exec" && !input2.command)
15361
15584
  throw new Error("Exec requires a command for controller admission.");
15362
- const directory = import_node_path44.default.join(DEVROUTER_HOME, "controller");
15585
+ const directory = import_node_path45.default.join(DEVROUTER_HOME, "controller");
15363
15586
  const cancellation = { requested: false };
15364
15587
  const removeSignals = installLifecycleWaitSignals(cancellation);
15365
15588
  const cancelled = () => {
@@ -15431,7 +15654,7 @@ async function superviseThroughController(input2) {
15431
15654
  removeSignals();
15432
15655
  }
15433
15656
  }
15434
- function renewLifecycleCapacity(request2, policy, samples, controller, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller"), nowMs = Date.now()) {
15657
+ function renewLifecycleCapacity(request2, policy, samples, controller, directory = import_node_path45.default.join(DEVROUTER_HOME, "controller"), nowMs = Date.now()) {
15435
15658
  return updateReliabilityOperation(request2.identity, (record5) => {
15436
15659
  const binding = record5.capacity;
15437
15660
  if (!matchesFence(record5, request2.fence) || !binding || binding.operationId !== request2.operationId || binding.workerId !== request2.workerId || binding.controller?.store !== controller.store || binding.controller?.epoch !== controller.epoch)
@@ -15519,7 +15742,7 @@ function retireQueuedLifecycle(request2, supersededOnly = false) {
15519
15742
  return true;
15520
15743
  });
15521
15744
  }
15522
- function bindLifecycleCapacity(request2, binding, directory = import_node_path44.default.join(DEVROUTER_HOME, "controller")) {
15745
+ function bindLifecycleCapacity(request2, binding, directory = import_node_path45.default.join(DEVROUTER_HOME, "controller")) {
15523
15746
  if (request2.kind === "stop") throw new Error("Stop never requires capacity admission.");
15524
15747
  updateReliabilityOperation(request2.identity, (record5) => {
15525
15748
  if (!matchesFence(record5, request2.fence) || record5.worker || record5.state.operation?.id !== request2.operationId || record5.state.operation.status !== "NOT_STARTED" || record5.state.operation.drained)
@@ -15534,12 +15757,12 @@ function bindLifecycleCapacity(request2, binding, directory = import_node_path44
15534
15757
  stepRecord(record5, { ...request2.fence, type: "admission", result: "admitted" });
15535
15758
  });
15536
15759
  }
15537
- 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;
15760
+ var import_node_crypto21, import_node_path45, import_node_util10, activeWorker, lockHeld, BUSY_LIFECYCLE_WAIT_MS, BUSY_LIFECYCLE_POLL_MS, CAPACITY_CLIENT_WAIT_MS, BUSY_LIFECYCLE_PROGRESS_MS;
15538
15761
  var init_reliability_lifecycle = __esm({
15539
15762
  "src/core/reliability-lifecycle.ts"() {
15540
15763
  "use strict";
15541
15764
  import_node_crypto21 = require("crypto");
15542
- import_node_path44 = __toESM(require("path"));
15765
+ import_node_path45 = __toESM(require("path"));
15543
15766
  import_node_util10 = require("util");
15544
15767
  init_capacity_accounting();
15545
15768
  init_capacity_policy();
@@ -15552,6 +15775,7 @@ var init_reliability_lifecycle = __esm({
15552
15775
  init_devsy_mutation();
15553
15776
  init_file_lock();
15554
15777
  init_host_routes();
15778
+ init_managed_devsy_stop();
15555
15779
  init_managed_runtime_state();
15556
15780
  init_managed_stop_recovery();
15557
15781
  init_reliability_context();
@@ -16031,7 +16255,7 @@ function createCapacityController(options) {
16031
16255
  const record5 = readReliabilityOperation(identity2);
16032
16256
  if (!record5?.capacity || !record5.preparation && !record5.capacity.execSteady || record5.worker || !record5.state.operation?.drained || !["ensure", "exec"].includes(record5.state.operation.kind))
16033
16257
  continue;
16034
- const bytes2 = readControllerEvidence(import_node_path45.default.join(enrollment.repoPath, ".devrouter.yml"));
16258
+ const bytes2 = readControllerEvidence(import_node_path46.default.join(enrollment.repoPath, ".devrouter.yml"));
16035
16259
  const estimates = loadRepoConfig(enrollment.repoPath, () => bytes2).capacity;
16036
16260
  if (!estimates) continue;
16037
16261
  settlePreparedLifecycleCapacity({
@@ -16272,12 +16496,12 @@ function createCapacityController(options) {
16272
16496
  }
16273
16497
  };
16274
16498
  }
16275
- var import_node_crypto22, import_node_path45, import_node_util12;
16499
+ var import_node_crypto22, import_node_path46, import_node_util12;
16276
16500
  var init_capacity_controller = __esm({
16277
16501
  "src/core/capacity-controller.ts"() {
16278
16502
  "use strict";
16279
16503
  import_node_crypto22 = require("crypto");
16280
- import_node_path45 = __toESM(require("path"));
16504
+ import_node_path46 = __toESM(require("path"));
16281
16505
  import_node_util12 = require("util");
16282
16506
  init_capacity_docker_probe();
16283
16507
  init_capacity_enrollment();
@@ -16637,12 +16861,12 @@ async function containers(project, signal) {
16637
16861
  }
16638
16862
  return values.sort((a, b) => a.id.localeCompare(b.id));
16639
16863
  }
16640
- var import_node_crypto24, import_node_path46, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
16864
+ var import_node_crypto24, import_node_path47, OBSERVATION_INSPECT_TEMPLATE, collectControllerObservation;
16641
16865
  var init_controller_observation = __esm({
16642
16866
  "src/core/controller-observation.ts"() {
16643
16867
  "use strict";
16644
16868
  import_node_crypto24 = require("crypto");
16645
- import_node_path46 = __toESM(require("path"));
16869
+ import_node_path47 = __toESM(require("path"));
16646
16870
  init_controller_binding();
16647
16871
  init_controller_monitor();
16648
16872
  init_controller_probe();
@@ -16685,14 +16909,14 @@ var init_controller_observation = __esm({
16685
16909
  )).trim().split("\n");
16686
16910
  if (metadata.length !== 2) throw new Error("Observation ownership paths unavailable.");
16687
16911
  const [gitDir, commonDir] = metadata;
16688
- const sourcePath = import_node_path46.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
16912
+ const sourcePath = import_node_path47.default.join(environment.repoPath, ".devcontainer/devcontainer.json");
16689
16913
  const files = [
16690
- import_node_path46.default.join(commonDir, "devrouter/workspaces", `${environment.workspace}.json`),
16691
- import_node_path46.default.join(environment.repoPath, ".devrouter.yml"),
16692
- import_node_path46.default.join(gitDir, "devrouter-workspace"),
16914
+ import_node_path47.default.join(commonDir, "devrouter/workspaces", `${environment.workspace}.json`),
16915
+ import_node_path47.default.join(environment.repoPath, ".devrouter.yml"),
16916
+ import_node_path47.default.join(gitDir, "devrouter-workspace"),
16693
16917
  managedRuntimeStatePath(environment.repoPath, environment.workspace),
16694
16918
  sourcePath,
16695
- import_node_path46.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
16919
+ import_node_path47.default.join(environment.repoPath, MANAGED_DEVCONTAINER_PATH)
16696
16920
  ];
16697
16921
  const persisted = captureControllerEvidence(files);
16698
16922
  if (controllerBindingFingerprint(persisted.contents[0], persisted.contents[1]) !== environment.fingerprint)
@@ -16717,7 +16941,7 @@ var init_controller_observation = __esm({
16717
16941
  if (!journal) throw new Error("Observation journal is unavailable.");
16718
16942
  const routes = readRoutes().filter((route) => route.repoPath === environment.repoPath);
16719
16943
  const before = await containers(state.composeProject, signal);
16720
- const composeDirectory = import_node_path46.default.join(environment.repoPath, ".devcontainer");
16944
+ const composeDirectory = import_node_path47.default.join(environment.repoPath, ".devcontainer");
16721
16945
  const references = typeof source.dockerComposeFile === "string" ? [source.dockerComposeFile] : source.dockerComposeFile;
16722
16946
  if (!Array.isArray(references) || !references.length || references.some((value) => typeof value !== "string"))
16723
16947
  throw new Error("Observation Compose source is unavailable.");
@@ -16734,7 +16958,7 @@ var init_controller_observation = __esm({
16734
16958
  if (!recorded || container.labels["com.docker.compose.project.working_dir"] !== composeDirectory)
16735
16959
  throw new Error("Observation Compose ownership is unavailable.");
16736
16960
  const files2 = recorded.split(",").map((file) => file.trim());
16737
- if (files2.some((file) => !import_node_path46.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
16961
+ if (files2.some((file) => !import_node_path47.default.isAbsolute(file)) || !hasExactComposeIdentity(container, {
16738
16962
  repoPath: environment.repoPath,
16739
16963
  service: container.labels["com.docker.compose.service"] ?? "",
16740
16964
  composeProject: state.composeProject,
@@ -17099,26 +17323,26 @@ var init_controller_sessions = __esm({
17099
17323
 
17100
17324
  // src/core/controller-server.ts
17101
17325
  function privateDirectory(directory) {
17102
- const absolute2 = import_node_path47.default.resolve(directory);
17103
- let current = import_node_path47.default.parse(absolute2).root;
17104
- for (const part of absolute2.slice(current.length).split(import_node_path47.default.sep)) {
17105
- current = import_node_path47.default.join(current, part);
17326
+ const absolute2 = import_node_path48.default.resolve(directory);
17327
+ let current = import_node_path48.default.parse(absolute2).root;
17328
+ for (const part of absolute2.slice(current.length).split(import_node_path48.default.sep)) {
17329
+ current = import_node_path48.default.join(current, part);
17106
17330
  try {
17107
- const stat2 = import_node_fs36.default.lstatSync(current);
17331
+ const stat2 = import_node_fs37.default.lstatSync(current);
17108
17332
  if (!stat2.isDirectory() || stat2.isSymbolicLink())
17109
17333
  throw new Error("Unsafe controller directory.");
17110
17334
  } catch (error) {
17111
17335
  if (error.code !== "ENOENT") throw error;
17112
- import_node_fs36.default.mkdirSync(current, { mode: 448 });
17336
+ import_node_fs37.default.mkdirSync(current, { mode: 448 });
17113
17337
  }
17114
17338
  }
17115
- const stat = import_node_fs36.default.lstatSync(absolute2);
17339
+ const stat = import_node_fs37.default.lstatSync(absolute2);
17116
17340
  if (stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
17117
17341
  throw new Error("Controller directory is not private.");
17118
17342
  }
17119
17343
  function validateOwnedFile(file, socket) {
17120
17344
  try {
17121
- const stat = import_node_fs36.default.lstatSync(file);
17345
+ const stat = import_node_fs37.default.lstatSync(file);
17122
17346
  if (stat.uid !== process.getuid?.() || (socket ? !stat.isSocket() : !stat.isFile()) || (stat.mode & 63) !== 0)
17123
17347
  throw new Error("Unsafe controller artifact.");
17124
17348
  return true;
@@ -17130,19 +17354,19 @@ function validateOwnedFile(file, socket) {
17130
17354
  async function runController(options) {
17131
17355
  if (options.operations && options.createOperations)
17132
17356
  throw new Error("Controller operations have multiple owners.");
17133
- const socketPath = import_node_path47.default.join(options.directory, "control.sock");
17357
+ const socketPath = import_node_path48.default.join(options.directory, "control.sock");
17134
17358
  if (Buffer.byteLength(socketPath) > 103) throw new Error("Controller socket path is too long.");
17135
17359
  privateDirectory(options.directory);
17136
- const lockPath = import_node_path47.default.join(options.directory, "owner.lock");
17360
+ const lockPath = import_node_path48.default.join(options.directory, "owner.lock");
17137
17361
  try {
17138
- const stat = import_node_fs36.default.lstatSync(lockPath);
17362
+ const stat = import_node_fs37.default.lstatSync(lockPath);
17139
17363
  if (!stat.isFile() || stat.uid !== process.getuid?.())
17140
17364
  throw new Error("Unsafe controller owner lock.");
17141
17365
  } catch (error) {
17142
17366
  if (error.code !== "ENOENT") throw error;
17143
17367
  }
17144
17368
  await withFileLock(lockPath, { activity: "controller ownership", waitMs: 0 }, async () => {
17145
- if (validateOwnedFile(socketPath, true)) import_node_fs36.default.unlinkSync(socketPath);
17369
+ if (validateOwnedFile(socketPath, true)) import_node_fs37.default.unlinkSync(socketPath);
17146
17370
  const sessions = new ControllerSessions(new ControllerStore(options.directory));
17147
17371
  const incarnation = sessions.read();
17148
17372
  let startupAvailable = true;
@@ -17531,7 +17755,7 @@ async function runController(options) {
17531
17755
  resolve();
17532
17756
  });
17533
17757
  });
17534
- import_node_fs36.default.chmodSync(socketPath, 384);
17758
+ import_node_fs37.default.chmodSync(socketPath, 384);
17535
17759
  await new Promise((resolve) => {
17536
17760
  finish = resolve;
17537
17761
  options.signal.addEventListener("abort", shutdown, { once: true });
@@ -17550,13 +17774,13 @@ async function runController(options) {
17550
17774
  }
17551
17775
  });
17552
17776
  }
17553
- var import_node_fs36, import_node_net4, import_node_path47, FRAME_BYTES;
17777
+ var import_node_fs37, import_node_net4, import_node_path48, FRAME_BYTES;
17554
17778
  var init_controller_server = __esm({
17555
17779
  "src/core/controller-server.ts"() {
17556
17780
  "use strict";
17557
- import_node_fs36 = __toESM(require("fs"));
17781
+ import_node_fs37 = __toESM(require("fs"));
17558
17782
  import_node_net4 = __toESM(require("net"));
17559
- import_node_path47 = __toESM(require("path"));
17783
+ import_node_path48 = __toESM(require("path"));
17560
17784
  init_controller_monitor();
17561
17785
  init_controller_protocol();
17562
17786
  init_controller_sessions();
@@ -17573,7 +17797,7 @@ __export(controller_exports, {
17573
17797
  runControllerCommand: () => runControllerCommand
17574
17798
  });
17575
17799
  async function runControllerCommand(method, options, repo, dependencies = {}) {
17576
- const directory = import_node_path48.default.join(DEVROUTER_HOME, "controller");
17800
+ const directory = import_node_path49.default.join(DEVROUTER_HOME, "controller");
17577
17801
  try {
17578
17802
  if (method === "run") {
17579
17803
  const controller = new AbortController();
@@ -17608,7 +17832,7 @@ async function runControllerCommand(method, options, repo, dependencies = {}) {
17608
17832
  if (fields2.timeout !== void 0) fields2.timeout = Number(fields2.timeout);
17609
17833
  await controllerRequest(
17610
17834
  directory,
17611
- { method, ...fields2, ...repo ? { path: import_node_path48.default.resolve(repo) } : {} },
17835
+ { method, ...fields2, ...repo ? { path: import_node_path49.default.resolve(repo) } : {} },
17612
17836
  (value) => process.stdout.write(`${JSON.stringify(value)}
17613
17837
  `)
17614
17838
  );
@@ -17623,11 +17847,11 @@ async function runControllerCommand(method, options, repo, dependencies = {}) {
17623
17847
  process.exitCode = 1;
17624
17848
  }
17625
17849
  }
17626
- var import_node_path48;
17850
+ var import_node_path49;
17627
17851
  var init_controller = __esm({
17628
17852
  "src/commands/controller.ts"() {
17629
17853
  "use strict";
17630
- import_node_path48 = __toESM(require("path"));
17854
+ import_node_path49 = __toESM(require("path"));
17631
17855
  init_capacity_collector();
17632
17856
  init_capacity_controller();
17633
17857
  init_capacity_policy();
@@ -17678,28 +17902,28 @@ function replaceDevrouterSection(content) {
17678
17902
  return `${content.slice(0, start)}${buildDevrouterSection()}${content.slice(end)}`;
17679
17903
  }
17680
17904
  function writeRepoFile(repoPath, relPath, content) {
17681
- const absolutePath = (0, import_node_path49.join)(repoPath, relPath);
17682
- (0, import_node_fs37.mkdirSync)((0, import_node_path49.dirname)(absolutePath), { recursive: true });
17683
- (0, import_node_fs37.writeFileSync)(absolutePath, content, "utf-8");
17905
+ const absolutePath = (0, import_node_path50.join)(repoPath, relPath);
17906
+ (0, import_node_fs38.mkdirSync)((0, import_node_path50.dirname)(absolutePath), { recursive: true });
17907
+ (0, import_node_fs38.writeFileSync)(absolutePath, content, "utf-8");
17684
17908
  return absolutePath;
17685
17909
  }
17686
17910
  function ensureAgentsMdSection(repoPath) {
17687
- const filePath = (0, import_node_path49.join)(repoPath, AGENTS_MD);
17688
- if ((0, import_node_fs37.existsSync)(filePath)) {
17689
- const content = (0, import_node_fs37.readFileSync)(filePath, "utf-8");
17911
+ const filePath = (0, import_node_path50.join)(repoPath, AGENTS_MD);
17912
+ if ((0, import_node_fs38.existsSync)(filePath)) {
17913
+ const content = (0, import_node_fs38.readFileSync)(filePath, "utf-8");
17690
17914
  if (content.includes(DEVROUTER_SENTINEL)) {
17691
17915
  const updated = replaceDevrouterSection(content);
17692
17916
  if (updated === void 0 || updated === content) return { path: filePath, written: false };
17693
- (0, import_node_fs37.writeFileSync)(filePath, updated, "utf-8");
17917
+ (0, import_node_fs38.writeFileSync)(filePath, updated, "utf-8");
17694
17918
  return { path: filePath, written: true };
17695
17919
  }
17696
- (0, import_node_fs37.writeFileSync)(filePath, `${content.trimEnd()}
17920
+ (0, import_node_fs38.writeFileSync)(filePath, `${content.trimEnd()}
17697
17921
 
17698
17922
  ${buildDevrouterSection()}
17699
17923
  `, "utf-8");
17700
17924
  return { path: filePath, written: true };
17701
17925
  }
17702
- (0, import_node_fs37.writeFileSync)(filePath, `# AGENTS.md
17926
+ (0, import_node_fs38.writeFileSync)(filePath, `# AGENTS.md
17703
17927
 
17704
17928
  ${buildDevrouterSection()}
17705
17929
  `, "utf-8");
@@ -17709,12 +17933,12 @@ function ensureSkillFile(repoPath) {
17709
17933
  const filePath = writeRepoFile(repoPath, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT);
17710
17934
  return { path: filePath, written: true };
17711
17935
  }
17712
- 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;
17936
+ var import_node_fs38, import_node_path50, DEVROUTER_SENTINEL, DEVROUTER_END_SENTINEL, LEGACY_DEVROUTER_SECTION_END, AGENTS_MD, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT;
17713
17937
  var init_agents_md = __esm({
17714
17938
  "src/core/agents-md.ts"() {
17715
17939
  "use strict";
17716
- import_node_fs37 = require("fs");
17717
- import_node_path49 = require("path");
17940
+ import_node_fs38 = require("fs");
17941
+ import_node_path50 = require("path");
17718
17942
  DEVROUTER_SENTINEL = "<!-- devrouter -->";
17719
17943
  DEVROUTER_END_SENTINEL = "<!-- /devrouter -->";
17720
17944
  LEGACY_DEVROUTER_SECTION_END = "- `devrouter ls`";
@@ -18439,7 +18663,7 @@ var init_ai_prompt = __esm({
18439
18663
  },
18440
18664
  {
18441
18665
  command: "devrouter stop [path] [--delete] [--json]",
18442
- purpose: "Stop the exact checkout workspace runtime and routes; --delete explicitly deletes the ownership-proven runtime without removing the checkout."
18666
+ purpose: "Stop the exact checkout workspace runtime and routes, or prove absence after pre-registration failure; --delete explicitly deletes the ownership-proven runtime without removing the checkout."
18443
18667
  },
18444
18668
  {
18445
18669
  command: "devrouter exec [path] -- <command...>",
@@ -18993,11 +19217,11 @@ function extractCurrentVersionFromRepoConfig(repoPath) {
18993
19217
  };
18994
19218
  }
18995
19219
  function readPromptDirectory(promptDirPath) {
18996
- if (!import_node_fs38.default.existsSync(promptDirPath)) {
19220
+ if (!import_node_fs39.default.existsSync(promptDirPath)) {
18997
19221
  throw new Error(`Missing ${UPGRADE_PROMPTS_DIR} directory at ${promptDirPath}.`);
18998
19222
  }
18999
19223
  const releases = [];
19000
- const files = import_node_fs38.default.readdirSync(promptDirPath, { withFileTypes: true });
19224
+ const files = import_node_fs39.default.readdirSync(promptDirPath, { withFileTypes: true });
19001
19225
  for (const file of files) {
19002
19226
  if (!file.isFile() || !file.name.endsWith(".md")) {
19003
19227
  continue;
@@ -19007,8 +19231,8 @@ function readPromptDirectory(promptDirPath) {
19007
19231
  continue;
19008
19232
  }
19009
19233
  const version = normalizeVersion(basename);
19010
- const promptPath = import_node_path50.default.join(promptDirPath, file.name);
19011
- const prompt = import_node_fs38.default.readFileSync(promptPath, "utf-8").trim();
19234
+ const promptPath = import_node_path51.default.join(promptDirPath, file.name);
19235
+ const prompt = import_node_fs39.default.readFileSync(promptPath, "utf-8").trim();
19012
19236
  releases.push({ version, prompt, promptPath });
19013
19237
  }
19014
19238
  const deduped = /* @__PURE__ */ new Map();
@@ -19029,19 +19253,19 @@ function listAvailableUpgradeTargets(currentVersion, releases) {
19029
19253
  }
19030
19254
  function resolvePromptDirectory(explicitPath) {
19031
19255
  if (explicitPath) {
19032
- return import_node_path50.default.resolve(explicitPath);
19256
+ return import_node_path51.default.resolve(explicitPath);
19033
19257
  }
19034
- const entryFile = process.argv[1] ? import_node_path50.default.resolve(process.argv[1]) : __filename;
19035
- const entryDir = import_node_path50.default.dirname(entryFile);
19036
- const resolvedEntryDir = import_node_fs38.default.existsSync(entryFile) ? import_node_path50.default.dirname(import_node_fs38.default.realpathSync(entryFile)) : entryDir;
19258
+ const entryFile = process.argv[1] ? import_node_path51.default.resolve(process.argv[1]) : __filename;
19259
+ const entryDir = import_node_path51.default.dirname(entryFile);
19260
+ const resolvedEntryDir = import_node_fs39.default.existsSync(entryFile) ? import_node_path51.default.dirname(import_node_fs39.default.realpathSync(entryFile)) : entryDir;
19037
19261
  const candidates = [
19038
- import_node_path50.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
19039
- import_node_path50.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
19040
- import_node_path50.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
19041
- import_node_path50.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
19042
- import_node_path50.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
19262
+ import_node_path51.default.resolve(resolvedEntryDir, "..", UPGRADE_PROMPTS_DIR),
19263
+ import_node_path51.default.resolve(entryDir, "..", UPGRADE_PROMPTS_DIR),
19264
+ import_node_path51.default.resolve(__dirname, "..", UPGRADE_PROMPTS_DIR),
19265
+ import_node_path51.default.resolve(__dirname, "..", "..", UPGRADE_PROMPTS_DIR),
19266
+ import_node_path51.default.resolve(process.cwd(), UPGRADE_PROMPTS_DIR)
19043
19267
  ];
19044
- const existing = candidates.find((candidate) => import_node_fs38.default.existsSync(candidate));
19268
+ const existing = candidates.find((candidate) => import_node_fs39.default.existsSync(candidate));
19045
19269
  return existing ?? candidates[0];
19046
19270
  }
19047
19271
  function loadUpgradeCatalog(options = {}) {
@@ -19057,12 +19281,12 @@ function loadUpgradeCatalog(options = {}) {
19057
19281
  releases
19058
19282
  };
19059
19283
  }
19060
- var import_node_fs38, import_node_path50, SEMVER_RE, UPGRADE_PROMPTS_DIR;
19284
+ var import_node_fs39, import_node_path51, SEMVER_RE, UPGRADE_PROMPTS_DIR;
19061
19285
  var init_upgrade = __esm({
19062
19286
  "src/core/upgrade.ts"() {
19063
19287
  "use strict";
19064
- import_node_fs38 = __toESM(require("fs"));
19065
- import_node_path50 = __toESM(require("path"));
19288
+ import_node_fs39 = __toESM(require("fs"));
19289
+ import_node_path51 = __toESM(require("path"));
19066
19290
  init_repo_config();
19067
19291
  SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
19068
19292
  UPGRADE_PROMPTS_DIR = "upgrade-prompts";
@@ -19205,7 +19429,7 @@ function isLoopbackHost(host) {
19205
19429
  return host === "127.0.0.1" || host === "localhost" || host === "host.docker.internal";
19206
19430
  }
19207
19431
  function inspectCompose(composeFile, _workspace) {
19208
- if (!import_node_fs39.default.existsSync(composeFile)) {
19432
+ if (!import_node_fs40.default.existsSync(composeFile)) {
19209
19433
  return {
19210
19434
  aliases: [],
19211
19435
  publishedPorts: [],
@@ -19214,7 +19438,7 @@ function inspectCompose(composeFile, _workspace) {
19214
19438
  };
19215
19439
  }
19216
19440
  try {
19217
- const raw = import_node_fs39.default.readFileSync(composeFile, "utf-8");
19441
+ const raw = import_node_fs40.default.readFileSync(composeFile, "utf-8");
19218
19442
  const parsed = import_yaml5.default.parse(raw);
19219
19443
  const root = asRecord(parsed);
19220
19444
  const services = asRecord(root?.services);
@@ -19310,11 +19534,11 @@ function buildHostPortClaimsCheck(repoPath, workspace) {
19310
19534
  }
19311
19535
  }
19312
19536
  function buildDevcontainerChecks(repoPath, config, workspace) {
19313
- const devcontainerDir = import_node_path51.default.join(repoPath, ".devcontainer");
19314
- if (!import_node_fs39.default.existsSync(devcontainerDir)) {
19537
+ const devcontainerDir = import_node_path52.default.join(repoPath, ".devcontainer");
19538
+ if (!import_node_fs40.default.existsSync(devcontainerDir)) {
19315
19539
  return [];
19316
19540
  }
19317
- const compose = inspectCompose(import_node_path51.default.join(devcontainerDir, "docker-compose.yml"), workspace);
19541
+ const compose = inspectCompose(import_node_path52.default.join(devcontainerDir, "docker-compose.yml"), workspace);
19318
19542
  const checks = [];
19319
19543
  if (compose.parseError) {
19320
19544
  checks.push({
@@ -19341,9 +19565,9 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
19341
19565
  details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
19342
19566
  suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
19343
19567
  });
19344
- const dockerfilePath = import_node_path51.default.join(devcontainerDir, "Dockerfile");
19345
- const dockerfileExists = import_node_fs39.default.existsSync(dockerfilePath);
19346
- const dockerfile = dockerfileExists ? import_node_fs39.default.readFileSync(dockerfilePath, "utf-8") : "";
19568
+ const dockerfilePath = import_node_path52.default.join(devcontainerDir, "Dockerfile");
19569
+ const dockerfileExists = import_node_fs40.default.existsSync(dockerfilePath);
19570
+ const dockerfile = dockerfileExists ? import_node_fs40.default.readFileSync(dockerfilePath, "utf-8") : "";
19347
19571
  const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
19348
19572
  (artifact) => dockerfile.includes(artifact)
19349
19573
  );
@@ -19401,12 +19625,12 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
19401
19625
  });
19402
19626
  return checks;
19403
19627
  }
19404
- var import_node_fs39, import_node_path51, import_yaml5;
19628
+ var import_node_fs40, import_node_path52, import_yaml5;
19405
19629
  var init_devcontainer_diagnostics = __esm({
19406
19630
  "src/core/devcontainer-diagnostics.ts"() {
19407
19631
  "use strict";
19408
- import_node_fs39 = __toESM(require("fs"));
19409
- import_node_path51 = __toESM(require("path"));
19632
+ import_node_fs40 = __toESM(require("fs"));
19633
+ import_node_path52 = __toESM(require("path"));
19410
19634
  import_yaml5 = __toESM(require("yaml"));
19411
19635
  init_devcontainer_profile();
19412
19636
  init_host_port_claims();
@@ -19501,11 +19725,11 @@ function reconcileRouteRunConflict(repoPath, app) {
19501
19725
  }
19502
19726
  }
19503
19727
  }
19504
- var import_node_fs40;
19728
+ var import_node_fs41;
19505
19729
  var init_route_state = __esm({
19506
19730
  "src/core/route-state.ts"() {
19507
19731
  "use strict";
19508
- import_node_fs40 = __toESM(require("fs"));
19732
+ import_node_fs41 = __toESM(require("fs"));
19509
19733
  init_host_routes();
19510
19734
  init_workspace();
19511
19735
  }
@@ -19680,7 +19904,7 @@ function toRepoStatus(repoPath) {
19680
19904
  const resolvedRepoPath = resolveRepoPath(repoPath);
19681
19905
  const configPath = getRepoConfigPath(resolvedRepoPath);
19682
19906
  const explicitRepo = typeof repoPath === "string" && repoPath.trim().length > 0;
19683
- const configExists = import_node_fs41.default.existsSync(configPath);
19907
+ const configExists = import_node_fs42.default.existsSync(configPath);
19684
19908
  if (!explicitRepo && !configExists) {
19685
19909
  return void 0;
19686
19910
  }
@@ -19828,11 +20052,11 @@ async function collectRouterStatus(repoPath) {
19828
20052
  }
19829
20053
  };
19830
20054
  }
19831
- var import_node_fs41;
20055
+ var import_node_fs42;
19832
20056
  var init_status = __esm({
19833
20057
  "src/core/status.ts"() {
19834
20058
  "use strict";
19835
- import_node_fs41 = __toESM(require("fs"));
20059
+ import_node_fs42 = __toESM(require("fs"));
19836
20060
  init_docker();
19837
20061
  init_managed_runtime_state();
19838
20062
  init_managed_runtime_status();
@@ -19906,12 +20130,12 @@ function parseMinimumNodeMajor(value) {
19906
20130
  return Number(match[1]);
19907
20131
  }
19908
20132
  function readPackageJson(repoPath) {
19909
- const packagePath = import_node_path52.default.join(repoPath, "package.json");
19910
- if (!import_node_fs42.default.existsSync(packagePath)) {
20133
+ const packagePath = import_node_path53.default.join(repoPath, "package.json");
20134
+ if (!import_node_fs43.default.existsSync(packagePath)) {
19911
20135
  return void 0;
19912
20136
  }
19913
20137
  try {
19914
- return JSON.parse(import_node_fs42.default.readFileSync(packagePath, "utf-8"));
20138
+ return JSON.parse(import_node_fs43.default.readFileSync(packagePath, "utf-8"));
19915
20139
  } catch {
19916
20140
  return void 0;
19917
20141
  }
@@ -20095,13 +20319,13 @@ function buildGlobalToolChecksWithoutTelemetry(repoPath) {
20095
20319
  checks.push(nodeToolchainCheck(repoPath));
20096
20320
  return checks;
20097
20321
  }
20098
- var import_node_child_process29, import_node_fs42, import_node_path52, DEVSY_DISABLE_TELEMETRY;
20322
+ var import_node_child_process29, import_node_fs43, import_node_path53, DEVSY_DISABLE_TELEMETRY;
20099
20323
  var init_tool_diagnostics = __esm({
20100
20324
  "src/core/tool-diagnostics.ts"() {
20101
20325
  "use strict";
20102
20326
  import_node_child_process29 = require("child_process");
20103
- import_node_fs42 = __toESM(require("fs"));
20104
- import_node_path52 = __toESM(require("path"));
20327
+ import_node_fs43 = __toESM(require("fs"));
20328
+ import_node_path53 = __toESM(require("path"));
20105
20329
  init_devsy_agent();
20106
20330
  init_workspace_runtime();
20107
20331
  DEVSY_DISABLE_TELEMETRY = "DEVSY_DISABLE_TELEMETRY";
@@ -20118,10 +20342,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
20118
20342
  if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
20119
20343
  const comparableRepo = comparableWorkspacePath(repoPath);
20120
20344
  const comparableWorktree = comparableWorkspacePath(worktreePath);
20121
- const localRoot = import_node_path53.default.join(comparableRepo, "trees") + import_node_path53.default.sep;
20345
+ const localRoot = import_node_path54.default.join(comparableRepo, "trees") + import_node_path54.default.sep;
20122
20346
  if (comparableWorktree.startsWith(localRoot)) return true;
20123
- const legacyPrefix = `${import_node_path53.default.basename(comparableRepo)}-`;
20124
- return import_node_path53.default.dirname(comparableWorktree) === import_node_path53.default.dirname(comparableRepo) && import_node_path53.default.basename(comparableWorktree).startsWith(legacyPrefix);
20347
+ const legacyPrefix = `${import_node_path54.default.basename(comparableRepo)}-`;
20348
+ return import_node_path54.default.dirname(comparableWorktree) === import_node_path54.default.dirname(comparableRepo) && import_node_path54.default.basename(comparableWorktree).startsWith(legacyPrefix);
20125
20349
  }
20126
20350
  function previewActions(devpodStatus, routeCount, includeRecord) {
20127
20351
  const actions = [
@@ -20373,11 +20597,11 @@ function applyWorkspaceGc(plan) {
20373
20597
  candidates
20374
20598
  };
20375
20599
  }
20376
- var import_node_path53;
20600
+ var import_node_path54;
20377
20601
  var init_workspace_gc = __esm({
20378
20602
  "src/core/workspace-gc.ts"() {
20379
20603
  "use strict";
20380
- import_node_path53 = __toESM(require("path"));
20604
+ import_node_path54 = __toESM(require("path"));
20381
20605
  init_devpod_mutation();
20382
20606
  init_devpod_workspaces();
20383
20607
  init_host_routes();
@@ -20447,11 +20671,11 @@ function inspectPostgresCredentials(repoPath, config) {
20447
20671
  parseErrors.push(`${app.name}: ${composeFile} (${message})`);
20448
20672
  continue;
20449
20673
  }
20450
- if (!import_node_fs43.default.existsSync(absolutePath)) {
20674
+ if (!import_node_fs44.default.existsSync(absolutePath)) {
20451
20675
  continue;
20452
20676
  }
20453
20677
  try {
20454
- const raw = import_node_fs43.default.readFileSync(absolutePath, "utf-8");
20678
+ const raw = import_node_fs44.default.readFileSync(absolutePath, "utf-8");
20455
20679
  const parsed = import_yaml6.default.parse(raw);
20456
20680
  const root = asRecord2(parsed);
20457
20681
  const services = asRecord2(root?.services);
@@ -20731,7 +20955,7 @@ async function buildDoctorReport(options = {}) {
20731
20955
  const config = runtimeConfig.config;
20732
20956
  loadedConfig = config;
20733
20957
  loadedWorkspace = runtimeConfig.workspace;
20734
- const cliVersion = true ? "0.0.73" : "0.0.0-dev";
20958
+ const cliVersion = true ? "0.0.74" : "0.0.0-dev";
20735
20959
  const configVersion = config.devrouter?.version;
20736
20960
  if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
20737
20961
  addCheck(checks, {
@@ -20761,9 +20985,9 @@ async function buildDoctorReport(options = {}) {
20761
20985
  (app) => app.docker.composeFiles.map((filePath) => ({
20762
20986
  app: app.name,
20763
20987
  filePath,
20764
- absolutePath: import_node_path54.default.resolve(repo.path, filePath)
20988
+ absolutePath: import_node_path55.default.resolve(repo.path, filePath)
20765
20989
  }))
20766
- ).filter((entry) => !import_node_fs43.default.existsSync(entry.absolutePath));
20990
+ ).filter((entry) => !import_node_fs44.default.existsSync(entry.absolutePath));
20767
20991
  addCheck(checks, {
20768
20992
  id: "repo.compose-files",
20769
20993
  level: missingComposeFiles.length === 0 ? "ok" : "error",
@@ -20806,8 +21030,8 @@ async function buildDoctorReport(options = {}) {
20806
21030
  const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
20807
21031
  app: app.name,
20808
21032
  cwd: app.hostRun.cwd,
20809
- absolutePath: import_node_path54.default.resolve(repo.path, app.hostRun.cwd)
20810
- })).filter((entry) => !import_node_fs43.default.existsSync(entry.absolutePath));
21033
+ absolutePath: import_node_path55.default.resolve(repo.path, app.hostRun.cwd)
21034
+ })).filter((entry) => !import_node_fs44.default.existsSync(entry.absolutePath));
20811
21035
  addCheck(checks, {
20812
21036
  id: "repo.host-cwd",
20813
21037
  level: missingHostCwds.length === 0 ? "ok" : "error",
@@ -20951,12 +21175,12 @@ async function buildDoctorReport(options = {}) {
20951
21175
  nextSteps
20952
21176
  };
20953
21177
  }
20954
- var import_node_fs43, import_node_path54, import_yaml6, POSTGRES_DEFAULTS;
21178
+ var import_node_fs44, import_node_path55, import_yaml6, POSTGRES_DEFAULTS;
20955
21179
  var init_doctor = __esm({
20956
21180
  "src/core/doctor.ts"() {
20957
21181
  "use strict";
20958
- import_node_fs43 = __toESM(require("fs"));
20959
- import_node_path54 = __toESM(require("path"));
21182
+ import_node_fs44 = __toESM(require("fs"));
21183
+ import_node_path55 = __toESM(require("path"));
20960
21184
  import_yaml6 = __toESM(require("yaml"));
20961
21185
  init_docker();
20962
21186
  init_host_routes();
@@ -21454,6 +21678,11 @@ async function runStopCommand(options) {
21454
21678
  return;
21455
21679
  }
21456
21680
  const label2 = result.kind === "primary" ? "Primary checkout" : `Workspace '${result.workspace}'`;
21681
+ if (result.runtimeAbsent) {
21682
+ process.stdout.write(`${label2} has no runtime or routes; stop is complete.
21683
+ `);
21684
+ return;
21685
+ }
21457
21686
  if (!result.stopped && !result.deleted && result.freedRoutes === 0) {
21458
21687
  process.stdout.write(`${label2} is already stopped; no routes needed removal.
21459
21688
  `);
@@ -21774,17 +22003,17 @@ function asRecord3(value) {
21774
22003
  return value;
21775
22004
  }
21776
22005
  function readJson(filePath) {
21777
- if (!import_node_fs44.default.existsSync(filePath)) {
22006
+ if (!import_node_fs45.default.existsSync(filePath)) {
21778
22007
  return void 0;
21779
22008
  }
21780
22009
  try {
21781
- return JSON.parse(import_node_fs44.default.readFileSync(filePath, "utf-8"));
22010
+ return JSON.parse(import_node_fs45.default.readFileSync(filePath, "utf-8"));
21782
22011
  } catch {
21783
22012
  return void 0;
21784
22013
  }
21785
22014
  }
21786
22015
  function relative(repoPath, filePath) {
21787
- return import_node_path55.default.relative(repoPath, filePath) || ".";
22016
+ return import_node_path56.default.relative(repoPath, filePath) || ".";
21788
22017
  }
21789
22018
  function redactEnvAssignments(value) {
21790
22019
  return value.replace(
@@ -21825,7 +22054,7 @@ function inspectPackageManager(repoPath, pkg) {
21825
22054
  ["bun.lock", "bun"]
21826
22055
  ];
21827
22056
  for (const [fileName, name] of lockfiles) {
21828
- if (import_node_fs44.default.existsSync(import_node_path55.default.join(repoPath, fileName))) {
22057
+ if (import_node_fs45.default.existsSync(import_node_path56.default.join(repoPath, fileName))) {
21829
22058
  return { name, source: fileName };
21830
22059
  }
21831
22060
  }
@@ -21840,9 +22069,9 @@ function inspectNode(repoPath, pkg) {
21840
22069
  if (typeof engines?.node === "string") {
21841
22070
  return { version: engines.node, source: "package.json:engines.node" };
21842
22071
  }
21843
- const nvmrc = import_node_path55.default.join(repoPath, ".nvmrc");
21844
- if (import_node_fs44.default.existsSync(nvmrc)) {
21845
- const version = import_node_fs44.default.readFileSync(nvmrc, "utf-8").trim();
22072
+ const nvmrc = import_node_path56.default.join(repoPath, ".nvmrc");
22073
+ if (import_node_fs45.default.existsSync(nvmrc)) {
22074
+ const version = import_node_fs45.default.readFileSync(nvmrc, "utf-8").trim();
21846
22075
  return { version, source: ".nvmrc" };
21847
22076
  }
21848
22077
  return void 0;
@@ -21903,7 +22132,7 @@ function configuredComposeFiles(repoPath) {
21903
22132
  const files = config.apps.filter(
21904
22133
  (app) => app.runtime === "docker"
21905
22134
  ).flatMap((app) => app.docker.composeFiles).filter(
21906
- (fileName) => !import_node_path55.default.isAbsolute(fileName) && !import_node_path55.default.normalize(fileName).startsWith("..")
22135
+ (fileName) => !import_node_path56.default.isAbsolute(fileName) && !import_node_path56.default.normalize(fileName).startsWith("..")
21907
22136
  );
21908
22137
  return Array.from(new Set(files));
21909
22138
  } catch {
@@ -21921,7 +22150,7 @@ function composeFiles(repoPath) {
21921
22150
  ...configuredComposeFiles(repoPath)
21922
22151
  ];
21923
22152
  return Array.from(new Set(candidates)).filter(
21924
- (fileName) => import_node_fs44.default.existsSync(import_node_path55.default.join(repoPath, fileName))
22153
+ (fileName) => import_node_fs45.default.existsSync(import_node_path56.default.join(repoPath, fileName))
21925
22154
  );
21926
22155
  }
21927
22156
  function stringArray2(value) {
@@ -21959,7 +22188,7 @@ function inspectServices(repoPath) {
21959
22188
  const services = [];
21960
22189
  for (const fileName of composeFiles(repoPath)) {
21961
22190
  try {
21962
- const parsed = import_yaml7.default.parse(import_node_fs44.default.readFileSync(import_node_path55.default.join(repoPath, fileName), "utf-8"));
22191
+ const parsed = import_yaml7.default.parse(import_node_fs45.default.readFileSync(import_node_path56.default.join(repoPath, fileName), "utf-8"));
21963
22192
  const serviceMap = asRecord3(asRecord3(parsed)?.services);
21964
22193
  for (const [name, value] of Object.entries(serviceMap ?? {})) {
21965
22194
  const service = asRecord3(value);
@@ -21994,8 +22223,8 @@ function inspectServices(repoPath) {
21994
22223
  return services;
21995
22224
  }
21996
22225
  function inspectEnvFiles(repoPath) {
21997
- const files = import_node_fs44.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
21998
- const content = import_node_fs44.default.readFileSync(import_node_path55.default.join(repoPath, fileName), "utf-8");
22226
+ const files = import_node_fs45.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
22227
+ const content = import_node_fs45.default.readFileSync(import_node_path56.default.join(repoPath, fileName), "utf-8");
21999
22228
  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();
22000
22229
  return { path: fileName, names };
22001
22230
  });
@@ -22009,18 +22238,18 @@ function inspectEnvFiles(repoPath) {
22009
22238
  };
22010
22239
  }
22011
22240
  function inspectDevcontainer(repoPath) {
22012
- const dir = import_node_path55.default.join(repoPath, ".devcontainer");
22013
- if (!import_node_fs44.default.existsSync(dir)) {
22241
+ const dir = import_node_path56.default.join(repoPath, ".devcontainer");
22242
+ if (!import_node_fs45.default.existsSync(dir)) {
22014
22243
  return { exists: false, files: [] };
22015
22244
  }
22016
22245
  return {
22017
22246
  exists: true,
22018
- 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}`)
22247
+ files: import_node_fs45.default.readdirSync(dir).filter((fileName) => import_node_fs45.default.statSync(import_node_path56.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
22019
22248
  };
22020
22249
  }
22021
22250
  function inspectDevrouter(repoPath) {
22022
22251
  const configPath = getRepoConfigPath(repoPath);
22023
- if (!import_node_fs44.default.existsSync(configPath)) {
22252
+ if (!import_node_fs45.default.existsSync(configPath)) {
22024
22253
  return {
22025
22254
  exists: false,
22026
22255
  configPath,
@@ -22064,15 +22293,15 @@ function inspectAgentGuidance(repoPath) {
22064
22293
  ["AGENTS.md", "agents"],
22065
22294
  ["CLAUDE.md", "claude"]
22066
22295
  ]) {
22067
- if (import_node_fs44.default.existsSync(import_node_path55.default.join(repoPath, fileName))) {
22296
+ if (import_node_fs45.default.existsSync(import_node_path56.default.join(repoPath, fileName))) {
22068
22297
  results.push({ path: fileName, kind });
22069
22298
  }
22070
22299
  }
22071
- const skillsDir = import_node_path55.default.join(repoPath, ".agents", "skills");
22072
- if (import_node_fs44.default.existsSync(skillsDir)) {
22073
- for (const name of import_node_fs44.default.readdirSync(skillsDir).sort()) {
22074
- const skillPath = import_node_path55.default.join(skillsDir, name, "SKILL.md");
22075
- if (import_node_fs44.default.existsSync(skillPath)) {
22300
+ const skillsDir = import_node_path56.default.join(repoPath, ".agents", "skills");
22301
+ if (import_node_fs45.default.existsSync(skillsDir)) {
22302
+ for (const name of import_node_fs45.default.readdirSync(skillsDir).sort()) {
22303
+ const skillPath = import_node_path56.default.join(skillsDir, name, "SKILL.md");
22304
+ if (import_node_fs45.default.existsSync(skillPath)) {
22076
22305
  results.push({ path: relative(repoPath, skillPath), kind: "skill" });
22077
22306
  }
22078
22307
  }
@@ -22115,7 +22344,7 @@ function buildIssues(report) {
22115
22344
  }
22116
22345
  function inspectRepo(options = {}) {
22117
22346
  const repoPath = resolveRepoPath(options.repo);
22118
- const pkg = readJson(import_node_path55.default.join(repoPath, "package.json"));
22347
+ const pkg = readJson(import_node_path56.default.join(repoPath, "package.json"));
22119
22348
  const scripts = inspectScripts(pkg);
22120
22349
  const reportWithoutIssues = {
22121
22350
  repoPath,
@@ -22134,12 +22363,12 @@ function inspectRepo(options = {}) {
22134
22363
  issues: buildIssues(reportWithoutIssues)
22135
22364
  };
22136
22365
  }
22137
- var import_node_fs44, import_node_path55, import_yaml7;
22366
+ var import_node_fs45, import_node_path56, import_yaml7;
22138
22367
  var init_repo_inspect = __esm({
22139
22368
  "src/core/repo-inspect.ts"() {
22140
22369
  "use strict";
22141
- import_node_fs44 = __toESM(require("fs"));
22142
- import_node_path55 = __toESM(require("path"));
22370
+ import_node_fs45 = __toESM(require("fs"));
22371
+ import_node_path56 = __toESM(require("path"));
22143
22372
  import_yaml7 = __toESM(require("yaml"));
22144
22373
  init_repo_config();
22145
22374
  }
@@ -22244,7 +22473,7 @@ function requiredFileChecks(repoPath) {
22244
22473
  ".devcontainer/docker-compose.yml",
22245
22474
  ".devrouter.yml"
22246
22475
  ];
22247
- const missing = required.filter((fileName) => !import_node_fs45.default.existsSync(import_node_path56.default.join(repoPath, fileName)));
22476
+ const missing = required.filter((fileName) => !import_node_fs46.default.existsSync(import_node_path57.default.join(repoPath, fileName)));
22248
22477
  return {
22249
22478
  id: "repo.devcontainer.verify-files",
22250
22479
  level: missing.length === 0 ? "ok" : "error",
@@ -22469,12 +22698,12 @@ async function verifyDevcontainer(options = {}) {
22469
22698
  nextSteps: collectNextSteps3(checks)
22470
22699
  };
22471
22700
  }
22472
- var import_node_fs45, import_node_path56;
22701
+ var import_node_fs46, import_node_path57;
22473
22702
  var init_devcontainer_verify = __esm({
22474
22703
  "src/core/devcontainer-verify.ts"() {
22475
22704
  "use strict";
22476
- import_node_fs45 = __toESM(require("fs"));
22477
- import_node_path56 = __toESM(require("path"));
22705
+ import_node_fs46 = __toESM(require("fs"));
22706
+ import_node_path57 = __toESM(require("path"));
22478
22707
  init_capabilities();
22479
22708
  init_doctor();
22480
22709
  init_host_routes();
@@ -22771,7 +23000,7 @@ function packageManagerIssues(repo) {
22771
23000
  }
22772
23001
  function plannedFiles(repoPath, version) {
22773
23002
  const repo = inspectRepo({ repo: repoPath });
22774
- const projectName = sanitizeProjectName(import_node_path57.default.basename(repo.repoPath));
23003
+ const projectName = sanitizeProjectName(import_node_path58.default.basename(repo.repoPath));
22775
23004
  const nodeMajor = majorVersion(repo.node?.version, "24");
22776
23005
  const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
22777
23006
  const port = inferPort2(repo);
@@ -22816,8 +23045,8 @@ function plannedFiles(repoPath, version) {
22816
23045
  };
22817
23046
  }
22818
23047
  function classifyFile(repoPath, file) {
22819
- const absolutePath = import_node_path57.default.join(repoPath, file.relativePath);
22820
- if (!import_node_fs46.default.existsSync(absolutePath)) {
23048
+ const absolutePath = import_node_path58.default.join(repoPath, file.relativePath);
23049
+ if (!import_node_fs47.default.existsSync(absolutePath)) {
22821
23050
  return {
22822
23051
  path: file.relativePath,
22823
23052
  action: "create",
@@ -22825,7 +23054,7 @@ function classifyFile(repoPath, file) {
22825
23054
  bytes: Buffer.byteLength(file.content)
22826
23055
  };
22827
23056
  }
22828
- const current = import_node_fs46.default.readFileSync(absolutePath, "utf-8");
23057
+ const current = import_node_fs47.default.readFileSync(absolutePath, "utf-8");
22829
23058
  if (!current.includes(MANAGED_MARKER2)) {
22830
23059
  return {
22831
23060
  path: file.relativePath,
@@ -22882,11 +23111,11 @@ function buildPlan(repoPath, dryRun, version) {
22882
23111
  };
22883
23112
  }
22884
23113
  function writeFile(repoPath, file) {
22885
- const absolutePath = import_node_path57.default.join(repoPath, file.relativePath);
22886
- import_node_fs46.default.mkdirSync(import_node_path57.default.dirname(absolutePath), { recursive: true });
22887
- import_node_fs46.default.writeFileSync(absolutePath, file.content, "utf-8");
23114
+ const absolutePath = import_node_path58.default.join(repoPath, file.relativePath);
23115
+ import_node_fs47.default.mkdirSync(import_node_path58.default.dirname(absolutePath), { recursive: true });
23116
+ import_node_fs47.default.writeFileSync(absolutePath, file.content, "utf-8");
22888
23117
  if (file.executable) {
22889
- import_node_fs46.default.chmodSync(absolutePath, 493);
23118
+ import_node_fs47.default.chmodSync(absolutePath, 493);
22890
23119
  }
22891
23120
  }
22892
23121
  function writeDevcontainer(options = {}) {
@@ -22924,12 +23153,12 @@ function writeDevcontainer(options = {}) {
22924
23153
  nextSteps: postWriteNextSteps(repoPath)
22925
23154
  };
22926
23155
  }
22927
- var import_node_fs46, import_node_path57, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
23156
+ var import_node_fs47, import_node_path58, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
22928
23157
  var init_devcontainer_write = __esm({
22929
23158
  "src/core/devcontainer-write.ts"() {
22930
23159
  "use strict";
22931
- import_node_fs46 = __toESM(require("fs"));
22932
- import_node_path57 = __toESM(require("path"));
23160
+ import_node_fs47 = __toESM(require("fs"));
23161
+ import_node_path58 = __toESM(require("path"));
22933
23162
  init_repo_config();
22934
23163
  init_repo_inspect();
22935
23164
  MANAGED_MARKER2 = "devrouter:managed devcontainer";
@@ -23263,13 +23492,13 @@ function buildProfilePlanReport(options) {
23263
23492
  return { ...report, contractPath, bindings };
23264
23493
  }
23265
23494
  function loadContract(repoPath, contractPath) {
23266
- if (import_node_path58.default.isAbsolute(contractPath)) {
23495
+ if (import_node_path59.default.isAbsolute(contractPath)) {
23267
23496
  fail3("profile plan contract path must be relative to the repository.");
23268
23497
  }
23269
23498
  const absolutePath = assertPathWithinRepo(contractPath, repoPath, "Profile plan contract");
23270
23499
  let metadata;
23271
23500
  try {
23272
- metadata = import_node_fs47.default.lstatSync(absolutePath);
23501
+ metadata = import_node_fs48.default.lstatSync(absolutePath);
23273
23502
  } catch (error) {
23274
23503
  fail3(`could not inspect profile plan contract '${contractPath}': ${error.message}`);
23275
23504
  }
@@ -23279,16 +23508,16 @@ function loadContract(repoPath, contractPath) {
23279
23508
  if (metadata.size > MAX_CONTRACT_BYTES) {
23280
23509
  fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
23281
23510
  }
23282
- const realRepo = import_node_fs47.default.realpathSync(repoPath);
23283
- const realContract = import_node_fs47.default.realpathSync(absolutePath);
23511
+ const realRepo = import_node_fs48.default.realpathSync(repoPath);
23512
+ const realContract = import_node_fs48.default.realpathSync(absolutePath);
23284
23513
  assertPathWithinRepo(realContract, realRepo, "Profile plan contract");
23285
- const source = import_node_fs47.default.readFileSync(absolutePath, "utf-8");
23514
+ const source = import_node_fs48.default.readFileSync(absolutePath, "utf-8");
23286
23515
  if (Buffer.byteLength(source, "utf-8") > MAX_CONTRACT_BYTES) {
23287
23516
  fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
23288
23517
  }
23289
23518
  return {
23290
23519
  contract: parseProfilePlanContract(source),
23291
- contractPath: import_node_path58.default.relative(repoPath, absolutePath).split(import_node_path58.default.sep).join("/")
23520
+ contractPath: import_node_path59.default.relative(repoPath, absolutePath).split(import_node_path59.default.sep).join("/")
23292
23521
  };
23293
23522
  }
23294
23523
  function resolveProfilePlan(options) {
@@ -23302,12 +23531,12 @@ function resolveProfilePlan(options) {
23302
23531
  contractPath: loaded.contractPath
23303
23532
  });
23304
23533
  }
23305
- 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;
23534
+ var import_node_fs48, import_node_path59, import_yaml8, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
23306
23535
  var init_profile_plan = __esm({
23307
23536
  "src/core/profile-plan.ts"() {
23308
23537
  "use strict";
23309
- import_node_fs47 = __toESM(require("fs"));
23310
- import_node_path58 = __toESM(require("path"));
23538
+ import_node_fs48 = __toESM(require("fs"));
23539
+ import_node_path59 = __toESM(require("path"));
23311
23540
  import_yaml8 = require("yaml");
23312
23541
  init_paths();
23313
23542
  init_profile_resolution();
@@ -23339,7 +23568,7 @@ async function runProfileResolveCommand(options) {
23339
23568
  async function runProfilePlanCommand(options) {
23340
23569
  const report = resolveProfilePlan(options);
23341
23570
  if (options.output) {
23342
- writeFileAtomically(import_node_path59.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
23571
+ writeFileAtomically(import_node_path60.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
23343
23572
  `);
23344
23573
  }
23345
23574
  if (options.json) {
@@ -23375,14 +23604,14 @@ function printProfilePlanSummary(report, output2) {
23375
23604
  process.stdout.write(`Binding ${key}: ${renderValues(report.bindings[key] ?? [])}
23376
23605
  `);
23377
23606
  }
23378
- if (output2) process.stdout.write(`Output: ${import_node_path59.default.resolve(output2)}
23607
+ if (output2) process.stdout.write(`Output: ${import_node_path60.default.resolve(output2)}
23379
23608
  `);
23380
23609
  }
23381
- var import_node_path59;
23610
+ var import_node_path60;
23382
23611
  var init_profile = __esm({
23383
23612
  "src/commands/profile.ts"() {
23384
23613
  "use strict";
23385
- import_node_path59 = __toESM(require("path"));
23614
+ import_node_path60 = __toESM(require("path"));
23386
23615
  init_atomic_file();
23387
23616
  init_output();
23388
23617
  init_profile_plan();
@@ -23672,7 +23901,7 @@ function sanitizeRouterId(value) {
23672
23901
  return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
23673
23902
  }
23674
23903
  function repoHash(repoPath) {
23675
- return (0, import_node_crypto26.createHash)("sha1").update(import_node_path60.default.resolve(repoPath)).digest("hex").slice(0, 12);
23904
+ return (0, import_node_crypto26.createHash)("sha1").update(import_node_path61.default.resolve(repoPath)).digest("hex").slice(0, 12);
23676
23905
  }
23677
23906
  function asDockerApp(app) {
23678
23907
  return app.runtime === "docker";
@@ -23751,11 +23980,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
23751
23980
  if (dockerApps.length === 0) {
23752
23981
  throw new Error("No docker apps selected to prepare compose overlay.");
23753
23982
  }
23754
- const cachePath = import_node_path60.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
23755
- import_node_fs48.default.mkdirSync(cachePath, { recursive: true });
23756
- const overlayPath = import_node_path60.default.join(cachePath, "compose.devrouter.yml");
23983
+ const cachePath = import_node_path61.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
23984
+ import_node_fs49.default.mkdirSync(cachePath, { recursive: true });
23985
+ const overlayPath = import_node_path61.default.join(cachePath, "compose.devrouter.yml");
23757
23986
  const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
23758
- import_node_fs48.default.writeFileSync(overlayPath, import_yaml9.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
23987
+ import_node_fs49.default.writeFileSync(overlayPath, import_yaml9.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
23759
23988
  return {
23760
23989
  overlayPath,
23761
23990
  composeFiles: ensureComposeFiles(dockerApps),
@@ -23875,14 +24104,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
23875
24104
  const port = Number(match[1]);
23876
24105
  return Number.isInteger(port) && port > 0 ? port : void 0;
23877
24106
  }
23878
- var import_node_child_process33, import_node_crypto26, import_node_fs48, import_node_path60, import_yaml9;
24107
+ var import_node_child_process33, import_node_crypto26, import_node_fs49, import_node_path61, import_yaml9;
23879
24108
  var init_docker_run = __esm({
23880
24109
  "src/core/docker-run.ts"() {
23881
24110
  "use strict";
23882
24111
  import_node_child_process33 = require("child_process");
23883
24112
  import_node_crypto26 = require("crypto");
23884
- import_node_fs48 = __toESM(require("fs"));
23885
- import_node_path60 = __toESM(require("path"));
24113
+ import_node_fs49 = __toESM(require("fs"));
24114
+ import_node_path61 = __toESM(require("path"));
23886
24115
  import_yaml9 = __toESM(require("yaml"));
23887
24116
  init_docker_error_guidance();
23888
24117
  init_paths();
@@ -24704,13 +24933,13 @@ function measureWorktreeConsumption(worktreePath, options) {
24704
24933
  const startedAt = Date.now();
24705
24934
  let rootStat;
24706
24935
  try {
24707
- rootStat = import_node_fs49.default.lstatSync(worktreePath);
24936
+ rootStat = import_node_fs50.default.lstatSync(worktreePath);
24708
24937
  } catch (error) {
24709
24938
  return { status: "unknown", reason: describeError(error, worktreePath) };
24710
24939
  }
24711
24940
  let rootEntries;
24712
24941
  try {
24713
- rootEntries = import_node_fs49.default.readdirSync(worktreePath, { withFileTypes: true });
24942
+ rootEntries = import_node_fs50.default.readdirSync(worktreePath, { withFileTypes: true });
24714
24943
  } catch (error) {
24715
24944
  return { status: "unknown", reason: describeError(error, worktreePath) };
24716
24945
  }
@@ -24736,10 +24965,10 @@ function measureWorktreeConsumption(worktreePath, options) {
24736
24965
  timedOut = true;
24737
24966
  break;
24738
24967
  }
24739
- const entryPath = import_node_path61.default.join(dirPath, entry.name);
24968
+ const entryPath = import_node_path62.default.join(dirPath, entry.name);
24740
24969
  let entryStat;
24741
24970
  try {
24742
- entryStat = import_node_fs49.default.lstatSync(entryPath);
24971
+ entryStat = import_node_fs50.default.lstatSync(entryPath);
24743
24972
  } catch (error) {
24744
24973
  if (error?.code === "ENOENT") continue;
24745
24974
  unreadableReason = describeIncompleteWalk(error);
@@ -24749,7 +24978,7 @@ function measureWorktreeConsumption(worktreePath, options) {
24749
24978
  if (!entryStat.isDirectory()) continue;
24750
24979
  let childEntries;
24751
24980
  try {
24752
- childEntries = import_node_fs49.default.readdirSync(entryPath, { withFileTypes: true });
24981
+ childEntries = import_node_fs50.default.readdirSync(entryPath, { withFileTypes: true });
24753
24982
  } catch (error) {
24754
24983
  unreadableReason = describeIncompleteWalk(error);
24755
24984
  break;
@@ -24813,12 +25042,12 @@ function describeError(error, worktreePath) {
24813
25042
  }
24814
25043
  return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
24815
25044
  }
24816
- var import_node_fs49, import_node_path61, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
25045
+ var import_node_fs50, import_node_path62, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
24817
25046
  var init_workspace_consumption = __esm({
24818
25047
  "src/core/workspace-consumption.ts"() {
24819
25048
  "use strict";
24820
- import_node_fs49 = __toESM(require("fs"));
24821
- import_node_path61 = __toESM(require("path"));
25049
+ import_node_fs50 = __toESM(require("fs"));
25050
+ import_node_path62 = __toESM(require("path"));
24822
25051
  init_devpod_environment();
24823
25052
  DEFAULT_DEADLINE_MS = 1e4;
24824
25053
  BLOCK_SIZE_BYTES = 512;
@@ -24919,7 +25148,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
24919
25148
  }
24920
25149
  function readGitSnapshot(worktree, commandRunner) {
24921
25150
  const comparablePath = comparableWorkspacePath(worktree.path);
24922
- if (worktree.prunable || !import_node_fs50.default.existsSync(comparablePath)) {
25151
+ if (worktree.prunable || !import_node_fs51.default.existsSync(comparablePath)) {
24923
25152
  return { worktree, checkout: "missing", head: null, committerDate: null };
24924
25153
  }
24925
25154
  const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
@@ -25444,7 +25673,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
25444
25673
  containers2 = measureContainersFn(worktreePaths);
25445
25674
  } catch (error) {
25446
25675
  const reason = `container measurement failed: ${describeCause(error)}`;
25447
- containers2 = new Map(worktreePaths.map((path62) => [path62, unknownContainers(reason)]));
25676
+ containers2 = new Map(worktreePaths.map((path63) => [path63, unknownContainers(reason)]));
25448
25677
  }
25449
25678
  }
25450
25679
  const rows2 = records.map((record5) => {
@@ -25494,12 +25723,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
25494
25723
  networkCapacity: (dependencies.inspectNetworkCapacity ?? inspectNetworkCapacity)()
25495
25724
  };
25496
25725
  }
25497
- var import_node_child_process35, import_node_fs50, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
25726
+ var import_node_child_process35, import_node_fs51, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
25498
25727
  var init_workspace_cleanup = __esm({
25499
25728
  "src/core/workspace-cleanup.ts"() {
25500
25729
  "use strict";
25501
25730
  import_node_child_process35 = require("child_process");
25502
- import_node_fs50 = __toESM(require("fs"));
25731
+ import_node_fs51 = __toESM(require("fs"));
25503
25732
  init_devpod_workspaces();
25504
25733
  init_host_routes();
25505
25734
  init_network_diagnostics();
@@ -25611,7 +25840,7 @@ function warnMissingWorkspaceOwnership(repoPath) {
25611
25840
  );
25612
25841
  }
25613
25842
  function defaultWorktreePath(mainRepo, ws) {
25614
- return import_node_path62.default.join(mainRepo, "trees", ws);
25843
+ return import_node_path63.default.join(mainRepo, "trees", ws);
25615
25844
  }
25616
25845
  function assertDefaultWorktreeRootIgnored(mainRepo) {
25617
25846
  const ignored = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
@@ -25619,12 +25848,12 @@ function assertDefaultWorktreeRootIgnored(mainRepo) {
25619
25848
  });
25620
25849
  if (ignored.status !== 0) {
25621
25850
  throw new Error(
25622
- `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.`
25851
+ `Default worktree root '${import_node_path63.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path63.default.join(mainRepo, ".gitignore")}' or use --path.`
25623
25852
  );
25624
25853
  }
25625
25854
  }
25626
25855
  function legacyDefaultWorktreePath(mainRepo, ws) {
25627
- return import_node_path62.default.join(import_node_path62.default.dirname(mainRepo), `${import_node_path62.default.basename(mainRepo)}-${ws}`);
25856
+ return import_node_path63.default.join(import_node_path63.default.dirname(mainRepo), `${import_node_path63.default.basename(mainRepo)}-${ws}`);
25628
25857
  }
25629
25858
  function teardownFallbackPath(mainRepo, workspace) {
25630
25859
  const candidates = [
@@ -25736,7 +25965,7 @@ function assertFullDownPreflight(mainRepo, target) {
25736
25965
  if (sameWorkspacePath(target.worktreePath, mainRepo)) {
25737
25966
  throw new Error("Refusing to remove the primary Git checkout.");
25738
25967
  }
25739
- if (!target.worktree || target.worktree.prunable || !import_node_fs51.default.existsSync(target.worktreePath)) return;
25968
+ if (!target.worktree || target.worktree.prunable || !import_node_fs52.default.existsSync(target.worktreePath)) return;
25740
25969
  if (target.worktree.locked) {
25741
25970
  throw new Error(
25742
25971
  `Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
@@ -25776,9 +26005,9 @@ async function workspaceUp(branch, opts = {}) {
25776
26005
  (candidate) => defaultWorktreePath(mainRepo, candidate)
25777
26006
  );
25778
26007
  const generatedPath = candidatePaths.find(
25779
- (candidate) => !import_node_fs51.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
26008
+ (candidate) => !import_node_fs52.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
25780
26009
  );
25781
- const selectedPath = opts.path ? import_node_path62.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
26010
+ const selectedPath = opts.path ? import_node_path63.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
25782
26011
  if (!selectedPath) {
25783
26012
  throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
25784
26013
  }
@@ -25787,7 +26016,7 @@ async function workspaceUp(branch, opts = {}) {
25787
26016
  `Branch '${branch}' already uses worktree '${existingBranch.path}', not '${selectedPath}'.`
25788
26017
  );
25789
26018
  }
25790
- if (import_node_fs51.default.existsSync(selectedPath)) {
26019
+ if (import_node_fs52.default.existsSync(selectedPath)) {
25791
26020
  const registered = worktrees.find(
25792
26021
  (worktree) => sameWorkspacePath(worktree.path, selectedPath)
25793
26022
  );
@@ -25890,7 +26119,7 @@ async function mutateWorkspaceRuntime(action2, resolved, worktrees, quiet = fals
25890
26119
  );
25891
26120
  }
25892
26121
  const retained = action2 === "stop" ? readManagedRuntimeState(resolved.worktreePath, resolved.workspace) : void 0;
25893
- let absent = false;
26122
+ let absent = mutation.status === "proven-absent";
25894
26123
  const remove = () => {
25895
26124
  if (retained?.stopBaseline) {
25896
26125
  absent = proveManagedStop(retained).status === "proven-absent";
@@ -25899,6 +26128,11 @@ async function mutateWorkspaceRuntime(action2, resolved, worktrees, quiet = fals
25899
26128
  ))
25900
26129
  throw new Error("Absent stop contains routes outside the retained desired set.");
25901
26130
  }
26131
+ if (absent && !retained) {
26132
+ if (listRoutesForWorktreePath(resolved.worktreePath).length)
26133
+ throw new Error("Initial managed stop observed remaining workspace routes.");
26134
+ return [];
26135
+ }
25902
26136
  claimLifecycleEffect();
25903
26137
  return removeWorkspaceRoutesForWorktree(resolved.workspace, resolved.worktreePath);
25904
26138
  };
@@ -25934,7 +26168,8 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
25934
26168
  workspace: resolved.workspace,
25935
26169
  devpodId: stopped.devpodId,
25936
26170
  freedRoutes: stopped.freedRoutes,
25937
- providerChanged: stopped.stopped
26171
+ providerChanged: stopped.stopped && !stopped.runtimeAbsent,
26172
+ ...stopped.runtimeAbsent ? { runtimeAbsent: true } : {}
25938
26173
  };
25939
26174
  }
25940
26175
  const operation = async () => {
@@ -25944,7 +26179,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
25944
26179
  }
25945
26180
  const result = await mutateWorkspaceRuntime("delete", resolved, worktrees, opts.quiet);
25946
26181
  if (removeWorktree) {
25947
- if (resolved.worktree && !resolved.worktree.prunable && import_node_fs51.default.existsSync(resolved.worktreePath)) {
26182
+ if (resolved.worktree && !resolved.worktree.prunable && import_node_fs52.default.existsSync(resolved.worktreePath)) {
25948
26183
  const rm = (0, import_node_child_process36.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
25949
26184
  encoding: "utf-8"
25950
26185
  });
@@ -25963,7 +26198,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
25963
26198
  }
25964
26199
  return result;
25965
26200
  };
25966
- return resolved.worktree && !resolved.worktree.prunable && import_node_fs51.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
26201
+ return resolved.worktree && !resolved.worktree.prunable && import_node_fs52.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
25967
26202
  }
25968
26203
  async function workspaceStop(target, opts = {}) {
25969
26204
  return runWorkspaceLifecycle("stop", target, opts);
@@ -25971,13 +26206,13 @@ async function workspaceStop(target, opts = {}) {
25971
26206
  async function workspaceDown(target, opts = {}) {
25972
26207
  return runWorkspaceLifecycle("down", target, opts);
25973
26208
  }
25974
- var import_node_child_process36, import_node_fs51, import_node_path62, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
26209
+ var import_node_child_process36, import_node_fs52, import_node_path63, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
25975
26210
  var init_workspace_lifecycle = __esm({
25976
26211
  "src/core/workspace-lifecycle.ts"() {
25977
26212
  "use strict";
25978
26213
  import_node_child_process36 = require("child_process");
25979
- import_node_fs51 = __toESM(require("fs"));
25980
- import_node_path62 = __toESM(require("path"));
26214
+ import_node_fs52 = __toESM(require("fs"));
26215
+ import_node_path63 = __toESM(require("path"));
25981
26216
  init_devpod_mutation();
25982
26217
  init_devpod_workspaces();
25983
26218
  init_devsy_mutation();
@@ -26161,7 +26396,7 @@ var init_version = __esm({
26161
26396
 
26162
26397
  // src/cli.ts
26163
26398
  var import_commander = require("commander");
26164
- var CLI_VERSION = true ? "0.0.73" : "0.0.0-dev";
26399
+ var CLI_VERSION = true ? "0.0.74" : "0.0.0-dev";
26165
26400
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
26166
26401
  function withErrorHandling(action2) {
26167
26402
  return async (...args) => {
@@ -26534,10 +26769,10 @@ workspaceCommand2.command("down").description("Delete a workspace's runtime and
26534
26769
  workspaceCommand2.command("journal").description("Inspect and recover workspace lifecycle journals").command("settle").description(
26535
26770
  "Settle an interrupted lifecycle operation as unobservable so ensure and stop can proceed"
26536
26771
  ).argument("[path]", "Checkout path (defaults to current directory)").option("--json", "Output JSON").action(
26537
- withErrorHandling(async (path62, _options, command) => {
26772
+ withErrorHandling(async (path63, _options, command) => {
26538
26773
  const options = command.opts();
26539
26774
  const { runWorkspaceJournalSettleCommand: runWorkspaceJournalSettleCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
26540
- await runWorkspaceJournalSettleCommand2({ ...path62 ? { path: path62 } : {}, ...options });
26775
+ await runWorkspaceJournalSettleCommand2({ ...path63 ? { path: path63 } : {}, ...options });
26541
26776
  })
26542
26777
  );
26543
26778
  function parseVersionRequest(argv) {