From 4a8c8b7e845dd99260ebdd9212ca75132c614bf1 Mon Sep 17 00:00:00 2001 From: Roberto Cano <3525807+robercano@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:04:54 +0200 Subject: [PATCH 1/2] feat(pnpm): add root package.json with pnpm cockpit alias Add a minimal, dependency-free root package.json exposing `pnpm cockpit` as a thin alias for .claude/scripts/cockpit-serve.sh (default port 8090, override via `pnpm cockpit -- `). Document the entry point in docs/ARCHITECTURE.md alongside the existing static cockpit description. Closes #90 Co-Authored-By: Claude Sonnet 5 --- docs/ARCHITECTURE.md | 4 ++++ package.json | 9 +++++++++ 2 files changed, 13 insertions(+) create mode 100644 package.json diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ed8c4aa..d2e89b2 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -63,6 +63,10 @@ worker worktrees. Run it, then open `.claude/state/cockpit.html`. No server, no script header for usage (`--fixtures`, `GATES_FILE` override) and `docs/COCKPIT_EVALUATION.md` for the design rationale. +For a live, auto-refreshing view, `.claude/scripts/cockpit-serve.sh` wraps the same renderer behind a small +HTTP server (127.0.0.1 only, node built-ins only). Run it via `pnpm cockpit` (default port 8090), or +`pnpm cockpit -- ` to override the port. + ### Concurrent config-write safety This template fans out to **parallel worktree-isolated workers**, and upstream [anthropics/claude-code#29217](https://github.com/anthropics/claude-code/issues/29217) reported that diff --git a/package.json b/package.json new file mode 100644 index 0000000..c79082d --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name": "recode", + "version": "0.1.0", + "private": true, + "description": "Orchestrated multi-agent Claude Code harness — root package.json is a thin alias for the cockpit dashboard (no dependencies, node built-ins only).", + "scripts": { + "cockpit": "bash .claude/scripts/cockpit-serve.sh" + } +} From a6cd882674609221915b9a143eee60d2838ccd96 Mon Sep 17 00:00:00 2001 From: Roberto Cano <3525807+robercano@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:19:41 +0200 Subject: [PATCH 2/2] fix(pnpm): pin packageManager for Node 20 CI compat, add cockpit alias smoke test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI (.github/actions/setup/action.yml) runs Node 20 + `corepack enable` with no packageManager pin, so corepack was resolving an unpinned pnpm (11.x, requires Node >=22.13) and crashing before `pnpm cockpit` ever ran. Pin packageManager to pnpm@10.34.4 (supports Node >=18.12) and add a minimal engines.node >=20, verified locally under Node 20.13.1: `pnpm cockpit` and `pnpm cockpit -- 9000` both start serving correctly. Also add a smoke case to cockpit.test.sh exercising the scripts.cockpit alias itself via `npm run cockpit` (no corepack/pnpm dependency in CI, same scripts.cockpit entry pnpm resolves) — starts the server bound to 127.0.0.1, asserts a 200 GET of the dashboard, then tears it down by PID (npm wraps the real node server behind npm -> sh -> node, so cleanup targets the actual listening PID via lsof/ss, not just npm's own PID). Co-Authored-By: Claude Sonnet 5 --- .claude/scripts/cockpit.test.sh | 78 +++++++++++++++++++++++++++++++++ package.json | 6 ++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/.claude/scripts/cockpit.test.sh b/.claude/scripts/cockpit.test.sh index f47ba88..d39a981 100755 --- a/.claude/scripts/cockpit.test.sh +++ b/.claude/scripts/cockpit.test.sh @@ -43,11 +43,23 @@ work="$(mktemp -d "${TMPDIR:-/tmp}/cockpit-test.XXXXXX")" # cockpit-serve.sh in the background -- declared here so the SAME EXIT trap # cleans it up no matter where in the script a later assertion fails. server_pid="" +# alias_npm_pid/alias_node_pid: same idea, for the `npm run cockpit` alias +# smoke (section 5b) -- npm wraps the real node server behind npm -> sh -> +# node, so cleanup needs to reach the actual node PID too, not just npm's. +alias_npm_pid="" +alias_node_pid="" cleanup() { if [ -n "$server_pid" ]; then kill "$server_pid" >/dev/null 2>&1 || true wait "$server_pid" 2>/dev/null || true fi + if [ -n "$alias_node_pid" ]; then + kill "$alias_node_pid" >/dev/null 2>&1 || true + fi + if [ -n "$alias_npm_pid" ]; then + kill "$alias_npm_pid" >/dev/null 2>&1 || true + wait "$alias_npm_pid" 2>/dev/null || true + fi rm -rf "$work" } trap cleanup EXIT @@ -430,6 +442,72 @@ tmp_after=0 for f in $tmp_glob; do [ -e "$f" ] && tmp_after=$((tmp_after + 1)); done check "cockpit-serve.sh does not leak its temp HTML file after exit (3a-followup fix)" [ "$tmp_after" -eq "$tmp_before" ] +# --------------------------------------------------------------------------- +# 5b. `pnpm cockpit` alias smoke (issue #90 review fix): exercises the ROOT +# package.json's `scripts.cockpit` entry itself -- not cockpit-serve.sh +# directly -- via `npm run cockpit`. npm (not pnpm) is used here +# deliberately: npm ships with node, so it needs no extra toolchain +# (corepack/pnpm) in CI, and it reads the SAME scripts.cockpit entry pnpm +# would resolve, so this validates the alias wiring end-to-end. Same +# offline --fixtures seam and 127.0.0.1-only contract as section 5's +# cockpit-serve.sh smoke, on a fresh free port, against the SAME +# fixtures dir used above. +# --------------------------------------------------------------------------- +# shellcheck source=resolve-roots.sh +. "$script_dir/resolve-roots.sh" + +check "root package.json has a cockpit script wired to cockpit-serve.sh" node -e ' + const p = require(process.argv[1]); + const s = p.scripts && p.scripts.cockpit; + if (typeof s !== "string" || s.indexOf("cockpit-serve.sh") === -1) { + console.error("scripts.cockpit =", s); + process.exit(1); + } +' "$root/package.json" + +alias_port="$(node -e ' + const s = require("net").createServer(); + s.listen(0, "127.0.0.1", () => { console.log(s.address().port); s.close(); }); +')" + +# --prefix (rather than a `cd`) so the script'"'"'s own relative path +# (.claude/scripts/cockpit-serve.sh) resolves against the repo root +# regardless of this test'"'"'s own cwd. +alias_log="$work/npm-run-cockpit.log" +: > "$alias_log" +npm --prefix "$root" run cockpit -- "$alias_port" --fixtures "$work/fixtures" >"$alias_log" 2>&1 & +alias_npm_pid=$! + +alias_ready=0 +for _ in $(seq 1 50); do + grep -q "cockpit serving" "$alias_log" 2>/dev/null && { alias_ready=1; break; } + kill -0 "$alias_npm_pid" 2>/dev/null || break # npm exited early -- stop polling + sleep 0.2 +done +check "npm run cockpit (the scripts.cockpit alias) prints a startup line within the readiness timeout" [ "$alias_ready" -eq 1 ] + +alias_resp="$(curl -s -o - -w '%{http_code}' "http://127.0.0.1:$alias_port/" 2>/dev/null)" +alias_code="${alias_resp: -3}" +alias_body="${alias_resp%???}" +check "npm run cockpit: GET / returns HTTP 200" [ "$alias_code" = "200" ] +check "npm run cockpit: GET / serves the dashboard (issues section present)" bash -c 'printf "%s" "$1" | grep -qF '"'"'
sh -> (bash exec) node, so +# killing only npm's own PID can leave the node process (and the listening +# port) orphaned. Find the actual PID bound to the port and kill it +# directly (lsof, falling back to ss if lsof isn't installed), then npm's +# own PID as a best-effort belt-and-braces cleanup. +if command -v lsof >/dev/null 2>&1; then + alias_node_pid="$(lsof -ti tcp:"$alias_port" 2>/dev/null | head -1)" +elif command -v ss >/dev/null 2>&1; then + alias_node_pid="$(ss -ltnp 2>/dev/null | grep ":$alias_port " | grep -oP 'pid=\K[0-9]+' | head -1)" +fi +[ -n "$alias_node_pid" ] && kill "$alias_node_pid" >/dev/null 2>&1 || true +kill "$alias_npm_pid" >/dev/null 2>&1 || true +wait "$alias_npm_pid" 2>/dev/null || true +alias_node_pid="" +alias_npm_pid="" + # --------------------------------------------------------------------------- # 6. Worker inspector endpoint (GET /api/worker//, issue #70): # event timeline + latest breadcrumbs + live worktree forensics, entirely diff --git a/package.json b/package.json index c79082d..52f28dd 100644 --- a/package.json +++ b/package.json @@ -5,5 +5,9 @@ "description": "Orchestrated multi-agent Claude Code harness — root package.json is a thin alias for the cockpit dashboard (no dependencies, node built-ins only).", "scripts": { "cockpit": "bash .claude/scripts/cockpit-serve.sh" - } + }, + "engines": { + "node": ">=20" + }, + "packageManager": "pnpm@10.34.4" }