diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4bb17c0..0221999 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,6 +23,9 @@ jobs: - name: Install Python deps for correctness checks run: pip install pandas + - name: Install MCP deps + run: npm install --prefix ponytail-mcp + - name: Check rule copies run: node scripts/check-rule-copies.js diff --git a/benchmarks/correctness.js b/benchmarks/correctness.js index fc56611..b833ce3 100644 --- a/benchmarks/correctness.js +++ b/benchmarks/correctness.js @@ -11,6 +11,11 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); +function correctnessTimeoutMs() { + const value = Number.parseInt(process.env.PONYTAIL_CORRECTNESS_TIMEOUT_MS || '', 10); + return Number.isFinite(value) && value > 0 ? value : 30_000; +} + // Extract fenced code blocks, tagged by language. function extractBlocks(text) { text = String(text || ''); @@ -35,7 +40,7 @@ function identifyTask(task) { // Run a command, return { ok, stderr }. function exec(cmd, opts = {}) { try { - execSync(cmd, { timeout: 10_000, encoding: 'utf8', stdio: 'pipe', ...opts }); + execSync(cmd, { timeout: correctnessTimeoutMs(), encoding: 'utf8', stdio: 'pipe', ...opts }); return { ok: true, stderr: '' }; } catch (e) { return { ok: false, stderr: (e.stderr || e.message || '').slice(0, 500) }; diff --git a/plugin.yaml b/plugin.yaml index 701d3db..c479ffb 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -1,7 +1,7 @@ name: ponytail version: 4.8.4 description: Lazy senior dev mode for Hermes Agent, always-on context, bundled skills, and slash commands. -author: Salaamdev +author: Dietrich Gebert provides_hooks: - pre_llm_call - pre_gateway_dispatch diff --git a/tests/correctness.test.js b/tests/correctness.test.js index a3facc8..7341f49 100644 --- a/tests/correctness.test.js +++ b/tests/correctness.test.js @@ -106,6 +106,36 @@ test('csv: value containing 351 as substring fails (e.g. 13510)', () => { assert.equal(result.score, 0); }); +test('csv: timeout can be raised for slow pandas startup', () => { + const previous = process.env.PONYTAIL_CORRECTNESS_TIMEOUT_MS; + try { + process.env.PONYTAIL_CORRECTNESS_TIMEOUT_MS = '1'; + const timedOut = check( + "Write Python code that reads sales.csv and sums the 'amount' column.", + 'python', + `import time +time.sleep(0.05) +print(351)`, + ); + assert.equal(timedOut.pass, false); + assert.match(timedOut.reason, /ETIMEDOUT|timed out/i); + + process.env.PONYTAIL_CORRECTNESS_TIMEOUT_MS = '1000'; + const completed = check( + "Write Python code that reads sales.csv and sums the 'amount' column.", + 'python', + `import time +time.sleep(0.05) +print(351)`, + ); + assert.equal(completed.pass, true); + assert.equal(completed.score, 1); + } finally { + if (previous === undefined) delete process.env.PONYTAIL_CORRECTNESS_TIMEOUT_MS; + else process.env.PONYTAIL_CORRECTNESS_TIMEOUT_MS = previous; + } +}); + // --- React countdown --- test('countdown: valid React component passes', () => { diff --git a/tests/hermes-plugin.test.js b/tests/hermes-plugin.test.js index 344f36a..4e07010 100644 --- a/tests/hermes-plugin.test.js +++ b/tests/hermes-plugin.test.js @@ -51,6 +51,7 @@ test('Hermes plugin manifest matches runtime skills, hooks, commands, and packag assert.match(manifest, /^name:\s*ponytail$/m); assert.match(manifest, new RegExp(`^version:\\s*${packageJson.version}$`, 'm')); + assert.match(manifest, new RegExp(`^author:\\s*${packageJson.author.name}$`, 'm')); assert.deepEqual(commands.filter((name) => manifest.includes(` - ${name}`)), commands); assert.deepEqual(skillDirs.filter((name) => manifest.includes(` - ${name}`)), skillDirs); assert.match(manifest, /pre_llm_call/); diff --git a/tests/package-scripts.test.js b/tests/package-scripts.test.js new file mode 100644 index 0000000..3bea2a1 --- /dev/null +++ b/tests/package-scripts.test.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node + +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); + +const root = path.join(__dirname, '..'); + +test('root npm test covers bundled subprojects', () => { + const packageJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); + + assert.match(packageJson.scripts.test, /npm test --prefix pi-extension/); + assert.match(packageJson.scripts.test, /npm test --prefix ponytail-mcp/); +}); + +test('CI installs MCP dependencies before root npm test', () => { + const workflow = fs.readFileSync(path.join(root, '.github', 'workflows', 'test.yml'), 'utf8'); + + assert.match(workflow, /npm install --prefix ponytail-mcp/); + assert.ok( + workflow.indexOf('npm install --prefix ponytail-mcp') < workflow.indexOf('npm test'), + 'MCP dependencies must be installed before the root test command runs', + ); +});