@devrouter/cli 0.0.80 → 0.1.1
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-lifecycle-worker.js +15 -3
- package/dist/devrouter.js +86 -25
- package/package.json +4 -1
- package/upgrade-prompts/0.1.0.md +69 -0
- package/upgrade-prompts/0.1.1.md +48 -0
|
@@ -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.
|
|
5350
|
+
return true ? "0.1.1" : "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.
|
|
7166
|
+
const cliVersion = true ? "0.1.1" : "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(
|
|
@@ -11957,6 +11957,11 @@ function reliabilityRecovery(reason2, repoPath) {
|
|
|
11957
11957
|
case "stop-incomplete":
|
|
11958
11958
|
case "operation-unknown":
|
|
11959
11959
|
return [`Run: devrouter stop ${repoPath}`, `Run: devrouter ensure ${repoPath} --repair`];
|
|
11960
|
+
case "start-refused":
|
|
11961
|
+
return [
|
|
11962
|
+
`Run: devrouter doctor ${repoPath} to read the refused fixed host-port claim and its holder`,
|
|
11963
|
+
`Stop the holding workspace or change this repository's own published binding, then run: devrouter ensure ${repoPath}`
|
|
11964
|
+
];
|
|
11960
11965
|
case "unadmittable":
|
|
11961
11966
|
return [
|
|
11962
11967
|
`Run: devrouter stop ${repoPath}`,
|
|
@@ -11970,6 +11975,13 @@ function reliabilityRecovery(reason2, repoPath) {
|
|
|
11970
11975
|
return [];
|
|
11971
11976
|
}
|
|
11972
11977
|
}
|
|
11978
|
+
function refusedManagedStart(result) {
|
|
11979
|
+
if (result?.ok !== true) return false;
|
|
11980
|
+
const value = result.value;
|
|
11981
|
+
if (typeof value !== "object" || value === null) return false;
|
|
11982
|
+
const conflicts = value.hostPortConflicts;
|
|
11983
|
+
return Array.isArray(conflicts) && conflicts.length > 0;
|
|
11984
|
+
}
|
|
11973
11985
|
function readReliabilityStatus(options) {
|
|
11974
11986
|
let provider;
|
|
11975
11987
|
try {
|
|
@@ -11989,7 +12001,7 @@ function readReliabilityStatus(options) {
|
|
|
11989
12001
|
return void 0;
|
|
11990
12002
|
}
|
|
11991
12003
|
if (!record5) return void 0;
|
|
11992
|
-
const reason2 = reliabilityAttention(record5.state);
|
|
12004
|
+
const reason2 = reliabilityAttention(record5.state) ?? (record5.state.desired === "running" && refusedManagedStart(record5.result) ? "start-refused" : void 0);
|
|
11993
12005
|
return {
|
|
11994
12006
|
desired: record5.state.desired,
|
|
11995
12007
|
phase: record5.state.phase,
|
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.
|
|
4278
|
+
const cliVersion = true ? "0.1.1" : "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.
|
|
6262
|
+
return true ? "0.1.1" : "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);
|
|
@@ -15902,6 +15902,11 @@ function reliabilityRecovery(reason, repoPath) {
|
|
|
15902
15902
|
case "stop-incomplete":
|
|
15903
15903
|
case "operation-unknown":
|
|
15904
15904
|
return [`Run: devrouter stop ${repoPath}`, `Run: devrouter ensure ${repoPath} --repair`];
|
|
15905
|
+
case "start-refused":
|
|
15906
|
+
return [
|
|
15907
|
+
`Run: devrouter doctor ${repoPath} to read the refused fixed host-port claim and its holder`,
|
|
15908
|
+
`Stop the holding workspace or change this repository's own published binding, then run: devrouter ensure ${repoPath}`
|
|
15909
|
+
];
|
|
15905
15910
|
case "unadmittable":
|
|
15906
15911
|
return [
|
|
15907
15912
|
`Run: devrouter stop ${repoPath}`,
|
|
@@ -15915,6 +15920,13 @@ function reliabilityRecovery(reason, repoPath) {
|
|
|
15915
15920
|
return [];
|
|
15916
15921
|
}
|
|
15917
15922
|
}
|
|
15923
|
+
function refusedManagedStart(result) {
|
|
15924
|
+
if (result?.ok !== true) return false;
|
|
15925
|
+
const value = result.value;
|
|
15926
|
+
if (typeof value !== "object" || value === null) return false;
|
|
15927
|
+
const conflicts = value.hostPortConflicts;
|
|
15928
|
+
return Array.isArray(conflicts) && conflicts.length > 0;
|
|
15929
|
+
}
|
|
15918
15930
|
function readReliabilityStatus(options) {
|
|
15919
15931
|
let provider;
|
|
15920
15932
|
try {
|
|
@@ -15934,7 +15946,7 @@ function readReliabilityStatus(options) {
|
|
|
15934
15946
|
return void 0;
|
|
15935
15947
|
}
|
|
15936
15948
|
if (!record5) return void 0;
|
|
15937
|
-
const reason = reliabilityAttention(record5.state);
|
|
15949
|
+
const reason = reliabilityAttention(record5.state) ?? (record5.state.desired === "running" && refusedManagedStart(record5.result) ? "start-refused" : void 0);
|
|
15938
15950
|
return {
|
|
15939
15951
|
desired: record5.state.desired,
|
|
15940
15952
|
phase: record5.state.phase,
|
|
@@ -21024,7 +21036,7 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
21024
21036
|
- **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.
|
|
21025
21037
|
- **devcontainer integration**: managed scaffolds list the base compose file, then \`\${localEnv:DEVCONTAINER_COMPOSE_OVERLAY:docker-compose.default.yml}\`; custom repositories may keep another default overlay. Selecting \`.devcontainer/docker-compose.devrouter.yml\` for linked worktrees must pass \`WORKSPACE\` and \`DEVROUTER_WORKSPACE\` across the combined base/overlay config and bind-mount \`\${DEVROUTER_GIT_COMMON_DIR}\` to the same absolute app-container path. The app exposes \`\${WORKSPACE}-app\`; the proxy uses \`upstream: \${WORKSPACE}-app:<port>\`.
|
|
21026
21038
|
- **Lifecycle**: after one-time \`setup\`, use \`ensure .\` for both primary and linked checkouts; never branch manually on checkout kind or use live verify as startup. \`stop .\` is non-destructive; \`stop . --delete\` is explicit exact-owner cleanup without worktree removal; and \`exec . -- <command...>\` runs one-shot commands only in the exact running workspace runtime (DevPod or Devsy). Never substitute raw \`devpod up\`, \`stop\`, \`delete\`, or the Devsy equivalents: they bypass devrouter's 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. Runtime selection is path-aware: \`DEVROUTER_WORKSPACE_RUNTIME=devpod|devsy\` forces one runtime, an exact-path registry owner wins next, then the machine preference from \`devrouter setup --workspace-runtime\`, then installed-CLI auto-detection. \`workspace up\` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped. Dirty or locked full down fails before side effects. For Devsy, run \`devrouter setup --yes --workspace-runtime devsy\` once per installed Devsy release, rerunning it after a Devsy update, so Devrouter acquires and verifies the official Linux agent in its own machine cache. Devrouter supports Devsy releases in the range >=1.16.2 <2.0.0 and refuses a release whose published asset has no SHA-256 digest. \`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 the official asset for the installed release.
|
|
21027
|
-
- **Interrupted lifecycle**: ensure reconciles an interrupted or never-dispatched ensure after positive worker drainage and automatically repairs retained degraded runtimes. Unknown arbitrary exec is never replayed; explicit stop reconciles that uncertainty. Preserve journals and retained configuration when evidence is unavailable. This does not provide OOM prevention or capacity-managed automatic recovery.
|
|
21039
|
+
- **Interrupted lifecycle**: ensure reconciles an interrupted or never-dispatched ensure after positive worker drainage and automatically repairs retained degraded runtimes. Unknown arbitrary exec is never replayed; explicit stop reconciles that uncertainty. A start refused before any provider mutation \u2014 a fixed published host port already held by a running container \u2014 stays visible in \`status\` as a \`start-refused\` attention reason naming the read-only \`doctor\` check and the consumer-side fix; an explicit \`stop\` releases the intent and the reason, and a later admitted ensure clears it. Preserve journals and retained configuration when evidence is unavailable. This does not provide OOM prevention or capacity-managed automatic recovery.
|
|
21028
21040
|
- **Managed process identity**: \`ensure\` executes an exact captured adapter snapshot. Default reuse includes command argv, workspace, and adapter SHA-256. Set \`DEVROUTER_PROCESS_FINGERPRINT_ENV\` only to comma-separated non-secret environment names whose values affect runtime identity; secret-like names are rejected and raw values are never persisted.
|
|
21029
21041
|
- **Route state**: the versioned Traefik dynamic file is authoritative for both metadata and rendering. JSON is a compatibility mirror; valid headerless generations migrate automatically, while corrupt canonical metadata fails closed.
|
|
21030
21042
|
- **Cleanup**: \`workspace cleanup --repo . --inactive-for 30d --json\` is a report-only, no-\`--yes\` report for managed linked workspaces. It joins ownership (\`present|missing|locked|conflict\`), workspace runtime registration, runtime state (\`running|stopped|busy|not-found|absent|unknown\`), checkout, route, advisory activity, and integration evidence without mutating the workspace runtime, routes, ownership, Git, Docker, applications, worktrees, or branches. Local DevPod/Devsy list/status checks always run; \`--check-merged\` alone enables read-only origin and matching GitHub/GitLab checks. Treat \`not-found\` as stale runtime after Docker pruning; busy, unavailable, or conflicting evidence suppresses destructive suggestions. Explicit \`gc\`/\`down\` can remove exact stale registration only after expected-ID \`NotFound\` proof and ownership revalidation. GC never removes Git worktrees, branches, or prune state. Git has no worktree-removal hook.
|
|
@@ -21117,7 +21129,7 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
21117
21129
|
|
|
21118
21130
|
A managed consumer can gate agent tool calls so a command never starts while the
|
|
21119
21131
|
checkout's environment is mid-transition. Wire \`devrouter harness gate\` as a
|
|
21120
|
-
Claude Code
|
|
21132
|
+
\`PreToolUse\` hook. Claude Code reads it from a settings file:
|
|
21121
21133
|
|
|
21122
21134
|
{
|
|
21123
21135
|
"hooks": {
|
|
@@ -21129,7 +21141,26 @@ Claude Code \`PreToolUse\` hook:
|
|
|
21129
21141
|
}
|
|
21130
21142
|
}
|
|
21131
21143
|
|
|
21132
|
-
The
|
|
21144
|
+
The Codex CLI reads the same hook from \`$CODEX_HOME/hooks.json\`, where the shell
|
|
21145
|
+
tool is named \`exec_command\`:
|
|
21146
|
+
|
|
21147
|
+
{
|
|
21148
|
+
"hooks": {
|
|
21149
|
+
"PreToolUse": [
|
|
21150
|
+
{ "matcher": "exec_command|Bash|shell", "hooks": [
|
|
21151
|
+
{ "type": "command", "command": "devrouter harness gate", "timeout": 180 }
|
|
21152
|
+
] }
|
|
21153
|
+
]
|
|
21154
|
+
}
|
|
21155
|
+
}
|
|
21156
|
+
|
|
21157
|
+
The gate identifies the requesting harness from its payload and answers in that
|
|
21158
|
+
harness's accepted shape: a refusal is a \`deny\` in both, while an allowed call is
|
|
21159
|
+
\`permissionDecision: allow\` for Claude Code and Codex receives a bare completion
|
|
21160
|
+
carrying the same guidance as \`additionalContext\`, because Codex rejects an
|
|
21161
|
+
\`allow\` decision as unsupported hook output.
|
|
21162
|
+
|
|
21163
|
+
|
|
21133
21164
|
the checkout's durable phase is \`queued\`, \`starting\`, \`verifying\`, \`recovering\`
|
|
21134
21165
|
or \`stopping\`, and prints the harness permission decision. The deferral consumes
|
|
21135
21166
|
no model turns because the harness is blocked on the hook. \`--wait-budget-ms\`
|
|
@@ -21141,7 +21172,7 @@ the wait budget: an overrunning hook is not honored, and the tool proceeds under
|
|
|
21141
21172
|
the harness's normal permission rules. Use \`--json\` for the devrouter decision
|
|
21142
21173
|
envelope with wait metrics.
|
|
21143
21174
|
|
|
21144
|
-
|
|
21175
|
+
Gated calls are keyed by the harness \`tool_use_id\` and recorded durably. If the
|
|
21145
21176
|
harness re-delivers a call it already granted or cancelled, the gate returns one
|
|
21146
21177
|
refusal naming the earlier decision instead of waiting again, because that call
|
|
21147
21178
|
may already have run; the agent should verify it and issue a new call. Entries
|
|
@@ -21280,7 +21311,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
21280
21311
|
"- if kind=app and runtime=proxy:",
|
|
21281
21312
|
" - HTTP-only optional readiness: { path: /api/health, statuses: [200], contentType: application/json }; statuses default to [200], explicit lists allow unique 2xx/4xx codes, never redirects. Paths stay on the configured host with no query, fragment, percent escapes, backslash or dot segments. Media types match their case-insensitive base type.",
|
|
21282
21313
|
" - Declared application readiness failures return applicationReadiness.status=application-error and a nonzero ensure exit, while retaining tooling and routes for application fixes; do not reset caches or recreate the provider. Absent readiness means legacy route liveness, not full application readiness.",
|
|
21283
|
-
" - A configured FIXED published host binding already held by a running container refuses managed ensure before any provider start (hostPortConflicts in --json, nonzero exit) naming the port, holder container, compose project, and owning workspace when attributable. Ephemeral host::container bindings are exempt. Never rewrite consumer ports to resolve a conflict: stop the holding workspace or use the consumer's own override, then ensure again. Unverifiable evidence also refuses.",
|
|
21314
|
+
" - A configured FIXED published host binding already held by a running container refuses managed ensure before any provider start (hostPortConflicts in --json, nonzero exit) naming the port, holder container, compose project, and owning workspace when attributable. Ephemeral host::container bindings are exempt. Never rewrite consumer ports to resolve a conflict: stop the holding workspace or use the consumer's own override, then ensure again. The refused start stays visible in `devrouter status` as a `start-refused` attention reason that names the read-only `doctor` check and the consumer-side fix; an explicit `stop` releases the intent and the reason, and a later admitted ensure clears it. Unverifiable evidence also refuses.",
|
|
21284
21315
|
' - upstream: "host:port" (an already-running port, e.g. a devcontainer published on 127.0.0.1:3000, or a container reachable by name on a shared Docker network such as `derivatives-db:5432`)',
|
|
21285
21316
|
` - upstream may use the \`${WORKSPACE_PLACEHOLDER}\` placeholder (e.g. \`${WORKSPACE_PLACEHOLDER}-app:3000\`) to target a per-workspace devcontainer alias; it is substituted with the resolved workspace token at runtime and re-validated. Do NOT put \`${WORKSPACE_PLACEHOLDER}\` in \`host\` (rejected) \u2014 the host is auto-namespaced.`,
|
|
21286
21317
|
" - managed `devrouter ensure` requires every HTTP/TCP proxy upstream to begin with the exact resolved workspace/project alias prefix before it mutates DevPod or routes",
|
|
@@ -21354,7 +21385,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
21354
21385
|
`- 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
21386
|
"- 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
21387
|
"- 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 `
|
|
21388
|
+
"- 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
21389
|
"- 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
21390
|
"- 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
21391
|
"- 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 +23205,7 @@ function probeInstalledCliVersion(executable) {
|
|
|
23174
23205
|
}
|
|
23175
23206
|
function buildCliPathCheck(options = {}) {
|
|
23176
23207
|
const id5 = "global.cli-path";
|
|
23177
|
-
const runningVersion = options.runningVersion ?? (true ? "0.
|
|
23208
|
+
const runningVersion = options.runningVersion ?? (true ? "0.1.1" : "0.0.0-dev");
|
|
23178
23209
|
if (runningVersion === "0.0.0-dev") {
|
|
23179
23210
|
return {
|
|
23180
23211
|
id: id5,
|
|
@@ -24022,7 +24053,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
24022
24053
|
suggestion: `Remedy ${expansionNotice.remedy.code}: set a named default profile other than '${expansionNotice.profile}'${remedyCandidates}.`
|
|
24023
24054
|
});
|
|
24024
24055
|
}
|
|
24025
|
-
const cliVersion = true ? "0.
|
|
24056
|
+
const cliVersion = true ? "0.1.1" : "0.0.0-dev";
|
|
24026
24057
|
const configVersion = config.devrouter?.version;
|
|
24027
24058
|
const installedKnown = cliVersion !== "0.0.0-dev";
|
|
24028
24059
|
const versionComparison = installedKnown && configVersion ? compareSemver(configVersion, cliVersion) : void 0;
|
|
@@ -25128,8 +25159,13 @@ var init_harness_gate = __esm({
|
|
|
25128
25159
|
// src/commands/harness.ts
|
|
25129
25160
|
var harness_exports = {};
|
|
25130
25161
|
__export(harness_exports, {
|
|
25162
|
+
detectHarnessHook: () => detectHarnessHook,
|
|
25131
25163
|
runHarnessCommand: () => runHarnessCommand
|
|
25132
25164
|
});
|
|
25165
|
+
function detectHarnessHook(payload) {
|
|
25166
|
+
const turnId = payload.turn_id;
|
|
25167
|
+
return typeof turnId === "string" && turnId.length > 0 ? "codex" : "claude";
|
|
25168
|
+
}
|
|
25133
25169
|
function readStdin() {
|
|
25134
25170
|
if (process.stdin.isTTY) {
|
|
25135
25171
|
return Promise.resolve("");
|
|
@@ -25181,11 +25217,28 @@ function permitReason(decision) {
|
|
|
25181
25217
|
return "devrouter: environment settled.";
|
|
25182
25218
|
}
|
|
25183
25219
|
}
|
|
25184
|
-
function hookOutput(decision) {
|
|
25220
|
+
function hookOutput(decision, kind) {
|
|
25221
|
+
if (decision.decision === "refuse") {
|
|
25222
|
+
return JSON.stringify({
|
|
25223
|
+
hookSpecificOutput: {
|
|
25224
|
+
hookEventName: "PreToolUse",
|
|
25225
|
+
permissionDecision: "deny",
|
|
25226
|
+
permissionDecisionReason: permitReason(decision)
|
|
25227
|
+
}
|
|
25228
|
+
});
|
|
25229
|
+
}
|
|
25230
|
+
if (kind === "codex") {
|
|
25231
|
+
return JSON.stringify({
|
|
25232
|
+
hookSpecificOutput: {
|
|
25233
|
+
hookEventName: "PreToolUse",
|
|
25234
|
+
additionalContext: permitReason(decision)
|
|
25235
|
+
}
|
|
25236
|
+
});
|
|
25237
|
+
}
|
|
25185
25238
|
return JSON.stringify({
|
|
25186
25239
|
hookSpecificOutput: {
|
|
25187
25240
|
hookEventName: "PreToolUse",
|
|
25188
|
-
permissionDecision:
|
|
25241
|
+
permissionDecision: "allow",
|
|
25189
25242
|
permissionDecisionReason: permitReason(decision)
|
|
25190
25243
|
}
|
|
25191
25244
|
});
|
|
@@ -25209,6 +25262,7 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
|
|
|
25209
25262
|
`));
|
|
25210
25263
|
const observe = dependencies.observe ?? readHarnessGateObservation;
|
|
25211
25264
|
const raw = await (dependencies.stdin ?? readStdin)();
|
|
25265
|
+
let kind = "claude";
|
|
25212
25266
|
let payload;
|
|
25213
25267
|
try {
|
|
25214
25268
|
const parsed = JSON.parse(raw);
|
|
@@ -25218,28 +25272,35 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
|
|
|
25218
25272
|
}
|
|
25219
25273
|
if (!payload) {
|
|
25220
25274
|
const decision2 = gateDecision("allow", "hook-payload-invalid");
|
|
25221
|
-
process.stdout.write(
|
|
25222
|
-
|
|
25223
|
-
`)
|
|
25275
|
+
process.stdout.write(
|
|
25276
|
+
json ? `${JSON.stringify(decision2)}
|
|
25277
|
+
` : `${hookOutput(decision2, kind)}
|
|
25278
|
+
`
|
|
25279
|
+
);
|
|
25224
25280
|
return;
|
|
25225
25281
|
}
|
|
25282
|
+
kind = detectHarnessHook(payload);
|
|
25226
25283
|
const toolName = typeof payload.tool_name === "string" ? payload.tool_name : "";
|
|
25227
25284
|
const toolInput = isRecord7(payload.tool_input) ? payload.tool_input : {};
|
|
25228
25285
|
const command = typeof toolInput.command === "string" ? toolInput.command : "";
|
|
25229
25286
|
if (toolName === "Bash" && DEVROUTER_COMMAND_RE.test(command)) {
|
|
25230
25287
|
const decision2 = gateDecision("allow", "devrouter-command");
|
|
25231
|
-
process.stdout.write(
|
|
25232
|
-
|
|
25233
|
-
`)
|
|
25288
|
+
process.stdout.write(
|
|
25289
|
+
json ? `${JSON.stringify(decision2)}
|
|
25290
|
+
` : `${hookOutput(decision2, kind)}
|
|
25291
|
+
`
|
|
25292
|
+
);
|
|
25234
25293
|
return;
|
|
25235
25294
|
}
|
|
25236
25295
|
const sessionCwd = repoPath ?? (typeof payload.cwd === "string" && payload.cwd.length > 0 ? payload.cwd : void 0);
|
|
25237
25296
|
const repoRoot = sessionCwd ? findHarnessGateRepoRoot(sessionCwd) : void 0;
|
|
25238
25297
|
if (!repoRoot) {
|
|
25239
25298
|
const decision2 = gateDecision("allow", "unmanaged-checkout");
|
|
25240
|
-
process.stdout.write(
|
|
25241
|
-
|
|
25242
|
-
`)
|
|
25299
|
+
process.stdout.write(
|
|
25300
|
+
json ? `${JSON.stringify(decision2)}
|
|
25301
|
+
` : `${hookOutput(decision2, kind)}
|
|
25302
|
+
`
|
|
25303
|
+
);
|
|
25243
25304
|
return;
|
|
25244
25305
|
}
|
|
25245
25306
|
const budgetMs = parseHarnessGateBudget(options.waitBudgetMs);
|
|
@@ -25290,7 +25351,7 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
|
|
|
25290
25351
|
};
|
|
25291
25352
|
process.stdout.write(
|
|
25292
25353
|
json ? `${JSON.stringify({ ...decision2, checkout: repoRoot })}
|
|
25293
|
-
` : `${hookOutput(decision2)}
|
|
25354
|
+
` : `${hookOutput(decision2, kind)}
|
|
25294
25355
|
`
|
|
25295
25356
|
);
|
|
25296
25357
|
return;
|
|
@@ -25348,7 +25409,7 @@ async function runHarnessCommand(method, options, repoPath, dependencies = {}) {
|
|
|
25348
25409
|
}
|
|
25349
25410
|
process.stdout.write(
|
|
25350
25411
|
json ? `${JSON.stringify({ ...decision, checkout: repoRoot })}
|
|
25351
|
-
` : `${hookOutput(decision)}
|
|
25412
|
+
` : `${hookOutput(decision, kind)}
|
|
25352
25413
|
`
|
|
25353
25414
|
);
|
|
25354
25415
|
}
|
|
@@ -29972,7 +30033,7 @@ var init_version = __esm({
|
|
|
29972
30033
|
|
|
29973
30034
|
// src/cli.ts
|
|
29974
30035
|
var import_commander = require("commander");
|
|
29975
|
-
var CLI_VERSION = true ? "0.
|
|
30036
|
+
var CLI_VERSION = true ? "0.1.1" : "0.0.0-dev";
|
|
29976
30037
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
29977
30038
|
function withErrorHandling(action2) {
|
|
29978
30039
|
return async (...args) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devrouter/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
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,9 @@
|
|
|
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",
|
|
57
|
+
"qualify:slow-dependency": "./scripts/qualify-slow-dependency-recovery.sh",
|
|
55
58
|
"test:process": "bash scripts/test-devrouter-process.sh",
|
|
56
59
|
"check": "biome check .",
|
|
57
60
|
"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.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Upgrade to devrouter 0.1.1
|
|
2
|
+
|
|
3
|
+
This is a patch release on top of 0.1.0. It closes one way a managed environment
|
|
4
|
+
could sit blocked without saying so: a start that admission refused on a fixed
|
|
5
|
+
published host-port claim left the journal recording the intent as `running`
|
|
6
|
+
while no runtime existed, so `devrouter status` reported a stable, drifting-free
|
|
7
|
+
environment that could not progress.
|
|
8
|
+
|
|
9
|
+
## What changed for a consumer
|
|
10
|
+
|
|
11
|
+
- A managed `ensure` refused because a running container already holds a fixed
|
|
12
|
+
published host port now stays visible after the command exits. `devrouter
|
|
13
|
+
status` reports a `start-refused` attention reason with two recovery
|
|
14
|
+
commands: the read-only `devrouter doctor <repo>` check that names the
|
|
15
|
+
conflicting consumer binding and its holder, and the consumer-side fix
|
|
16
|
+
followed by `devrouter ensure <repo>`.
|
|
17
|
+
- An explicit `devrouter stop <repo>` releases that intent and the reason, and a
|
|
18
|
+
later admitted ensure clears it. Nothing else about the refusal changed: the
|
|
19
|
+
start is still refused before any provider mutation, and live-worker,
|
|
20
|
+
unknown-ownership and surviving-resource checks still refuse fail-closed.
|
|
21
|
+
- The release also carries `pnpm qualify:slow-dependency`, the developer
|
|
22
|
+
qualifier behind the Q06 dependency-recovery evidence. It is a repository
|
|
23
|
+
script and has no consumer-facing effect.
|
|
24
|
+
|
|
25
|
+
## Task
|
|
26
|
+
|
|
27
|
+
Use the published 0.1.1 CLI. Update the repository `devrouter.version` metadata
|
|
28
|
+
only when consumer source changes are authorized, and refresh generated agent
|
|
29
|
+
guidance only with its owner's authority. No schema, command, flag, environment
|
|
30
|
+
or journal migration is needed, and the upgrade rewrites no runtime state.
|
|
31
|
+
|
|
32
|
+
## Validation
|
|
33
|
+
|
|
34
|
+
Verify the resolved executable and installed version, then run `devrouter
|
|
35
|
+
doctor` for the checkout and confirm `global.cli-path` reports one
|
|
36
|
+
authoritative install. Run `devrouter status` and confirm the reported intent,
|
|
37
|
+
phase and attention reason agree with the runtime you observe. When the reported
|
|
38
|
+
reason is `start-refused`, resolve the named binding as the owner of that
|
|
39
|
+
port — the shared router is never the side to stop — and keep each lifecycle
|
|
40
|
+
operation's own terminal evidence separate from a status block or progress line.
|
|
41
|
+
|
|
42
|
+
## Report
|
|
43
|
+
|
|
44
|
+
Report the release revision, the executable and installed version, the status
|
|
45
|
+
block for any affected checkout, and the terminal result of any authorized
|
|
46
|
+
lifecycle operation. Keep source and package validation separate from consumer
|
|
47
|
+
readiness or recovery, and record unknown ownership or surviving resources as
|
|
48
|
+
unresolved rather than as recovered.
|