@devrouter/cli 0.0.61 → 0.0.62

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
@@ -2450,7 +2450,7 @@ function loadRepoConfig(repoPath, read = (file) => import_node_fs6.default.readF
2450
2450
  const config = parseConfig(parsed ?? {}, configPath);
2451
2451
  const requiredVersion = config.devrouter?.version;
2452
2452
  if (requiredVersion && !hasWarnedVersionMismatch) {
2453
- const cliVersion = true ? "0.0.61" : "0.0.0-dev";
2453
+ const cliVersion = true ? "0.0.62" : "0.0.0-dev";
2454
2454
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
2455
2455
  hasWarnedVersionMismatch = true;
2456
2456
  process.stderr.write(
@@ -4558,6 +4558,19 @@ function parseManagedStopJson(output2) {
4558
4558
  throw new Error("Managed stop Docker response is malformed.");
4559
4559
  }
4560
4560
  }
4561
+ function resolveManagedStopEndpoint() {
4562
+ const endpoint = process.env.DOCKER_HOST && !process.env.DOCKER_CONTEXT ? process.env.DOCKER_HOST : parseManagedStopJson(
4563
+ runManagedStopDocker([
4564
+ "context",
4565
+ "inspect",
4566
+ "--format",
4567
+ "{{json .Endpoints.docker.Host}}"
4568
+ ])
4569
+ );
4570
+ if (typeof endpoint !== "string") throw new Error("Docker endpoint is unavailable.");
4571
+ supportsManagedStopBaseline(endpoint);
4572
+ return endpoint;
4573
+ }
4561
4574
  function inspectManagedStopDaemon(endpoint) {
4562
4575
  const value = parseManagedStopJson(
4563
4576
  runManagedStopDocker(["info", "--format", "{{json .ID}}"], endpoint)
@@ -4579,6 +4592,28 @@ function inspectManagedStopRunnerId(endpoint, containerId) {
4579
4592
  throw new Error("Provider container binding is unavailable.");
4580
4593
  return value;
4581
4594
  }
4595
+ function inspectProviderRunnerContainers(endpoint, runnerId) {
4596
+ if (!runnerId || runnerId.length > 4096 || /[\r\n\0]/.test(runnerId))
4597
+ throw new Error("Provider runner identity is malformed.");
4598
+ const ids = parseDockerLines(
4599
+ runManagedStopDocker(
4600
+ [
4601
+ "ps",
4602
+ "-a",
4603
+ "--no-trunc",
4604
+ "--filter",
4605
+ `label=dev.containers.id=${runnerId}`,
4606
+ "--format",
4607
+ "{{.ID}}"
4608
+ ],
4609
+ endpoint
4610
+ )
4611
+ );
4612
+ if (ids.length > 256) throw new Error("Provider runner population exceeds its bound.");
4613
+ ids.forEach(assertFullContainerId);
4614
+ assertUniqueContainerIds(ids);
4615
+ return ids;
4616
+ }
4582
4617
  function inspectManagedStopWorkspaceIds(endpoint, composeDirectory) {
4583
4618
  const ids = parseDockerLines(
4584
4619
  runManagedStopDocker(
@@ -7106,6 +7141,7 @@ function buildOnboardingPrompt(options = {}) {
7106
7141
  `- When a workspace is active: hosts auto-namespace (\`web.localhost\` \u2192 \`web.<ws>.localhost\`), \`${WORKSPACE_PLACEHOLDER}\` in \`upstream\` is substituted with the token, and the docker \`router\` key is suffixed per workspace. Managed ensure rejects every HTTP/TCP upstream outside that exact alias namespace before mutation. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.`,
7107
7142
  "- TLS: namespaced hosts (`web.<ws>.localhost`) are not covered by the `*.localhost` wildcard; devrouter auto-extends the mkcert cert SANs for active hosts when TLS is enabled.",
7108
7143
  "- Lifecycle: after one-time setup, use `devrouter ensure .` for both primary and linked checkouts; never branch on checkout kind or use live verify as startup. Managed consumer images contain no devrouter package/helper: ensure delivers its matching helper at runtime and invokes an exact captured snapshot of the repository-owned post-start adapter. Keep `.devrouter.yml` as the only consumer-side version pin. Use `devrouter stop .` for a non-destructive pause, `devrouter stop . --delete` only for explicit exact-owner cleanup without removing the checkout, and `devrouter exec . -- <command...>` for container commands. Never substitute raw DevPod/Devsy mutations; they bypass the machine-global ownership lock, which serializes provider mutations in a fair arrival-order queue and lets contenders wait up to thirty minutes with throttled stderr progress before failing with the queue position or holder PID and true durations. `workspace up` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped.",
7144
+ "- After a drained interrupted ensure, manual exec can use an exactly proven retained Devsy runtime with the plain local Docker command. Revalidate identity for each tooling command until later preparation reconciles startup; preserve configuration, data and interrupted history. This does not repair absent stop baselines or provide OOM protection.",
7109
7145
  "- Ensure reconciles interrupted or never-dispatched ensure after positive worker drainage. Unknown arbitrary exec never replays; use explicit stop to reconcile that uncertainty. Preserve journals and generated configuration if evidence is unavailable. This does not prevent OOM or activate capacity-managed recovery.",
7110
7146
  "- Devsy agent readiness: run `devrouter setup --yes --workspace-runtime devsy` once so Devrouter acquires and verifies the pinned agent in its own machine cache. `doctor` reports `ready`, `missing`, `stale`, or `invalid` without network access, and `ensure` fails before the provider queue when readiness is not `ready`. An explicit `DEVSY_AGENT_BINARY` remains authoritative and must match a pinned official asset.",
7111
7147
  "- A managed adapter plus `postCreateCommand` requires `waitFor` exactly `postCreateCommand` or `postStartCommand`; managed selective config preserves lifecycle fields and changes only `runServices`.",
@@ -9634,6 +9670,10 @@ function listDevsyWorkspaces() {
9634
9670
  ...typeof candidate.uid === "string" ? { uid: candidate.uid } : {},
9635
9671
  ...typeof candidate.context === "string" ? { context: candidate.context } : {}
9636
9672
  };
9673
+ const provider = candidate.provider;
9674
+ if (provider && typeof provider.name === "string") workspace.providerName = provider.name;
9675
+ if (typeof provider?.options?.DOCKER_PATH?.value === "string")
9676
+ workspace.dockerPathOption = provider.options.DOCKER_PATH.value;
9637
9677
  if ("lastUsed" in candidate) {
9638
9678
  if (typeof candidate.lastUsed === "string") {
9639
9679
  workspace.lastUsed = candidate.lastUsed;
@@ -9691,6 +9731,16 @@ function inspectDevsyWorkspaceOwnership(workspaces, devsyId, worktreePath) {
9691
9731
  }
9692
9732
  return exact[0] ? { status: "owned", workspace: exact[0] } : { status: "absent" };
9693
9733
  }
9734
+ function selectDevsyWorkspace(workspaces, repoPath) {
9735
+ const matches = workspaces.filter(
9736
+ (workspace) => sameWorkspacePath(workspace.source.localFolder, repoPath)
9737
+ );
9738
+ if (matches.length > 1) {
9739
+ const ids = matches.map((match) => match.id).join(", ");
9740
+ throw new Error(`Multiple Devsy workspaces reference '${repoPath}': ${ids}`);
9741
+ }
9742
+ return matches[0];
9743
+ }
9694
9744
  var import_node_child_process13;
9695
9745
  var init_devsy_workspaces = __esm({
9696
9746
  "src/core/devsy-workspaces.ts"() {
@@ -12018,7 +12068,7 @@ async function buildDoctorReport(options = {}) {
12018
12068
  const config = runtimeConfig.config;
12019
12069
  loadedConfig = config;
12020
12070
  loadedWorkspace = runtimeConfig.workspace;
12021
- const cliVersion = true ? "0.0.61" : "0.0.0-dev";
12071
+ const cliVersion = true ? "0.0.62" : "0.0.0-dev";
12022
12072
  const configVersion = config.devrouter?.version;
12023
12073
  if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
12024
12074
  addCheck(checks, {
@@ -12651,6 +12701,96 @@ var init_up = __esm({
12651
12701
  }
12652
12702
  });
12653
12703
 
12704
+ // src/core/devsy-exec-proof.ts
12705
+ function proveLocalDockerSelection(workspace) {
12706
+ const name = workspace.providerName;
12707
+ const context = workspace.context;
12708
+ if (!name || !context || !/^[a-z0-9][a-z0-9-]{0,31}$/.test(name) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(context))
12709
+ throw new Error("Retained exec provider configuration identity is unavailable.");
12710
+ const file = import_node_path37.default.join(
12711
+ process.env.DEVSY_HOME || import_node_path37.default.join(import_node_os4.default.homedir(), ".devsy"),
12712
+ "contexts",
12713
+ context,
12714
+ "providers",
12715
+ name,
12716
+ "provider.json"
12717
+ );
12718
+ try {
12719
+ if (import_node_fs30.default.statSync(file).size > 1024 * 1024) throw new Error();
12720
+ const config = JSON.parse(import_node_fs30.default.readFileSync(file, "utf8"));
12721
+ const driver = config.agent?.driver ?? "docker";
12722
+ const configuredPath = config.agent?.docker?.path ?? "";
12723
+ if (typeof configuredPath !== "string") throw new Error();
12724
+ const command = configuredPath === "${DOCKER_PATH}" || configuredPath === "$DOCKER_PATH" ? workspace.dockerPathOption || process.env.DOCKER_PATH || "docker" : configuredPath || "docker";
12725
+ if (config.name !== name || driver !== "docker" || command !== "docker") throw new Error();
12726
+ } catch {
12727
+ throw new Error("Retained exec requires a provider configured for the local Docker command.");
12728
+ }
12729
+ return name;
12730
+ }
12731
+ function captureDevsyExecProof(repoPath) {
12732
+ repoPath = comparableWorkspacePath(repoPath);
12733
+ const registry = listDevsyWorkspaces();
12734
+ const workspace = selectDevsyWorkspace(registry, repoPath);
12735
+ if (!workspace?.uid || !workspace.context || workspace.source.container || inspectDevsyWorkspaceOwnership(registry, workspace.id, repoPath).status !== "owned" || inspectDevsyRuntimeStatus(workspace.id) !== "running")
12736
+ throw new Error("Retained exec requires one exact running Devsy registration.");
12737
+ const providerName = proveLocalDockerSelection(workspace);
12738
+ const endpoint = resolveManagedStopEndpoint();
12739
+ if (!supportsManagedStopBaseline(endpoint))
12740
+ throw new Error("Retained exec requires an exact local Docker endpoint.");
12741
+ const daemon = inspectManagedStopDaemon(endpoint);
12742
+ const candidates = workspaceAppContainers(inspectWorkspaceContainers(), repoPath).filter(
12743
+ (container) => container.state.Running
12744
+ );
12745
+ if (candidates.length !== 1)
12746
+ throw new Error("Retained exec requires one exact running primary container.");
12747
+ const candidate = candidates[0];
12748
+ const project = candidate.labels["com.docker.compose.project"];
12749
+ if (!project) throw new Error("Retained exec primary has no Compose identity.");
12750
+ const pinned = workspaceAppContainers(
12751
+ inspectManagedStopContainers(project, endpoint),
12752
+ repoPath
12753
+ ).filter((container) => container.state.Running);
12754
+ if (pinned.length !== 1 || pinned[0].id !== candidate.id)
12755
+ throw new Error("Retained exec primary changed during inspection.");
12756
+ const mounts = pinned[0].mounts.filter(
12757
+ (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
12758
+ );
12759
+ if (mounts.length !== 1 || !mounts[0].Destination.startsWith("/"))
12760
+ throw new Error("Retained exec requires one exact source mount.");
12761
+ const uidBytes = Buffer.byteLength(workspace.uid);
12762
+ const runner = uidBytes === 16 || uidBytes === 40 ? workspace.uid : workspace.id;
12763
+ const runnerContainers = inspectProviderRunnerContainers(endpoint, runner);
12764
+ if (runnerContainers.length !== 1 || runnerContainers[0] !== candidate.id)
12765
+ throw new Error("Retained exec runner does not select the exact primary container.");
12766
+ if (inspectManagedStopRunnerId(endpoint, candidate.id) !== runner || inspectManagedStopDaemon(endpoint) !== daemon)
12767
+ throw new Error("Retained exec provider or Docker identity changed.");
12768
+ return {
12769
+ repoPath,
12770
+ id: workspace.id,
12771
+ uid: workspace.uid,
12772
+ context: workspace.context,
12773
+ providerName,
12774
+ endpoint,
12775
+ daemon,
12776
+ containerId: candidate.id,
12777
+ workspacePath: mounts[0].Destination
12778
+ };
12779
+ }
12780
+ var import_node_fs30, import_node_os4, import_node_path37, import_node_util3;
12781
+ var init_devsy_exec_proof = __esm({
12782
+ "src/core/devsy-exec-proof.ts"() {
12783
+ "use strict";
12784
+ import_node_fs30 = __toESM(require("fs"));
12785
+ import_node_os4 = __toESM(require("os"));
12786
+ import_node_path37 = __toESM(require("path"));
12787
+ import_node_util3 = require("util");
12788
+ init_devpod_environment();
12789
+ init_devsy_workspaces();
12790
+ init_workspace();
12791
+ }
12792
+ });
12793
+
12654
12794
  // src/core/reliability-model.ts
12655
12795
  function record3(value) {
12656
12796
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -12680,7 +12820,7 @@ function assertReliabilityEvent(value) {
12680
12820
  valid = oneOf2(value.mode, ["start", "attach"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer);
12681
12821
  break;
12682
12822
  case "operation-request":
12683
- valid = oneOf2(value.kind, ["ensure", "exec"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer) && typeof value.runtimeRunning === "boolean";
12823
+ valid = oneOf2(value.kind, ["ensure", "exec"]) && isReliabilityId(value.key) && isReliabilityId(value.operationId) && isReliabilityProfile(value.profile) && isConsumer(value.consumer) && typeof value.runtimeRunning === "boolean" && (value.recoverInterruptedEnsure === void 0 || typeof value.recoverInterruptedEnsure === "boolean");
12684
12824
  break;
12685
12825
  case "drained":
12686
12826
  valid = isReliabilityId(value.operationId);
@@ -12863,6 +13003,10 @@ function handleRequest(state, event) {
12863
13003
  });
12864
13004
  return transition(state, "accepted");
12865
13005
  }
13006
+ function canExecAfterInterruptedEnsure(state) {
13007
+ const latestEnsure = [...state.operationHistory].reverse().find((operation) => operation.kind === "ensure");
13008
+ return state.executionPolicy === "manual" && state.desired === "running" && latestEnsure?.status === "INTERRUPTED" && latestEnsure.drained && state.operation?.drained === true && (state.operation.kind === "ensure" && state.phase === "recovering" || state.operation.kind === "exec" && ["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status));
13009
+ }
12866
13010
  function handleOperationRequest(state, event) {
12867
13011
  if (state.executionPolicy !== "manual") return unchanged(state, "blocked");
12868
13012
  const previous = state.operationHistory.find((entry) => entry.key === event.key);
@@ -12873,9 +13017,11 @@ function handleOperationRequest(state, event) {
12873
13017
  );
12874
13018
  if (state.operationHistory.some((entry) => entry.id === event.operationId))
12875
13019
  return unchanged(state, "conflict");
13020
+ if (event.kind === "exec" && canExecAfterInterruptedEnsure(state) && event.recoverInterruptedEnsure !== true)
13021
+ return unchanged(state, "blocked");
12876
13022
  const fullyStopped = state.stopProof.workloadsStopped && state.stopProof.routesRemoved;
12877
13023
  const reconcileEnsure = event.kind === "ensure" && state.operation?.kind === "ensure" && ["NOT_STARTED", "INTERRUPTED"].includes(state.operation.status);
12878
- if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure))
13024
+ if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED", "NOT_STARTED"].includes(state.operation.status) && !fullyStopped && !reconcileEnsure && !(event.kind === "exec" && event.recoverInterruptedEnsure === true && canExecAfterInterruptedEnsure(state))))
12879
13025
  return unchanged(state, "blocked");
12880
13026
  if (state.phase === "stopping" || state.desired === "parked-for-capacity")
12881
13027
  return unchanged(state, "blocked");
@@ -12886,8 +13032,9 @@ function handleOperationRequest(state, event) {
12886
13032
  if (rollover) {
12887
13033
  if (state.operation && (!state.operation.drained || !["COMPLETED", "NOT_LAUNCHED"].includes(state.operation.status)))
12888
13034
  return unchanged(state, "blocked");
13035
+ const latestEnsureId = [...state.operationHistory].reverse().find((entry) => entry.kind === "ensure")?.id;
12889
13036
  retired = state.operationHistory.findIndex(
12890
- (entry) => entry.id !== state.operation?.id && entry.drained && ["COMPLETED", "NOT_LAUNCHED"].includes(entry.status)
13037
+ (entry) => entry.id !== state.operation?.id && entry.id !== latestEnsureId && entry.drained && ["COMPLETED", "NOT_LAUNCHED"].includes(entry.status)
12891
13038
  );
12892
13039
  if (retired === -1) return unchanged(state, "blocked");
12893
13040
  }
@@ -13289,8 +13436,8 @@ function signalOwnedGroup(child, signal) {
13289
13436
  async function runLifecycleWorker(request) {
13290
13437
  if (process.platform === "win32")
13291
13438
  throw new Error("Lifecycle workers require POSIX process-group ownership.");
13292
- const workerPath = import_node_path37.default.join(__dirname, "devrouter-lifecycle-worker.js");
13293
- if (!import_node_fs30.default.existsSync(workerPath))
13439
+ const workerPath = import_node_path38.default.join(__dirname, "devrouter-lifecycle-worker.js");
13440
+ if (!import_node_fs31.default.existsSync(workerPath))
13294
13441
  throw new Error("The packaged lifecycle worker is missing; rebuild the CLI.");
13295
13442
  const child = (0, import_node_child_process21.fork)(workerPath, [], {
13296
13443
  detached: true,
@@ -13448,14 +13595,14 @@ async function runLifecycleWorker(request) {
13448
13595
  function newLifecycleIds() {
13449
13596
  return { requestId: (0, import_node_crypto16.randomUUID)(), operationId: (0, import_node_crypto16.randomUUID)(), workerId: (0, import_node_crypto16.randomUUID)() };
13450
13597
  }
13451
- var import_node_child_process21, import_node_crypto16, import_node_fs30, import_node_path37;
13598
+ var import_node_child_process21, import_node_crypto16, import_node_fs31, import_node_path38;
13452
13599
  var init_reliability_worker = __esm({
13453
13600
  "src/core/reliability-worker.ts"() {
13454
13601
  "use strict";
13455
13602
  import_node_child_process21 = require("child_process");
13456
13603
  import_node_crypto16 = require("crypto");
13457
- import_node_fs30 = __toESM(require("fs"));
13458
- import_node_path37 = __toESM(require("path"));
13604
+ import_node_fs31 = __toESM(require("fs"));
13605
+ import_node_path38 = __toESM(require("path"));
13459
13606
  init_file_lock();
13460
13607
  init_reliability_contract();
13461
13608
  init_reliability_model();
@@ -13464,12 +13611,12 @@ var init_reliability_worker = __esm({
13464
13611
  });
13465
13612
 
13466
13613
  // src/core/managed-host-preparation.ts
13467
- var import_node_child_process22, import_node_fs31;
13614
+ var import_node_child_process22, import_node_fs32;
13468
13615
  var init_managed_host_preparation = __esm({
13469
13616
  "src/core/managed-host-preparation.ts"() {
13470
13617
  "use strict";
13471
13618
  import_node_child_process22 = require("child_process");
13472
- import_node_fs31 = __toESM(require("fs"));
13619
+ import_node_fs32 = __toESM(require("fs"));
13473
13620
  init_repo_config();
13474
13621
  }
13475
13622
  });
@@ -13578,7 +13725,7 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
13578
13725
  throw new Error(
13579
13726
  "Traefik routes did not reload; shared router restart is disabled for this operation."
13580
13727
  );
13581
- import_node_fs32.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
13728
+ import_node_fs33.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
13582
13729
  return withFileLock(
13583
13730
  ROUTER_RELOAD_LOCK_FILE,
13584
13731
  {
@@ -13609,19 +13756,19 @@ async function ensureTraefikRouteExpectation(routes, expectation, options) {
13609
13756
  async function ensureTraefikRoutesRemoved(routes, options = {}) {
13610
13757
  return ensureTraefikRouteExpectation(routes, "removed", options);
13611
13758
  }
13612
- var import_node_child_process23, import_node_fs32, import_node_path38, 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;
13759
+ var import_node_child_process23, import_node_fs33, import_node_path39, 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;
13613
13760
  var init_traefik_route_health = __esm({
13614
13761
  "src/core/traefik-route-health.ts"() {
13615
13762
  "use strict";
13616
13763
  import_node_child_process23 = require("child_process");
13617
- import_node_fs32 = __toESM(require("fs"));
13618
- import_node_path38 = __toESM(require("path"));
13764
+ import_node_fs33 = __toESM(require("fs"));
13765
+ import_node_path39 = __toESM(require("path"));
13619
13766
  init_file_lock();
13620
13767
  init_host_routes();
13621
13768
  init_reliability_context();
13622
13769
  init_router();
13623
13770
  ROUTER_API_BASE = "http://127.0.0.1:8080/api";
13624
- ROUTER_RELOAD_LOCK_FILE = import_node_path38.default.join(DEVROUTER_HOME, "router-reload.lock");
13771
+ ROUTER_RELOAD_LOCK_FILE = import_node_path39.default.join(DEVROUTER_HOME, "router-reload.lock");
13625
13772
  ROUTER_API_REQUEST_TIMEOUT_SECONDS = "2";
13626
13773
  DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
13627
13774
  DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
@@ -13660,13 +13807,13 @@ function resolveLinkedTarget(repoPath) {
13660
13807
  gitCommonDir: resolveGitCommonDir(repoPath)
13661
13808
  };
13662
13809
  }
13663
- var import_node_child_process24, import_node_fs33, import_node_path39;
13810
+ var import_node_child_process24, import_node_fs34, import_node_path40;
13664
13811
  var init_workspace_ensure = __esm({
13665
13812
  "src/core/workspace-ensure.ts"() {
13666
13813
  "use strict";
13667
13814
  import_node_child_process24 = require("child_process");
13668
- import_node_fs33 = __toESM(require("fs"));
13669
- import_node_path39 = __toESM(require("path"));
13815
+ import_node_fs34 = __toESM(require("fs"));
13816
+ import_node_path40 = __toESM(require("path"));
13670
13817
  init_devcontainer_profile();
13671
13818
  init_devpod_environment();
13672
13819
  init_devpod_mutation();
@@ -13753,8 +13900,22 @@ async function superviseLifecycle(kind, repoPath, options = {}, command) {
13753
13900
  const ids = newLifecycleIds();
13754
13901
  let fence;
13755
13902
  const runtimeRunning = kind === "exec" ? Boolean(resolveRunningWorkspaceContainer(repoPath)) : false;
13903
+ const previous = kind === "exec" && identity2.provider === "devsy" ? updateReliabilityOperation(identity2, (record4) => {
13904
+ reconcileDrained(record4);
13905
+ return record4;
13906
+ }) : void 0;
13907
+ let retainedExecProof;
13908
+ if (previous && !previous.worker && canExecAfterInterruptedEnsure(previous.state)) {
13909
+ retainedExecProof = withMutationLock(
13910
+ "Prove retained exec",
13911
+ repoPath,
13912
+ () => captureDevsyExecProof(repoPath)
13913
+ );
13914
+ }
13756
13915
  updateReliabilityOperation(identity2, (record4) => {
13757
13916
  reconcileDrained(record4);
13917
+ if (retainedExecProof && record4.state.operation?.id !== previous?.state.operation?.id)
13918
+ throw new Error("Lifecycle operation changed during retained exec proof.");
13758
13919
  if (kind === "stop") {
13759
13920
  stepRecord(record4, { ...reliabilityFence(record4.state), type: "stop" });
13760
13921
  } else {
@@ -13771,7 +13932,8 @@ async function superviseLifecycle(kind, repoPath, options = {}, command) {
13771
13932
  operationId: ids.operationId,
13772
13933
  profile: options.profile ?? record4.state.profile ?? "full",
13773
13934
  consumer: { id: "manual-cli", requiredCapabilities: [], pinned: false },
13774
- runtimeRunning
13935
+ runtimeRunning,
13936
+ ...retainedExecProof ? { recoverInterruptedEnsure: true } : {}
13775
13937
  });
13776
13938
  }
13777
13939
  fence = reliabilityFence(record4.state);
@@ -13783,15 +13945,17 @@ async function superviseLifecycle(kind, repoPath, options = {}, command) {
13783
13945
  ...ids,
13784
13946
  fence,
13785
13947
  options,
13786
- ...command ? { command } : {}
13948
+ ...command ? { command } : {},
13949
+ ...retainedExecProof ? { retainedExecProof } : {}
13787
13950
  });
13788
13951
  }
13789
- var import_node_path40, activeWorker, lockHeld;
13952
+ var import_node_path41, activeWorker, lockHeld;
13790
13953
  var init_reliability_lifecycle = __esm({
13791
13954
  "src/core/reliability-lifecycle.ts"() {
13792
13955
  "use strict";
13793
- import_node_path40 = __toESM(require("path"));
13956
+ import_node_path41 = __toESM(require("path"));
13794
13957
  init_devpod_environment();
13958
+ init_devsy_exec_proof();
13795
13959
  init_devsy_mutation();
13796
13960
  init_file_lock();
13797
13961
  init_host_routes();
@@ -14200,17 +14364,17 @@ function asRecord3(value) {
14200
14364
  return value;
14201
14365
  }
14202
14366
  function readJson(filePath) {
14203
- if (!import_node_fs34.default.existsSync(filePath)) {
14367
+ if (!import_node_fs35.default.existsSync(filePath)) {
14204
14368
  return void 0;
14205
14369
  }
14206
14370
  try {
14207
- return JSON.parse(import_node_fs34.default.readFileSync(filePath, "utf-8"));
14371
+ return JSON.parse(import_node_fs35.default.readFileSync(filePath, "utf-8"));
14208
14372
  } catch {
14209
14373
  return void 0;
14210
14374
  }
14211
14375
  }
14212
14376
  function relative(repoPath, filePath) {
14213
- return import_node_path41.default.relative(repoPath, filePath) || ".";
14377
+ return import_node_path42.default.relative(repoPath, filePath) || ".";
14214
14378
  }
14215
14379
  function redactEnvAssignments(value) {
14216
14380
  return value.replace(
@@ -14251,7 +14415,7 @@ function inspectPackageManager(repoPath, pkg) {
14251
14415
  ["bun.lock", "bun"]
14252
14416
  ];
14253
14417
  for (const [fileName, name] of lockfiles) {
14254
- if (import_node_fs34.default.existsSync(import_node_path41.default.join(repoPath, fileName))) {
14418
+ if (import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName))) {
14255
14419
  return { name, source: fileName };
14256
14420
  }
14257
14421
  }
@@ -14266,9 +14430,9 @@ function inspectNode(repoPath, pkg) {
14266
14430
  if (typeof engines?.node === "string") {
14267
14431
  return { version: engines.node, source: "package.json:engines.node" };
14268
14432
  }
14269
- const nvmrc = import_node_path41.default.join(repoPath, ".nvmrc");
14270
- if (import_node_fs34.default.existsSync(nvmrc)) {
14271
- const version = import_node_fs34.default.readFileSync(nvmrc, "utf-8").trim();
14433
+ const nvmrc = import_node_path42.default.join(repoPath, ".nvmrc");
14434
+ if (import_node_fs35.default.existsSync(nvmrc)) {
14435
+ const version = import_node_fs35.default.readFileSync(nvmrc, "utf-8").trim();
14272
14436
  return { version, source: ".nvmrc" };
14273
14437
  }
14274
14438
  return void 0;
@@ -14329,7 +14493,7 @@ function configuredComposeFiles(repoPath) {
14329
14493
  const files = config.apps.filter(
14330
14494
  (app) => app.runtime === "docker"
14331
14495
  ).flatMap((app) => app.docker.composeFiles).filter(
14332
- (fileName) => !import_node_path41.default.isAbsolute(fileName) && !import_node_path41.default.normalize(fileName).startsWith("..")
14496
+ (fileName) => !import_node_path42.default.isAbsolute(fileName) && !import_node_path42.default.normalize(fileName).startsWith("..")
14333
14497
  );
14334
14498
  return Array.from(new Set(files));
14335
14499
  } catch {
@@ -14347,7 +14511,7 @@ function composeFiles(repoPath) {
14347
14511
  ...configuredComposeFiles(repoPath)
14348
14512
  ];
14349
14513
  return Array.from(new Set(candidates)).filter(
14350
- (fileName) => import_node_fs34.default.existsSync(import_node_path41.default.join(repoPath, fileName))
14514
+ (fileName) => import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName))
14351
14515
  );
14352
14516
  }
14353
14517
  function stringArray2(value) {
@@ -14385,7 +14549,7 @@ function inspectServices(repoPath) {
14385
14549
  const services = [];
14386
14550
  for (const fileName of composeFiles(repoPath)) {
14387
14551
  try {
14388
- const parsed = import_yaml6.default.parse(import_node_fs34.default.readFileSync(import_node_path41.default.join(repoPath, fileName), "utf-8"));
14552
+ const parsed = import_yaml6.default.parse(import_node_fs35.default.readFileSync(import_node_path42.default.join(repoPath, fileName), "utf-8"));
14389
14553
  const serviceMap = asRecord3(asRecord3(parsed)?.services);
14390
14554
  for (const [name, value] of Object.entries(serviceMap ?? {})) {
14391
14555
  const service = asRecord3(value);
@@ -14420,8 +14584,8 @@ function inspectServices(repoPath) {
14420
14584
  return services;
14421
14585
  }
14422
14586
  function inspectEnvFiles(repoPath) {
14423
- const files = import_node_fs34.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
14424
- const content = import_node_fs34.default.readFileSync(import_node_path41.default.join(repoPath, fileName), "utf-8");
14587
+ const files = import_node_fs35.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
14588
+ const content = import_node_fs35.default.readFileSync(import_node_path42.default.join(repoPath, fileName), "utf-8");
14425
14589
  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();
14426
14590
  return { path: fileName, names };
14427
14591
  });
@@ -14435,18 +14599,18 @@ function inspectEnvFiles(repoPath) {
14435
14599
  };
14436
14600
  }
14437
14601
  function inspectDevcontainer(repoPath) {
14438
- const dir = import_node_path41.default.join(repoPath, ".devcontainer");
14439
- if (!import_node_fs34.default.existsSync(dir)) {
14602
+ const dir = import_node_path42.default.join(repoPath, ".devcontainer");
14603
+ if (!import_node_fs35.default.existsSync(dir)) {
14440
14604
  return { exists: false, files: [] };
14441
14605
  }
14442
14606
  return {
14443
14607
  exists: true,
14444
- files: import_node_fs34.default.readdirSync(dir).filter((fileName) => import_node_fs34.default.statSync(import_node_path41.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
14608
+ files: import_node_fs35.default.readdirSync(dir).filter((fileName) => import_node_fs35.default.statSync(import_node_path42.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
14445
14609
  };
14446
14610
  }
14447
14611
  function inspectDevrouter(repoPath) {
14448
14612
  const configPath = getRepoConfigPath(repoPath);
14449
- if (!import_node_fs34.default.existsSync(configPath)) {
14613
+ if (!import_node_fs35.default.existsSync(configPath)) {
14450
14614
  return {
14451
14615
  exists: false,
14452
14616
  configPath,
@@ -14490,15 +14654,15 @@ function inspectAgentGuidance(repoPath) {
14490
14654
  ["AGENTS.md", "agents"],
14491
14655
  ["CLAUDE.md", "claude"]
14492
14656
  ]) {
14493
- if (import_node_fs34.default.existsSync(import_node_path41.default.join(repoPath, fileName))) {
14657
+ if (import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName))) {
14494
14658
  results.push({ path: fileName, kind });
14495
14659
  }
14496
14660
  }
14497
- const skillsDir = import_node_path41.default.join(repoPath, ".agents", "skills");
14498
- if (import_node_fs34.default.existsSync(skillsDir)) {
14499
- for (const name of import_node_fs34.default.readdirSync(skillsDir).sort()) {
14500
- const skillPath = import_node_path41.default.join(skillsDir, name, "SKILL.md");
14501
- if (import_node_fs34.default.existsSync(skillPath)) {
14661
+ const skillsDir = import_node_path42.default.join(repoPath, ".agents", "skills");
14662
+ if (import_node_fs35.default.existsSync(skillsDir)) {
14663
+ for (const name of import_node_fs35.default.readdirSync(skillsDir).sort()) {
14664
+ const skillPath = import_node_path42.default.join(skillsDir, name, "SKILL.md");
14665
+ if (import_node_fs35.default.existsSync(skillPath)) {
14502
14666
  results.push({ path: relative(repoPath, skillPath), kind: "skill" });
14503
14667
  }
14504
14668
  }
@@ -14541,7 +14705,7 @@ function buildIssues(report) {
14541
14705
  }
14542
14706
  function inspectRepo(options = {}) {
14543
14707
  const repoPath = resolveRepoPath(options.repo);
14544
- const pkg = readJson(import_node_path41.default.join(repoPath, "package.json"));
14708
+ const pkg = readJson(import_node_path42.default.join(repoPath, "package.json"));
14545
14709
  const scripts = inspectScripts(pkg);
14546
14710
  const reportWithoutIssues = {
14547
14711
  repoPath,
@@ -14560,12 +14724,12 @@ function inspectRepo(options = {}) {
14560
14724
  issues: buildIssues(reportWithoutIssues)
14561
14725
  };
14562
14726
  }
14563
- var import_node_fs34, import_node_path41, import_yaml6;
14727
+ var import_node_fs35, import_node_path42, import_yaml6;
14564
14728
  var init_repo_inspect = __esm({
14565
14729
  "src/core/repo-inspect.ts"() {
14566
14730
  "use strict";
14567
- import_node_fs34 = __toESM(require("fs"));
14568
- import_node_path41 = __toESM(require("path"));
14731
+ import_node_fs35 = __toESM(require("fs"));
14732
+ import_node_path42 = __toESM(require("path"));
14569
14733
  import_yaml6 = __toESM(require("yaml"));
14570
14734
  init_repo_config();
14571
14735
  }
@@ -14670,7 +14834,7 @@ function requiredFileChecks(repoPath) {
14670
14834
  ".devcontainer/docker-compose.yml",
14671
14835
  ".devrouter.yml"
14672
14836
  ];
14673
- const missing = required.filter((fileName) => !import_node_fs35.default.existsSync(import_node_path42.default.join(repoPath, fileName)));
14837
+ const missing = required.filter((fileName) => !import_node_fs36.default.existsSync(import_node_path43.default.join(repoPath, fileName)));
14674
14838
  return {
14675
14839
  id: "repo.devcontainer.verify-files",
14676
14840
  level: missing.length === 0 ? "ok" : "error",
@@ -14895,12 +15059,12 @@ async function verifyDevcontainer(options = {}) {
14895
15059
  nextSteps: collectNextSteps3(checks)
14896
15060
  };
14897
15061
  }
14898
- var import_node_fs35, import_node_path42;
15062
+ var import_node_fs36, import_node_path43;
14899
15063
  var init_devcontainer_verify = __esm({
14900
15064
  "src/core/devcontainer-verify.ts"() {
14901
15065
  "use strict";
14902
- import_node_fs35 = __toESM(require("fs"));
14903
- import_node_path42 = __toESM(require("path"));
15066
+ import_node_fs36 = __toESM(require("fs"));
15067
+ import_node_path43 = __toESM(require("path"));
14904
15068
  init_capabilities();
14905
15069
  init_doctor();
14906
15070
  init_host_routes();
@@ -15197,7 +15361,7 @@ function packageManagerIssues(repo) {
15197
15361
  }
15198
15362
  function plannedFiles(repoPath, version) {
15199
15363
  const repo = inspectRepo({ repo: repoPath });
15200
- const projectName = sanitizeProjectName(import_node_path43.default.basename(repo.repoPath));
15364
+ const projectName = sanitizeProjectName(import_node_path44.default.basename(repo.repoPath));
15201
15365
  const nodeMajor = majorVersion(repo.node?.version, "24");
15202
15366
  const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
15203
15367
  const port = inferPort2(repo);
@@ -15242,8 +15406,8 @@ function plannedFiles(repoPath, version) {
15242
15406
  };
15243
15407
  }
15244
15408
  function classifyFile(repoPath, file) {
15245
- const absolutePath = import_node_path43.default.join(repoPath, file.relativePath);
15246
- if (!import_node_fs36.default.existsSync(absolutePath)) {
15409
+ const absolutePath = import_node_path44.default.join(repoPath, file.relativePath);
15410
+ if (!import_node_fs37.default.existsSync(absolutePath)) {
15247
15411
  return {
15248
15412
  path: file.relativePath,
15249
15413
  action: "create",
@@ -15251,7 +15415,7 @@ function classifyFile(repoPath, file) {
15251
15415
  bytes: Buffer.byteLength(file.content)
15252
15416
  };
15253
15417
  }
15254
- const current = import_node_fs36.default.readFileSync(absolutePath, "utf-8");
15418
+ const current = import_node_fs37.default.readFileSync(absolutePath, "utf-8");
15255
15419
  if (!current.includes(MANAGED_MARKER2)) {
15256
15420
  return {
15257
15421
  path: file.relativePath,
@@ -15308,11 +15472,11 @@ function buildPlan(repoPath, dryRun, version) {
15308
15472
  };
15309
15473
  }
15310
15474
  function writeFile(repoPath, file) {
15311
- const absolutePath = import_node_path43.default.join(repoPath, file.relativePath);
15312
- import_node_fs36.default.mkdirSync(import_node_path43.default.dirname(absolutePath), { recursive: true });
15313
- import_node_fs36.default.writeFileSync(absolutePath, file.content, "utf-8");
15475
+ const absolutePath = import_node_path44.default.join(repoPath, file.relativePath);
15476
+ import_node_fs37.default.mkdirSync(import_node_path44.default.dirname(absolutePath), { recursive: true });
15477
+ import_node_fs37.default.writeFileSync(absolutePath, file.content, "utf-8");
15314
15478
  if (file.executable) {
15315
- import_node_fs36.default.chmodSync(absolutePath, 493);
15479
+ import_node_fs37.default.chmodSync(absolutePath, 493);
15316
15480
  }
15317
15481
  }
15318
15482
  function writeDevcontainer(options = {}) {
@@ -15350,12 +15514,12 @@ function writeDevcontainer(options = {}) {
15350
15514
  nextSteps: postWriteNextSteps(repoPath)
15351
15515
  };
15352
15516
  }
15353
- var import_node_fs36, import_node_path43, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
15517
+ var import_node_fs37, import_node_path44, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
15354
15518
  var init_devcontainer_write = __esm({
15355
15519
  "src/core/devcontainer-write.ts"() {
15356
15520
  "use strict";
15357
- import_node_fs36 = __toESM(require("fs"));
15358
- import_node_path43 = __toESM(require("path"));
15521
+ import_node_fs37 = __toESM(require("fs"));
15522
+ import_node_path44 = __toESM(require("path"));
15359
15523
  init_repo_config();
15360
15524
  init_repo_inspect();
15361
15525
  MANAGED_MARKER2 = "devrouter:managed devcontainer";
@@ -15689,13 +15853,13 @@ function buildProfilePlanReport(options) {
15689
15853
  return { ...report, contractPath, bindings };
15690
15854
  }
15691
15855
  function loadContract(repoPath, contractPath) {
15692
- if (import_node_path44.default.isAbsolute(contractPath)) {
15856
+ if (import_node_path45.default.isAbsolute(contractPath)) {
15693
15857
  fail3("profile plan contract path must be relative to the repository.");
15694
15858
  }
15695
15859
  const absolutePath = assertPathWithinRepo(contractPath, repoPath, "Profile plan contract");
15696
15860
  let metadata;
15697
15861
  try {
15698
- metadata = import_node_fs37.default.lstatSync(absolutePath);
15862
+ metadata = import_node_fs38.default.lstatSync(absolutePath);
15699
15863
  } catch (error) {
15700
15864
  fail3(`could not inspect profile plan contract '${contractPath}': ${error.message}`);
15701
15865
  }
@@ -15705,16 +15869,16 @@ function loadContract(repoPath, contractPath) {
15705
15869
  if (metadata.size > MAX_CONTRACT_BYTES) {
15706
15870
  fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
15707
15871
  }
15708
- const realRepo = import_node_fs37.default.realpathSync(repoPath);
15709
- const realContract = import_node_fs37.default.realpathSync(absolutePath);
15872
+ const realRepo = import_node_fs38.default.realpathSync(repoPath);
15873
+ const realContract = import_node_fs38.default.realpathSync(absolutePath);
15710
15874
  assertPathWithinRepo(realContract, realRepo, "Profile plan contract");
15711
- const source = import_node_fs37.default.readFileSync(absolutePath, "utf-8");
15875
+ const source = import_node_fs38.default.readFileSync(absolutePath, "utf-8");
15712
15876
  if (Buffer.byteLength(source, "utf-8") > MAX_CONTRACT_BYTES) {
15713
15877
  fail3(`profile plan contract exceeds the ${MAX_CONTRACT_BYTES}-byte limit.`);
15714
15878
  }
15715
15879
  return {
15716
15880
  contract: parseProfilePlanContract(source),
15717
- contractPath: import_node_path44.default.relative(repoPath, absolutePath).split(import_node_path44.default.sep).join("/")
15881
+ contractPath: import_node_path45.default.relative(repoPath, absolutePath).split(import_node_path45.default.sep).join("/")
15718
15882
  };
15719
15883
  }
15720
15884
  function resolveProfilePlan(options) {
@@ -15728,12 +15892,12 @@ function resolveProfilePlan(options) {
15728
15892
  contractPath: loaded.contractPath
15729
15893
  });
15730
15894
  }
15731
- var import_node_fs37, import_node_path44, import_yaml7, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
15895
+ var import_node_fs38, import_node_path45, import_yaml7, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
15732
15896
  var init_profile_plan = __esm({
15733
15897
  "src/core/profile-plan.ts"() {
15734
15898
  "use strict";
15735
- import_node_fs37 = __toESM(require("fs"));
15736
- import_node_path44 = __toESM(require("path"));
15899
+ import_node_fs38 = __toESM(require("fs"));
15900
+ import_node_path45 = __toESM(require("path"));
15737
15901
  import_yaml7 = require("yaml");
15738
15902
  init_paths();
15739
15903
  init_profile_resolution();
@@ -15765,7 +15929,7 @@ async function runProfileResolveCommand(options) {
15765
15929
  async function runProfilePlanCommand(options) {
15766
15930
  const report = resolveProfilePlan(options);
15767
15931
  if (options.output) {
15768
- writeFileAtomically(import_node_path45.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
15932
+ writeFileAtomically(import_node_path46.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
15769
15933
  `);
15770
15934
  }
15771
15935
  if (options.json) {
@@ -15801,14 +15965,14 @@ function printProfilePlanSummary(report, output2) {
15801
15965
  process.stdout.write(`Binding ${key}: ${renderValues(report.bindings[key] ?? [])}
15802
15966
  `);
15803
15967
  }
15804
- if (output2) process.stdout.write(`Output: ${import_node_path45.default.resolve(output2)}
15968
+ if (output2) process.stdout.write(`Output: ${import_node_path46.default.resolve(output2)}
15805
15969
  `);
15806
15970
  }
15807
- var import_node_path45;
15971
+ var import_node_path46;
15808
15972
  var init_profile = __esm({
15809
15973
  "src/commands/profile.ts"() {
15810
15974
  "use strict";
15811
- import_node_path45 = __toESM(require("path"));
15975
+ import_node_path46 = __toESM(require("path"));
15812
15976
  init_atomic_file();
15813
15977
  init_output();
15814
15978
  init_profile_plan();
@@ -16098,7 +16262,7 @@ function sanitizeRouterId(value) {
16098
16262
  return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
16099
16263
  }
16100
16264
  function repoHash(repoPath) {
16101
- return (0, import_node_crypto17.createHash)("sha1").update(import_node_path46.default.resolve(repoPath)).digest("hex").slice(0, 12);
16265
+ return (0, import_node_crypto17.createHash)("sha1").update(import_node_path47.default.resolve(repoPath)).digest("hex").slice(0, 12);
16102
16266
  }
16103
16267
  function asDockerApp(app) {
16104
16268
  return app.runtime === "docker";
@@ -16177,11 +16341,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
16177
16341
  if (dockerApps.length === 0) {
16178
16342
  throw new Error("No docker apps selected to prepare compose overlay.");
16179
16343
  }
16180
- const cachePath = import_node_path46.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
16181
- import_node_fs38.default.mkdirSync(cachePath, { recursive: true });
16182
- const overlayPath = import_node_path46.default.join(cachePath, "compose.devrouter.yml");
16344
+ const cachePath = import_node_path47.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
16345
+ import_node_fs39.default.mkdirSync(cachePath, { recursive: true });
16346
+ const overlayPath = import_node_path47.default.join(cachePath, "compose.devrouter.yml");
16183
16347
  const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
16184
- import_node_fs38.default.writeFileSync(overlayPath, import_yaml8.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
16348
+ import_node_fs39.default.writeFileSync(overlayPath, import_yaml8.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
16185
16349
  return {
16186
16350
  overlayPath,
16187
16351
  composeFiles: ensureComposeFiles(dockerApps),
@@ -16301,14 +16465,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
16301
16465
  const port = Number(match[1]);
16302
16466
  return Number.isInteger(port) && port > 0 ? port : void 0;
16303
16467
  }
16304
- var import_node_child_process27, import_node_crypto17, import_node_fs38, import_node_path46, import_yaml8;
16468
+ var import_node_child_process27, import_node_crypto17, import_node_fs39, import_node_path47, import_yaml8;
16305
16469
  var init_docker_run = __esm({
16306
16470
  "src/core/docker-run.ts"() {
16307
16471
  "use strict";
16308
16472
  import_node_child_process27 = require("child_process");
16309
16473
  import_node_crypto17 = require("crypto");
16310
- import_node_fs38 = __toESM(require("fs"));
16311
- import_node_path46 = __toESM(require("path"));
16474
+ import_node_fs39 = __toESM(require("fs"));
16475
+ import_node_path47 = __toESM(require("path"));
16312
16476
  import_yaml8 = __toESM(require("yaml"));
16313
16477
  init_docker_error_guidance();
16314
16478
  init_paths();
@@ -17130,13 +17294,13 @@ function measureWorktreeConsumption(worktreePath, options) {
17130
17294
  const startedAt = Date.now();
17131
17295
  let rootStat;
17132
17296
  try {
17133
- rootStat = import_node_fs39.default.lstatSync(worktreePath);
17297
+ rootStat = import_node_fs40.default.lstatSync(worktreePath);
17134
17298
  } catch (error) {
17135
17299
  return { status: "unknown", reason: describeError(error, worktreePath) };
17136
17300
  }
17137
17301
  let rootEntries;
17138
17302
  try {
17139
- rootEntries = import_node_fs39.default.readdirSync(worktreePath, { withFileTypes: true });
17303
+ rootEntries = import_node_fs40.default.readdirSync(worktreePath, { withFileTypes: true });
17140
17304
  } catch (error) {
17141
17305
  return { status: "unknown", reason: describeError(error, worktreePath) };
17142
17306
  }
@@ -17162,10 +17326,10 @@ function measureWorktreeConsumption(worktreePath, options) {
17162
17326
  timedOut = true;
17163
17327
  break;
17164
17328
  }
17165
- const entryPath = import_node_path47.default.join(dirPath, entry.name);
17329
+ const entryPath = import_node_path48.default.join(dirPath, entry.name);
17166
17330
  let entryStat;
17167
17331
  try {
17168
- entryStat = import_node_fs39.default.lstatSync(entryPath);
17332
+ entryStat = import_node_fs40.default.lstatSync(entryPath);
17169
17333
  } catch (error) {
17170
17334
  if (error?.code === "ENOENT") continue;
17171
17335
  unreadableReason = describeIncompleteWalk(error);
@@ -17175,7 +17339,7 @@ function measureWorktreeConsumption(worktreePath, options) {
17175
17339
  if (!entryStat.isDirectory()) continue;
17176
17340
  let childEntries;
17177
17341
  try {
17178
- childEntries = import_node_fs39.default.readdirSync(entryPath, { withFileTypes: true });
17342
+ childEntries = import_node_fs40.default.readdirSync(entryPath, { withFileTypes: true });
17179
17343
  } catch (error) {
17180
17344
  unreadableReason = describeIncompleteWalk(error);
17181
17345
  break;
@@ -17239,12 +17403,12 @@ function describeError(error, worktreePath) {
17239
17403
  }
17240
17404
  return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
17241
17405
  }
17242
- var import_node_fs39, import_node_path47, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
17406
+ var import_node_fs40, import_node_path48, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
17243
17407
  var init_workspace_consumption = __esm({
17244
17408
  "src/core/workspace-consumption.ts"() {
17245
17409
  "use strict";
17246
- import_node_fs39 = __toESM(require("fs"));
17247
- import_node_path47 = __toESM(require("path"));
17410
+ import_node_fs40 = __toESM(require("fs"));
17411
+ import_node_path48 = __toESM(require("path"));
17248
17412
  init_devpod_environment();
17249
17413
  DEFAULT_DEADLINE_MS = 1e4;
17250
17414
  BLOCK_SIZE_BYTES = 512;
@@ -17345,7 +17509,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
17345
17509
  }
17346
17510
  function readGitSnapshot(worktree, commandRunner) {
17347
17511
  const comparablePath = comparableWorkspacePath(worktree.path);
17348
- if (worktree.prunable || !import_node_fs40.default.existsSync(comparablePath)) {
17512
+ if (worktree.prunable || !import_node_fs41.default.existsSync(comparablePath)) {
17349
17513
  return { worktree, checkout: "missing", head: null, committerDate: null };
17350
17514
  }
17351
17515
  const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
@@ -17870,7 +18034,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
17870
18034
  containers2 = measureContainersFn(worktreePaths);
17871
18035
  } catch (error) {
17872
18036
  const reason = `container measurement failed: ${describeCause(error)}`;
17873
- containers2 = new Map(worktreePaths.map((path48) => [path48, unknownContainers(reason)]));
18037
+ containers2 = new Map(worktreePaths.map((path49) => [path49, unknownContainers(reason)]));
17874
18038
  }
17875
18039
  }
17876
18040
  const rows = records.map((record4) => {
@@ -17919,12 +18083,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
17919
18083
  workspaces: rows
17920
18084
  };
17921
18085
  }
17922
- var import_node_child_process29, import_node_fs40, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
18086
+ var import_node_child_process29, import_node_fs41, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
17923
18087
  var init_workspace_cleanup = __esm({
17924
18088
  "src/core/workspace-cleanup.ts"() {
17925
18089
  "use strict";
17926
18090
  import_node_child_process29 = require("child_process");
17927
- import_node_fs40 = __toESM(require("fs"));
18091
+ import_node_fs41 = __toESM(require("fs"));
17928
18092
  init_devpod_workspaces();
17929
18093
  init_host_routes();
17930
18094
  init_repo_config();
@@ -17971,7 +18135,7 @@ function warnMissingWorkspaceOwnership(repoPath) {
17971
18135
  );
17972
18136
  }
17973
18137
  function defaultWorktreePath(mainRepo, ws) {
17974
- return import_node_path48.default.join(mainRepo, "trees", ws);
18138
+ return import_node_path49.default.join(mainRepo, "trees", ws);
17975
18139
  }
17976
18140
  function assertDefaultWorktreeRootIgnored(mainRepo) {
17977
18141
  const ignored = (0, import_node_child_process30.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
@@ -17979,12 +18143,12 @@ function assertDefaultWorktreeRootIgnored(mainRepo) {
17979
18143
  });
17980
18144
  if (ignored.status !== 0) {
17981
18145
  throw new Error(
17982
- `Default worktree root '${import_node_path48.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path48.default.join(mainRepo, ".gitignore")}' or use --path.`
18146
+ `Default worktree root '${import_node_path49.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path49.default.join(mainRepo, ".gitignore")}' or use --path.`
17983
18147
  );
17984
18148
  }
17985
18149
  }
17986
18150
  function legacyDefaultWorktreePath(mainRepo, ws) {
17987
- return import_node_path48.default.join(import_node_path48.default.dirname(mainRepo), `${import_node_path48.default.basename(mainRepo)}-${ws}`);
18151
+ return import_node_path49.default.join(import_node_path49.default.dirname(mainRepo), `${import_node_path49.default.basename(mainRepo)}-${ws}`);
17988
18152
  }
17989
18153
  function teardownFallbackPath(mainRepo, workspace) {
17990
18154
  const candidates = [
@@ -18096,7 +18260,7 @@ function assertFullDownPreflight(mainRepo, target) {
18096
18260
  if (sameWorkspacePath(target.worktreePath, mainRepo)) {
18097
18261
  throw new Error("Refusing to remove the primary Git checkout.");
18098
18262
  }
18099
- if (!target.worktree || target.worktree.prunable || !import_node_fs41.default.existsSync(target.worktreePath)) return;
18263
+ if (!target.worktree || target.worktree.prunable || !import_node_fs42.default.existsSync(target.worktreePath)) return;
18100
18264
  if (target.worktree.locked) {
18101
18265
  throw new Error(
18102
18266
  `Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
@@ -18136,9 +18300,9 @@ async function workspaceUp(branch, opts = {}) {
18136
18300
  (candidate) => defaultWorktreePath(mainRepo, candidate)
18137
18301
  );
18138
18302
  const generatedPath = candidatePaths.find(
18139
- (candidate) => !import_node_fs41.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
18303
+ (candidate) => !import_node_fs42.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
18140
18304
  );
18141
- const selectedPath = opts.path ? import_node_path48.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
18305
+ const selectedPath = opts.path ? import_node_path49.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
18142
18306
  if (!selectedPath) {
18143
18307
  throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
18144
18308
  }
@@ -18147,7 +18311,7 @@ async function workspaceUp(branch, opts = {}) {
18147
18311
  `Branch '${branch}' already uses worktree '${existingBranch.path}', not '${selectedPath}'.`
18148
18312
  );
18149
18313
  }
18150
- if (import_node_fs41.default.existsSync(selectedPath)) {
18314
+ if (import_node_fs42.default.existsSync(selectedPath)) {
18151
18315
  const registered = worktrees.find(
18152
18316
  (worktree) => sameWorkspacePath(worktree.path, selectedPath)
18153
18317
  );
@@ -18287,7 +18451,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
18287
18451
  }
18288
18452
  const result = await mutateWorkspaceRuntime("delete", resolved, worktrees, opts.quiet);
18289
18453
  if (removeWorktree) {
18290
- if (resolved.worktree && !resolved.worktree.prunable && import_node_fs41.default.existsSync(resolved.worktreePath)) {
18454
+ if (resolved.worktree && !resolved.worktree.prunable && import_node_fs42.default.existsSync(resolved.worktreePath)) {
18291
18455
  const rm = (0, import_node_child_process30.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
18292
18456
  encoding: "utf-8"
18293
18457
  });
@@ -18306,7 +18470,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
18306
18470
  }
18307
18471
  return result;
18308
18472
  };
18309
- return resolved.worktree && !resolved.worktree.prunable && import_node_fs41.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
18473
+ return resolved.worktree && !resolved.worktree.prunable && import_node_fs42.default.existsSync(resolved.worktreePath) ? withLifecycleOperationLock(resolved.worktreePath, operation) : operation();
18310
18474
  }
18311
18475
  async function workspaceStop(target, opts = {}) {
18312
18476
  return runWorkspaceLifecycle("stop", target, opts);
@@ -18314,13 +18478,13 @@ async function workspaceStop(target, opts = {}) {
18314
18478
  async function workspaceDown(target, opts = {}) {
18315
18479
  return runWorkspaceLifecycle("down", target, opts);
18316
18480
  }
18317
- var import_node_child_process30, import_node_fs41, import_node_path48, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
18481
+ var import_node_child_process30, import_node_fs42, import_node_path49, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
18318
18482
  var init_workspace_lifecycle = __esm({
18319
18483
  "src/core/workspace-lifecycle.ts"() {
18320
18484
  "use strict";
18321
18485
  import_node_child_process30 = require("child_process");
18322
- import_node_fs41 = __toESM(require("fs"));
18323
- import_node_path48 = __toESM(require("path"));
18486
+ import_node_fs42 = __toESM(require("fs"));
18487
+ import_node_path49 = __toESM(require("path"));
18324
18488
  init_devpod_mutation();
18325
18489
  init_devpod_workspaces();
18326
18490
  init_managed_runtime_state();
@@ -18479,7 +18643,7 @@ var init_version = __esm({
18479
18643
 
18480
18644
  // src/cli.ts
18481
18645
  var import_commander = require("commander");
18482
- var CLI_VERSION = true ? "0.0.61" : "0.0.0-dev";
18646
+ var CLI_VERSION = true ? "0.0.62" : "0.0.0-dev";
18483
18647
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
18484
18648
  function withErrorHandling(action2) {
18485
18649
  return async (...args) => {