Quickstart #152
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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' |