@devrouter/cli 0.0.80 → 0.1.0

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.
@@ -5347,7 +5347,7 @@ function retain(session, previous) {
5347
5347
 
5348
5348
  // src/core/reliability-operation-store.ts
5349
5349
  function installedCliVersion() {
5350
- return true ? "0.0.80" : "0.0.0-dev";
5350
+ return true ? "0.1.0" : "0.0.0-dev";
5351
5351
  }
5352
5352
  function compareCliVersions(left, right) {
5353
5353
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -7163,7 +7163,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs18.default.rea
7163
7163
  const config = parseConfig(parsed ?? {}, configPath);
7164
7164
  const requiredVersion = config.devrouter?.version;
7165
7165
  if (requiredVersion && !hasWarnedVersionMismatch) {
7166
- const cliVersion = true ? "0.0.80" : "0.0.0-dev";
7166
+ const cliVersion = true ? "0.1.0" : "0.0.0-dev";
7167
7167
  if (cliVersion !== "0.0.0-dev" && compareSemver2(requiredVersion, cliVersion) > 0) {
7168
7168
  hasWarnedVersionMismatch = true;
7169
7169
  process.stderr.write(
package/dist/devrouter.js CHANGED
@@ -4275,7 +4275,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs7.default.read
4275
4275
  const config = parseConfig(parsed ?? {}, configPath);
4276
4276
  const requiredVersion = config.devrouter?.version;
4277
4277
  if (requiredVersion && !hasWarnedVersionMismatch) {
4278
- const cliVersion = true ? "0.0.80" : "0.0.0-dev";
4278
+ const cliVersion = true ? "0.1.0" : "0.0.0-dev";
4279
4279
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
4280
4280
  hasWarnedVersionMismatch = true;
4281
4281
  process.stderr.write(
@@ -6259,7 +6259,7 @@ var init_reliability_contract = __esm({
6259
6259
 
6260
6260
  // src/core/reliability-operation-store.ts
6261
6261
  function installedCliVersion() {
6262
- return true ? "0.0.80" : "0.0.0-dev";
6262
+ return true ? "0.1.0" : "0.0.0-dev";
6263
6263
  }
6264
6264
  function compareCliVersions(left, right) {
6265
6265
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -21117,7 +21117,7 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
21117
21117
 
21118
21118
  A managed consumer can gate agent tool calls so a command never starts while the
21119
21119
  checkout's environment is mid-transition. Wire \`devrouter harness gate\` as a
21120
- Claude Code \`PreToolUse\` hook:
21120
+ \`PreToolUse\` hook. Claude Code reads it from a settings file:
21121
21121
 
21122
21122
  {
21123
21123
  "hooks": {
@@ -21129,7 +21129,26 @@ Claude Code \`PreToolUse\` hook:
21129
21129
  }
21130
21130
  }
21131
21131
 
21132
- The hook reads the harness payload on stdin, defers inside the hook process while
21132
+ The Codex CLI reads the same hook from \`$CODEX_HOME/hooks.json\`, where the shell
21133
+ tool is named \`exec_command\`:
21134
+
21135
+ {
21136
+ "hooks": {
21137
+ "PreToolUse": [
21138
+ { "matcher": "exec_command|Bash|shell", "hooks": [
21139
+ { "type": "command", "command": "devrouter harness gate", "timeout": 180 }
21140
+ ] }
21141
+ ]
21142
+ }
21143
+ }
21144
+
21145
+ The gate identifies the requesting harness from its payload and answers in that
21146
+ harness's accepted shape: a refusal is a \`deny\` in both, while an allowed call is
21147
+ \`permissionDecision: allow\` for Claude Code and Codex receives a bare completion
21148
+ carrying the same guidance as \`additionalContext\`, because Codex rejects an
21149
+ \`allow\` decision as unsupported hook output.
21150
+
21151
+
21133
21152
  the checkout's durable phase is \`queued\`, \`starting\`, \`verifying\`, \`recovering\`
21134
21153
  or \`stopping\`, and prints the harness permission decision. The deferral consumes
21135
21154
  no model turns because the harness is blocked on the hook. \`--wait-budget-ms\`
@@ -21141,7 +21160,7 @@ the wait budget: an overrunning hook is not honored, and the tool proceeds under
21141
21160
  the harness's normal permission rules. Use \`--json\` for the devrouter decision
21142
21161
  envelope with wait metrics.
21143
21162
 
21144
- +Gated calls are keyed by the harness \`tool_use_id\` and recorded durably. If the
21163
+ Gated calls are keyed by the harness \`tool_use_id\` and recorded durably. If the
21145
21164
  harness re-delivers a call it already granted or cancelled, the gate returns one
21146
21165
  refusal naming the earlier decision instead of waiting again, because that call
21147
21166
  may already have run; the agent should verify it and issue a new call. Entries
@@ -21354,7 +21373,7 @@ function buildOnboardingPrompt(options = {}) {
21354
21373
  `- 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.`,
21355
21374
  "- 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.",
21356
21375
  "- 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.",
21357
- "- Managed agent harnesses can defer tool calls through `devrouter harness gate` wired as a Claude Code `PreToolUse` hook: it waits inside the hook process while the checkout's durable phase is queued, starting, verifying, recovering or stopping (no model turns), allows the tool once the phase settles, and refuses once with recovery guidance when `--wait-budget-ms` (default 30000) runs out. It also records the gated call by harness tool-use id, so a call re-delivered after a granted or cancelled wait returns one refusal instead of repeating a command that may already have run. Lifecycle commands pass through, unreadable journal evidence is allowed rather than blocking the agent, and the hook timeout must exceed the wait budget.",
21376
+ "- Managed agent harnesses can defer tool calls through `devrouter harness gate` wired as a `PreToolUse` hook (Claude Code settings, or `$CODEX_HOME/hooks.json` for the Codex CLI with the `exec_command` shell tool): it waits inside the hook process while the checkout's durable phase is queued, starting, verifying, recovering or stopping (no model turns), allows the tool once the phase settles, and refuses once with recovery guidance when `--wait-budget-ms` (default 30000) runs out. It identifies the requesting harness from its payload and answers in that harness's accepted shape, because Codex rejects an `allow` decision. It also records the gated call by harness tool-use id, so a call re-delivered after a granted or cancelled wait returns one refusal instead of repeating a command that may already have run. Lifecycle commands pass through, unreadable journal evidence is allowed rather than blocking the agent, and the hook timeout must exceed the wait budget.",
21358
21377
  "- 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.",
21359
21378
  "- Busy ensure and exec wait up to thirty minutes for a positively identified active lifecycle worker on the same checkout, with stderr progress. Do not stop healthy work to make room. Commands stay serial; timeout or cancellation before admission leaves prior work untouched. A lifecycle fence change cancels admission, and uncertain execution is never replayed.",
21360
21379
  "- 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 detect or prevent OOM; bounded automatic recovery runs only when the operator enables the capacity recovery policy.",
@@ -23174,7 +23193,7 @@ function probeInstalledCliVersion(executable) {
23174
23193
  }
23175
23194
  function buildCliPathCheck(options = {}) {
23176
23195
  const id5 = "global.cli-path";
23177
- const runningVersion = options.runningVersion ?? (true ? "0.0.80" : "0.0.0-dev");
23196
+ const runningVersion = options.runningVersion ?? (true ? "0.1.0" : "0.0.0-dev");
23178
23197
  if (runningVersion === "0.0.0-dev") {
23179
23198
  return {
23180
23199
  id: id5,
@@ -24022,7 +24041,7 @@ async function buildDoctorReport(options = {}) {
24022
24041
  suggestion: `Remedy ${expansionNotice.remedy.code}: set a named default profile other than '${expansionNotice.profile}'${remedyCandidates}.`
24023
24042
  });
24024
24043
  }
24025
- const cliVersion = true ? "0.0.80" : "0.0.0-dev";
24044
+ const cliVersion = true ? "0.1.0" : "0.0.0-dev";
24026
24045
  const configVersion = config.devrouter?.version;
24027
24046
  const installedKnown = cliVersion !== "0.0.0-dev";
24028
24047
  const versionComparison = installedKnown && configVersion ? compareSemver(configVersion, cliVersion) : void 0;
@@ -25128,8 +25147,13 @@ var init_harness_gate = __esm({
25128
25147
  // src/commands/harness.ts
25129
25148
  var harness_exports = {};
25130
25149
  __export(harness_exports, {
25150
+ detectHarnessHook: () => detectHarnessHook,
25131
25151
  runHarnessCommand: () => runHarnessCommand
25132
25152
  });
25153
+ function detectHarnessHook(payload) {
25154
+ const turnId = payload.turn_id;
25155
+ return typeof turnId === "string" && turnId.length > 0 ? "codex" : "claude";
25156
+ }
25133
25157
  function readStdin() {
25134
25158
  if (process.stdin.isTTY) {
25135
25159
  return Promise.resolve("");
@@ -25181,11 +25205,28 @@ function permitReason(decision) {
25181
25205
  return "devrouter: environment settled.";
25182
25206
  }
25183
25207
  }
25184
- function hookOutput(decision) {
25208
+ function hookOutput(decision, kind) {
25209
+ if (decision.decision === "refuse") {
25210
+ return JSON.stringify({
25211
+ hookSpecificOutput: {
25212
+ hookEventName: "PreToolUse",
25213
+ permissionDecision: "deny",
25214
+ permissionDecisionReason: permitReason(decision)
25215
+ }
25216
+ });
25217
+ }
25218
+ if (kind === "codex") {
25219
+ return JSON.stringify({
25220
+ hookSpecificOutput: {
25221
+ hookEventName: "PreToolUse",
25222
+ additionalContext: permitReason(decision)
25223
+ }
25224
+ });
25225
+ }
25185
25226
  return JSON.stringify({
25186
25227
  hookSpecificOutput: {
25187
25228
  hookEventName: "PreToolUse",
25188
- permissionDecision: decision.decision === "refuse" ? "deny" : "allow",
25229
+ permissionDecision: "allow",
25189
25230
  permissionDecisionReason: permitReason(decision)
25190
25231
  }
25191
25232
  });
@@ -25209,6 +25250,7 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
25209
25250
  `));
25210
25251
  const observe = dependencies.observe ?? readHarnessGateObservation;
25211
25252
  const raw = await (dependencies.stdin ?? readStdin)();
25253
+ let kind = "claude";
25212
25254
  let payload;
25213
25255
  try {
25214
25256
  const parsed = JSON.parse(raw);
@@ -25218,28 +25260,35 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
25218
25260
  }
25219
25261
  if (!payload) {
25220
25262
  const decision2 = gateDecision("allow", "hook-payload-invalid");
25221
- process.stdout.write(json ? `${JSON.stringify(decision2)}
25222
- ` : `${hookOutput(decision2)}
25223
- `);
25263
+ process.stdout.write(
25264
+ json ? `${JSON.stringify(decision2)}
25265
+ ` : `${hookOutput(decision2, kind)}
25266
+ `
25267
+ );
25224
25268
  return;
25225
25269
  }
25270
+ kind = detectHarnessHook(payload);
25226
25271
  const toolName = typeof payload.tool_name === "string" ? payload.tool_name : "";
25227
25272
  const toolInput = isRecord7(payload.tool_input) ? payload.tool_input : {};
25228
25273
  const command = typeof toolInput.command === "string" ? toolInput.command : "";
25229
25274
  if (toolName === "Bash" && DEVROUTER_COMMAND_RE.test(command)) {
25230
25275
  const decision2 = gateDecision("allow", "devrouter-command");
25231
- process.stdout.write(json ? `${JSON.stringify(decision2)}
25232
- ` : `${hookOutput(decision2)}
25233
- `);
25276
+ process.stdout.write(
25277
+ json ? `${JSON.stringify(decision2)}
25278
+ ` : `${hookOutput(decision2, kind)}
25279
+ `
25280
+ );
25234
25281
  return;
25235
25282
  }
25236
25283
  const sessionCwd = repoPath ?? (typeof payload.cwd === "string" && payload.cwd.length > 0 ? payload.cwd : void 0);
25237
25284
  const repoRoot = sessionCwd ? findHarnessGateRepoRoot(sessionCwd) : void 0;
25238
25285
  if (!repoRoot) {
25239
25286
  const decision2 = gateDecision("allow", "unmanaged-checkout");
25240
- process.stdout.write(json ? `${JSON.stringify(decision2)}
25241
- ` : `${hookOutput(decision2)}
25242
- `);
25287
+ process.stdout.write(
25288
+ json ? `${JSON.stringify(decision2)}
25289
+ ` : `${hookOutput(decision2, kind)}
25290
+ `
25291
+ );
25243
25292
  return;
25244
25293
  }
25245
25294
  const budgetMs = parseHarnessGateBudget(options.waitBudgetMs);
@@ -25290,7 +25339,7 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
25290
25339
  };
25291
25340
  process.stdout.write(
25292
25341
  json ? `${JSON.stringify({ ...decision2, checkout: repoRoot })}
25293
- ` : `${hookOutput(decision2)}
25342
+ ` : `${hookOutput(decision2, kind)}
25294
25343
  `
25295
25344
  );
25296
25345
  return;
@@ -25348,7 +25397,7 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
25348
25397
  }
25349
25398
  process.stdout.write(
25350
25399
  json ? `${JSON.stringify({ ...decision, checkout: repoRoot })}
25351
- ` : `${hookOutput(decision)}
25400
+ ` : `${hookOutput(decision, kind)}
25352
25401
  `
25353
25402
  );
25354
25403
  }
@@ -29972,7 +30021,7 @@ var init_version = __esm({
29972
30021
 
29973
30022
  // src/cli.ts
29974
30023
  var import_commander = require("commander");
29975
- var CLI_VERSION = true ? "0.0.80" : "0.0.0-dev";
30024
+ var CLI_VERSION = true ? "0.1.0" : "0.0.0-dev";
29976
30025
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
29977
30026
  function withErrorHandling(action2) {
29978
30027
  return async (...args) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devrouter/cli",
3
- "version": "0.0.80",
3
+ "version": "0.1.0",
4
4
  "description": "Local dev routing CLI with shared Traefik reverse proxy",
5
5
  "author": "Roland Schlaefli",
6
6
  "homepage": "https://github.com/rschlaefli/devrouter#readme",
@@ -52,6 +52,8 @@
52
52
  "qualify:controller": "node --import tsx scripts/qualify-controller.ts",
53
53
  "qualify:stop-drift": "node --import tsx scripts/qualify-config-drift-stop.ts",
54
54
  "qualify:harness-journey": "./scripts/qualify-harness-journey.sh",
55
+ "qualify:codex-journey": "DR_JOURNEY_HARNESS=codex ./scripts/qualify-harness-journey.sh",
56
+ "qualify:non-node": "./scripts/qualify-non-node-consumer.sh",
55
57
  "test:process": "bash scripts/test-devrouter-process.sh",
56
58
  "check": "biome check .",
57
59
  "check:fix": "biome check --write .",
@@ -0,0 +1,69 @@
1
+ # Upgrade to devrouter 0.1.0
2
+
3
+ This release is the first stable cut of the reliability program. The managed
4
+ lifecycle is now driven by durable intent instead of a live CLI process, capacity
5
+ admission refuses instead of guessing when its evidence is incomplete, and the
6
+ agent harness gate serves Claude Code and the Codex CLI in the shape each of them
7
+ accepts.
8
+
9
+ ## What changed for a consumer
10
+
11
+ - A managed `stop` whose physical cessation is proven against a positively
12
+ absent capacity ledger now completes instead of staying in `stopping`
13
+ forever. An intact ledger keeps today's settlement and revision fencing, and
14
+ every other ledger classification still refuses.
15
+ - `devrouter capacity reconcile --yes [--json]` is the explicit forward
16
+ recovery for a provably absent ledger. It refuses without `--yes`, while any
17
+ journal binding remains, and when a declared runtime domain cannot be observed.
18
+ - `devrouter doctor` reports `global.cli-path` when another `devrouter` on
19
+ PATH is newer than the running CLI or when the shell resolves a different
20
+ install. Keep one install authoritative before trusting lifecycle state.
21
+ - `devrouter harness gate` defers one agent tool call while the checkout's
22
+ durable phase is `queued`, `starting`, `verifying`, `recovering` or
23
+ `stopping`, waits inside the hook process with no model turns, and refuses
24
+ once with recovery guidance when the budget runs out. It identifies the
25
+ requesting harness from its payload: Claude Code receives
26
+ `permissionDecision: allow`, the Codex CLI receives the same guidance as
27
+ `additionalContext` because it reports an `allow` decision as unsupported
28
+ hook output. A re-delivered call returns one refusal that names the earlier
29
+ decision instead of repeating a command that may already have run.
30
+ - A fixed host-port conflict whose holder is the shared `devrouter-traefik`
31
+ router names the consumer's own published binding as the side to change instead
32
+ of telling the agent to stop the router.
33
+ - Managed `ensure` accepts a devnet upstream served by any Compose project owned
34
+ by the exact worktree, while a container owned by a different worktree or by a
35
+ shared project outside the checkout is still refused.
36
+
37
+ ## Task
38
+
39
+ Use the published 0.1.0 CLI. Update the repository `devrouter.version` metadata
40
+ only when consumer source changes are authorized, and refresh generated agent
41
+ guidance only with its owner's authority. No schema, command, flag, environment
42
+ or journal migration is needed, and the upgrade rewrites no runtime state.
43
+
44
+ Two operating notes matter for scripts and agents:
45
+
46
+ - `ensure` and `stop` write bounded progress lines to stderr. Parse JSON from
47
+ stdout only; do not merge the streams before parsing.
48
+ - Capacity admission, parking consent and automatic recovery stay inert unless an
49
+ operator enables them. Nothing in this release starts, stops or parks a
50
+ consumer environment on its own.
51
+
52
+ ## Validation
53
+
54
+ Verify the resolved executable and installed version, then run `devrouter
55
+ doctor` and confirm `global.cli-path` reports one authoritative install and no
56
+ newer shadow copy. For an authorized managed checkout, run `devrouter status`
57
+ and confirm the reported intent, phase, capacity state and attention reason agree
58
+ with the runtime you observe. Require each lifecycle operation's own terminal
59
+ evidence: a status block or a progress line is not stop or ensure success. When
60
+ the gate is wired, prove it once in the harness you use by gating a harmless
61
+ command against a settled checkout.
62
+
63
+ ## Report
64
+
65
+ Report the release revision, the executable and installed version, the
66
+ CLI-path diagnostics, the status block for any affected checkout, and the
67
+ terminal result of any authorized lifecycle operation. Keep source and package
68
+ validation separate from consumer readiness or recovery, and record unknown
69
+ ownership or surviving resources as unresolved rather than as recovered.