Skip to content

Quickstart

Quickstart #152

Workflow file for this run

name: Quickstart
# D1 (stranger-readiness): the README's install path executed verbatim on a
# genuinely clean machine, against the PUBLISHED package — the stranger walk
# as a permanent regression guard. The weekly run catches registry/uv drift
# that no push would.
on:
push:
branches: [main]
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:
jobs:
stranger:
runs-on: ubuntu-latest
steps:
# checkout exists ONLY for the doc-drift guard below; the walk itself
# uses the published package, never the repo code.
- uses: actions/checkout@v5
- name: README still documents the exact command this job runs
run: grep -F "uvx --from clauderizer clauderize init" README.md
- name: Install uv per its official one-liner
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: The stranger's first hour
run: |
export PATH="$HOME/.local/bin:$PATH"
mkdir /tmp/my-project && cd /tmp/my-project
git init
uvx --from clauderizer clauderize init --size standard
test -f .clauderizer/config.toml
REG=$(python3 -c "import json;print([h['command'] for g in json.load(open('.claude/settings.json'))['hooks']['SessionStart'] for h in g['hooks']][0])")
echo "registered hook: $REG"
cd /tmp
OUT=$(/bin/sh -c "$REG")
echo "$OUT"
printf '%s\n' "$OUT" | head -1 | grep -q '^\[Clauderizer\]'
cd /tmp/my-project
uvx --from clauderizer clauderize doctor
- name: The MCP leg — the PUBLISHED server answers, and says which version
# H-20/D-060. test.yml runs in-process and structurally cannot see this
# (L-60): the tool surface a session actually gets comes from whatever
# `uvx --from clauderizer[mcp]` resolves, which is a DIFFERENT install
# from the engine under test. That gap is how a repo came to run its hook
# on one engine while its MCP client was served another, advertising five
# tools the server did not have.
run: |
export PATH="$HOME/.local/bin:$PATH"
cd /tmp/my-project
# Drive the server as a real client would: hold stdin OPEN and read
# until the responses arrive. Piping all three messages and closing
# stdin is a race — the server may see EOF and exit before answering
# tools/list, which made this gate a coin flip (green on 1.14.1's
# release commit, red on 1.14.2's, green again on a bare re-run, with
# the published server proven healthy at 67 tools throughout). A
# flaky release gate is worse than none: it teaches you to re-run red.
python3 - > /tmp/mcp.out <<'DRIVE_EOF' || true
import json, subprocess, sys, time
p = subprocess.Popen(["uvx", "--from", "clauderizer[mcp]", "clauderizer-mcp"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL, text=True, bufsize=1)
def send(o):
p.stdin.write(json.dumps(o) + "\n"); p.stdin.flush()
send({"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {"protocolVersion": "2024-11-05", "capabilities": {},
"clientInfo": {"name": "ci", "version": "0"}}})
time.sleep(1.0)
send({"jsonrpc": "2.0", "method": "notifications/initialized"})
time.sleep(0.5)
send({"jsonrpc": "2.0", "id": 2, "method": "tools/list"})
seen, deadline = set(), time.time() + 120
while time.time() < deadline and 2 not in seen:
line = p.stdout.readline()
if not line:
break
sys.stdout.write(line)
try:
seen.add(json.loads(line).get("id"))
except Exception:
pass
p.kill()
DRIVE_EOF
python3 - <<'PY_EOF'
import json, sys
served_name = served_version = None
tools = None
for line in open("/tmp/mcp.out", encoding="utf-8", errors="replace"):
try:
m = json.loads(line)
except Exception:
continue
if m.get("id") == 1:
info = m["result"].get("serverInfo") or {}
served_name, served_version = info.get("name"), info.get("version")
if m.get("id") == 2:
tools = [t["name"] for t in m["result"]["tools"]]
assert served_name == "clauderizer", f"serverInfo.name={served_name!r}"
assert served_version, "the published server reported no version"
assert tools, "the published server listed no tools"
print(f"published MCP server: clauderizer {served_version}, {len(tools)} tools")
# The digest advertises TOOL_NAMES from the engine that wrote it; if the
# served surface is smaller, an agent is told about tools it cannot call.
missing = [t for t in ("cz_status", "cz_next_phase_context", "cz_preflight")
if t not in tools]
assert not missing, f"published surface is missing core tools: {missing}"
PY_EOF
- name: The wiring survives a cache clean (self-arming)
# Published 0.9.0 predates the ephemeral-wiring fix (it wires uv-cache
# paths that die on `uv cache clean`); the assertion arms automatically
# at the first release that carries it.
run: |
export PATH="$HOME/.local/bin:$PATH"
cd /tmp/my-project
V=$(uvx --from clauderizer clauderize --version | awk '{print $2}')
if [ "$V" = "0.9.0" ]; then
echo "published $V predates the ephemeral-wiring fix — assertion arms at the next release"
exit 0
fi
uv cache clean
REG=$(python3 -c "import json;print([h['command'] for g in json.load(open('.claude/settings.json'))['hooks']['SessionStart'] for h in g['hooks']][0])")
OUT=$(/bin/sh -c "$REG")
echo "$OUT"
printf '%s\n' "$OUT" | head -1 | grep -q '^\[Clauderizer\]'
printf '%s\n' "$OUT" | grep -qv 'engine unreachable'