diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..c8fc566 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: test + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Python deps for correctness checks + run: pip install pandas + + - name: Check rule copies + run: node scripts/check-rule-copies.js + + - name: Run tests + run: npm test diff --git a/README.md b/README.md index 7f15e3f..b76594a 100644 --- a/README.md +++ b/README.md @@ -165,8 +165,11 @@ When changing the compact rule text, keep the agent copies aligned: ```bash node scripts/check-rule-copies.js +npm test ``` +The correctness benchmark spawns Python for email and CSV checks; `python3` is tried before `python`. CSV checks need `pandas` installed locally. + ## FAQ **Does it need a config file?** diff --git a/benchmarks/correctness.js b/benchmarks/correctness.js index 23faccd..5ca1753 100644 --- a/benchmarks/correctness.js +++ b/benchmarks/correctness.js @@ -38,6 +38,20 @@ function exec(cmd, opts = {}) { } } +// ponytail: probe once at load; macOS and many Linux images ship python3 only. +let pythonCmd; +function python() { + if (pythonCmd) return pythonCmd; + for (const cmd of ['python3', 'python']) { + if (exec(`${cmd} -c "import sys"`).ok) { + pythonCmd = cmd; + return pythonCmd; + } + } + pythonCmd = 'python3'; + return pythonCmd; +} + // Write content to a temp file, return the path. function tmpFile(ext, content) { const p = path.join(os.tmpdir(), `ponytail-bench-${Date.now()}-${Math.random().toString(36).slice(2)}${ext}`); @@ -100,7 +114,7 @@ if failures: print("PASS") `; const f = tmpFile('.py', harness); - const result = exec(`python "${f}"`); + const result = exec(`${python()} "${f}"`); fs.unlinkSync(f); if (result.ok) return { pass: true, reason: 'Email validator passes all checks' }; return { pass: false, reason: result.stderr || 'Email validator failed' }; @@ -194,7 +208,7 @@ else: sys.exit(1) `; const f = tmpFile('.py', harness); - const result = exec(`python "${f}"`); + const result = exec(`${python()} "${f}"`); try { fs.unlinkSync(f); } catch (e) {} try { fs.unlinkSync(csvPath); } catch (e) {} if (result.ok) return { pass: true, reason: 'CSV sum produces correct result (351)' }; diff --git a/package.json b/package.json index 5a5ec4b..74587f1 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "description": "Lazy senior dev mode for AI agents. The best code is the code you never wrote.", "keywords": ["pi-package", "pi", "skills", "ponytail"], "license": "MIT", + "scripts": { + "test": "node --test tests/*.test.js && npm test --prefix pi-extension" + }, "pi": { "extensions": ["./pi-extension/index.js"], "skills": ["./skills"]