23f7624596
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
473 lines
24 KiB
JavaScript
Executable File
473 lines
24 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
// test-mcp-tools.mjs — runtime test for the iter-20/21 MCP tool registry.
|
|
//
|
|
// tsc proves the metaharness-tools.ts module COMPILES; structural smoke
|
|
// proves the source DECLARES the right tool names. Neither proves the
|
|
// HANDLERS actually run without throwing. This test imports the compiled
|
|
// module and invokes every tool's handler with minimal inputs.
|
|
//
|
|
// CONTRACT EACH TOOL MUST SATISFY
|
|
// - handler is callable as `await tool.handler({ ... })`
|
|
// - returns an object with keys: success, data, degraded, exitCode
|
|
// - never throws (even with bad/missing optional dep — graceful)
|
|
// - handler honors the 120s subprocess timeout (no hang)
|
|
//
|
|
// USAGE
|
|
// node scripts/test-mcp-tools.mjs # default
|
|
// node scripts/test-mcp-tools.mjs --format json
|
|
//
|
|
// EXIT CODES
|
|
// 0 all tools satisfy the contract
|
|
// 1 at least one tool failed
|
|
// 2 setup error (compiled dist not present)
|
|
|
|
import { existsSync } from 'node:fs';
|
|
import { dirname, join, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const SCRIPTS_DIR = dirname(fileURLToPath(import.meta.url));
|
|
const ARGS = (() => {
|
|
const a = { format: 'table' };
|
|
for (let i = 2; i < process.argv.length; i++) {
|
|
if (process.argv[i] === '--format') a.format = process.argv[++i];
|
|
}
|
|
return a;
|
|
})();
|
|
|
|
let passed = 0, failed = 0;
|
|
const failures = [];
|
|
|
|
function assert(cond, label) {
|
|
if (cond) { console.log(` ✓ ${label}`); passed++; }
|
|
else { console.log(` ✗ ${label}`); failures.push(label); failed++; }
|
|
}
|
|
|
|
async function main() {
|
|
// Locate the compiled dist of metaharness-tools.
|
|
const distPath = resolve(SCRIPTS_DIR, '..', '..', '..',
|
|
'v3', '@claude-flow', 'cli', 'dist', 'src', 'mcp-tools', 'metaharness-tools.js');
|
|
|
|
if (!existsSync(distPath)) {
|
|
console.log(`# test-mcp-tools — SKIPPED`);
|
|
console.log('');
|
|
console.log(`Compiled dist not present: ${distPath}`);
|
|
console.log(`Build the CLI first:`);
|
|
console.log(` cd v3/@claude-flow/cli && npm run build`);
|
|
console.log('');
|
|
console.log(`Exit 0 — this script is meaningfully runnable only post-build.`);
|
|
process.exit(0);
|
|
}
|
|
|
|
let mod;
|
|
try {
|
|
mod = await import(distPath);
|
|
} catch (e) {
|
|
console.error(`test-mcp-tools: failed to import ${distPath}: ${e.message}`);
|
|
process.exit(2);
|
|
}
|
|
|
|
const tools = mod.metaharnessTools;
|
|
console.log(`# test-mcp-tools — runtime contract\n`);
|
|
|
|
// ──────────────────────────────────────────────────────────────────
|
|
// PHASE 1 — module exports the right shape
|
|
// ──────────────────────────────────────────────────────────────────
|
|
console.log('Phase 1 — module shape');
|
|
assert(Array.isArray(tools), 'metaharnessTools is an array');
|
|
assert(tools.length === 15, `15 tools registered (got ${tools.length})`);
|
|
|
|
const expectedNames = new Set([
|
|
'metaharness_score',
|
|
'metaharness_genome',
|
|
'metaharness_mcp_scan',
|
|
'metaharness_threat_model',
|
|
'metaharness_oia_audit',
|
|
'metaharness_audit_list',
|
|
'metaharness_audit_trend',
|
|
// iter 36 — ADR-152 §3.1 production
|
|
'metaharness_similarity',
|
|
// iter 54 — one-command drift detection (composes audit-list + oia-audit + audit-trend)
|
|
'metaharness_drift_from_history',
|
|
// ADR-153 — bench suites + evolve driver + security-focused bench
|
|
'metaharness_bench',
|
|
'metaharness_evolve',
|
|
'metaharness_security_bench',
|
|
// @metaharness/redblue@~0.1.4 — adversarial red/blue LLM testing
|
|
'metaharness_redblue',
|
|
// metaharness@0.3.0 — upstream ADR-235 GEPA learning run
|
|
'metaharness_learn',
|
|
// @metaharness/darwin@0.8.0 — GEPA library surface (genome ops)
|
|
'metaharness_gepa',
|
|
]);
|
|
const actualNames = new Set(tools.map((t) => t.name));
|
|
for (const name of expectedNames) {
|
|
assert(actualNames.has(name), `${name} registered`);
|
|
}
|
|
|
|
// ──────────────────────────────────────────────────────────────────
|
|
// PHASE 2 — every tool has the required MCP shape
|
|
// ──────────────────────────────────────────────────────────────────
|
|
console.log('\nPhase 2 — per-tool shape');
|
|
for (const tool of tools) {
|
|
const ok = typeof tool.name === 'string'
|
|
&& typeof tool.description === 'string'
|
|
&& typeof tool.category === 'string'
|
|
&& typeof tool.handler === 'function'
|
|
&& typeof tool.inputSchema === 'object';
|
|
assert(ok, `${tool.name} has {name, description, category, handler, inputSchema}`);
|
|
assert(tool.category === 'metaharness', `${tool.name} category === 'metaharness'`);
|
|
}
|
|
|
|
// ──────────────────────────────────────────────────────────────────
|
|
// PHASE 3 — handlers callable + return contract shape
|
|
//
|
|
// We invoke each handler with minimal valid input. The handlers may
|
|
// succeed (if metaharness is installed) or report degraded (if not).
|
|
// EITHER way, they must return { success, data, degraded, exitCode }
|
|
// without throwing.
|
|
// ──────────────────────────────────────────────────────────────────
|
|
console.log('\nPhase 3 — handler invocations (allow up to 30s each)');
|
|
for (const tool of tools) {
|
|
// Construct minimal valid input per tool.
|
|
let input = {};
|
|
if (tool.name === 'metaharness_audit_trend') {
|
|
// Requires baselineKey + currentKey — use fake keys that won't
|
|
// resolve so we exercise the not-found path.
|
|
input = { baselineKey: 'audit-fake-base', currentKey: 'audit-fake-curr' };
|
|
}
|
|
if (tool.name === 'metaharness_similarity') {
|
|
// Needs --a/--b OR --a-key/--b-key. Use fake mem keys to exercise
|
|
// the graceful not-found path (matches audit_trend convention).
|
|
input = { aKey: 'harness-fake-a', bKey: 'harness-fake-b' };
|
|
}
|
|
if (tool.name === 'metaharness_drift_from_history') {
|
|
// iter 54 — composes 3 subprocesses, needs more time than the default.
|
|
input = { dryRun: true, threshold: 0.5 };
|
|
}
|
|
if (tool.name === 'metaharness_oia_audit') {
|
|
// iter 128 — composite audit runs 5 sub-audits (oia-manifest +
|
|
// threat-model + mcp-scan + score + genome) in parallel. Each
|
|
// shells out via npx. --dry-run skips memory persistence so the
|
|
// test doesn't pollute namespaces.
|
|
input = { dryRun: true };
|
|
}
|
|
if (tool.name === 'metaharness_redblue') {
|
|
// `attack` preview is the fastest path that exercises the upstream
|
|
// binary without needing OPENROUTER_API_KEY or running any model
|
|
// calls. Count=1 keeps cold-cache npx fetch the dominant cost.
|
|
input = { subcommand: 'attack', family: 'prompt', count: 1 };
|
|
}
|
|
if (tool.name === 'metaharness_learn') {
|
|
// No repo checkout in CI → structured {status:"checkout-required"}
|
|
// exit-0 path. $0: without run=true upstream never spends anyway.
|
|
input = {};
|
|
}
|
|
if (tool.name === 'metaharness_gepa') {
|
|
// op is required; `genome` loads + validates the SHIPPED cand-6
|
|
// genome — pure-local library call once darwin is cached.
|
|
input = { op: 'genome' };
|
|
}
|
|
|
|
// iter 124 → 130 — timeouts have crept up as CI cold-cache npx
|
|
// warmup costs got measured. Final budgets:
|
|
// default : 60s
|
|
// chain-tools : 180s (drift_from_history + oia_audit + audit_list)
|
|
// iter 131 — bumped chain-tool budget 90s → 180s. audit_list still
|
|
// timed out at 90s in CI; locally it runs in ~4s, but CI's
|
|
// `npx @claude-flow/cli@latest memory list` invocation pays both
|
|
// the npx fetch AND a full CLI startup (which loads agentic-flow +
|
|
// ONNX). 180s gives 30x headroom over the local cost.
|
|
const isChainTool = tool.name === 'metaharness_drift_from_history'
|
|
|| tool.name === 'metaharness_oia_audit'
|
|
|| tool.name === 'metaharness_audit_list'
|
|
// redblue: `attack prompt --count 1` is preview-only (no model
|
|
// calls) but the cold-cache `npx -y @metaharness/redblue@~0.1.4`
|
|
// fetch can take 30-60s. 180s gives 3x headroom.
|
|
|| tool.name === 'metaharness_redblue'
|
|
// learn: cold-cache `npx -y metaharness@latest` fetch dominates.
|
|
// gepa: one-time `npm install --prefix ~/.ruflo/darwin-cache-*`
|
|
// fallback install can take 30-60s on cold cache.
|
|
|| tool.name === 'metaharness_learn'
|
|
|| tool.name === 'metaharness_gepa';
|
|
const timeoutMs = isChainTool ? 180_000 : 60_000;
|
|
const handlerPromise = tool.handler(input);
|
|
const timeoutPromise = new Promise((_, reject) =>
|
|
setTimeout(() => reject(new Error(`${timeoutMs / 1000}s handler timeout`)), timeoutMs));
|
|
|
|
let result;
|
|
let threw = false;
|
|
try {
|
|
result = await Promise.race([handlerPromise, timeoutPromise]);
|
|
} catch (e) {
|
|
threw = true;
|
|
console.log(` [${tool.name}] handler threw: ${e.message.slice(0, 80)}`);
|
|
}
|
|
|
|
assert(!threw, `${tool.name} handler did not throw`);
|
|
if (!threw && result) {
|
|
assert(typeof result === 'object', `${tool.name} returns object`);
|
|
assert('success' in result, `${tool.name} result has 'success'`);
|
|
assert('data' in result, `${tool.name} result has 'data'`);
|
|
assert('degraded' in result, `${tool.name} result has 'degraded'`);
|
|
assert('exitCode' in result, `${tool.name} result has 'exitCode'`);
|
|
}
|
|
}
|
|
|
|
// ──────────────────────────────────────────────────────────────────
|
|
// PHASE 4 — POSITIVE-CASE data-shape validation (iter 43)
|
|
//
|
|
// Iter 37 verified the {success, data, degraded, exitCode} envelope.
|
|
// It did NOT verify that data.X contains the right keys when success
|
|
// is genuinely true — leaving room for iter 42-style bugs where a
|
|
// handler returns valid-looking degraded JSON while silently
|
|
// misrouting input. This phase invokes each handler with VALID
|
|
// inputs and asserts the expected output shape.
|
|
//
|
|
// Tools that depend on `npx metaharness` (score/genome/mcp-scan/
|
|
// threat-model/oia-audit/audit-list/audit-trend) are SKIPPED in this
|
|
// phase when the optional dep isn't installed — they're covered by
|
|
// the no-metaharness-smoke workflow's drill. The similarity tool
|
|
// has no @metaharness/* dep, so its positive case ALWAYS runs.
|
|
// ──────────────────────────────────────────────────────────────────
|
|
console.log('\nPhase 4 — positive-case data shape (iter 43)');
|
|
|
|
const { writeFileSync, mkdtempSync } = await import('node:fs');
|
|
const { tmpdir } = await import('node:os');
|
|
const { join: pjoin } = await import('node:path');
|
|
const tmp = mkdtempSync(pjoin(tmpdir(), 'mcp-positive-'));
|
|
|
|
// metaharness_similarity — full positive case (no @metaharness/* needed)
|
|
const simTool = tools.find((t) => t.name === 'metaharness_similarity');
|
|
if (simTool) {
|
|
const aPath = pjoin(tmp, 'a.json');
|
|
const bPath = pjoin(tmp, 'b.json');
|
|
writeFileSync(aPath, JSON.stringify({
|
|
score: { harnessFit: 78, compileConfidence: 92, taskCoverage: 65, toolSafety: 88, memoryUsefulness: 70, estCostPerRunUsd: 0.04, recommendedMode: 'CLI + MCP', archetype: 'compliance-harness', template: 'vertical:legal' },
|
|
genome: { repo_type: 'node_mcp_ci', agent_topology: ['x','y','z','w'], risk_score: 0.45, test_confidence: 0.7, publish_readiness: 0.6 },
|
|
}));
|
|
writeFileSync(bPath, JSON.stringify({
|
|
score: { harnessFit: 75, compileConfidence: 90, taskCoverage: 70, toolSafety: 90, memoryUsefulness: 72, estCostPerRunUsd: 0.05, recommendedMode: 'CLI + MCP', archetype: 'compliance-harness', template: 'vertical:support' },
|
|
genome: { repo_type: 'node_mcp_ci', agent_topology: ['x','y','z','q','r'], risk_score: 0.40, test_confidence: 0.75, publish_readiness: 0.65 },
|
|
}));
|
|
const r = await simTool.handler({ aFile: aPath, bFile: bPath });
|
|
assert(r.degraded === false, 'similarity positive case: degraded === false');
|
|
assert(r.success === true, 'similarity positive case: success === true');
|
|
assert(r.exitCode === 0, 'similarity positive case: exitCode === 0');
|
|
const d = r.data ?? {};
|
|
assert(typeof d.overall === 'number', 'similarity data has numeric `overall`');
|
|
assert(typeof d.components === 'object' && d.components !== null,
|
|
'similarity data has `components` object');
|
|
assert(typeof d.components?.cosine === 'number',
|
|
'similarity components.cosine numeric');
|
|
assert(typeof d.components?.categorical === 'number',
|
|
'similarity components.categorical numeric');
|
|
assert(typeof d.components?.jaccard === 'number',
|
|
'similarity components.jaccard numeric');
|
|
assert(typeof d.weights === 'object' && d.weights !== null,
|
|
'similarity data has `weights` object');
|
|
assert(d.adr === 'ADR-152', 'similarity data tagged adr=ADR-152');
|
|
// Regression anchor — same fixtures as iter-35 spike with non-matching topologies
|
|
assert(d.overall > 0 && d.overall < 1,
|
|
`similarity overall in (0, 1) — got ${d.overall}`);
|
|
|
|
// Per-dimension variant
|
|
const rPD = await simTool.handler({ aFile: aPath, bFile: bPath, perDimension: true });
|
|
assert(typeof rPD.data?.perDimension === 'object',
|
|
'similarity perDimension=true populates breakdown');
|
|
|
|
// Alert-below variant exercises non-zero exit
|
|
const rAlert = await simTool.handler({ aFile: aPath, bFile: bPath, alertBelow: 0.99 });
|
|
assert(rAlert.data?.alert?.triggered === true,
|
|
'similarity alertBelow=0.99 triggers alert');
|
|
assert(rAlert.exitCode === 1, 'similarity alertBelow=0.99 → exitCode 1');
|
|
// iter 44 — success semantic anchor (was true under the pre-iter-44
|
|
// `!degraded` rule; now false because exitCode !== 0 dominates).
|
|
assert(rAlert.success === false,
|
|
'similarity alertBelow=0.99 → success === false (iter 44 fix)');
|
|
}
|
|
|
|
// metaharness_mcp_scan — positive case post iter-50 parser landing.
|
|
// Until iter 50, mcp_scan's data field was an alert-only object with
|
|
// no structured findings. After iter 50, findings[] is always present
|
|
// (parsed from upstream text) and summary{overallSeverity, totalCount}
|
|
// accompanies it.
|
|
const scanTool = tools.find((t) => t.name === 'metaharness_mcp_scan');
|
|
if (scanTool) {
|
|
// Run against ruflo itself — guaranteed to produce at least the
|
|
// INFO finding the iter-50 parser test verified manually.
|
|
const r = await scanTool.handler({ path: '.', failOn: 'high' });
|
|
// Either succeeds with structured findings, or gracefully degrades
|
|
// if metaharness isn't installed in this environment.
|
|
if (!r.degraded) {
|
|
assert(r.success === true, 'mcp_scan positive: success === true');
|
|
assert(r.exitCode === 0, 'mcp_scan positive: exitCode === 0');
|
|
assert(Array.isArray(r.data?.findings),
|
|
'mcp_scan positive: data.findings is an array (iter 50 fix)');
|
|
// Cwd-dependent: when scanning a dir without .mcp/servers.json the
|
|
// upstream emits no findings. Only verify shape contract when array
|
|
// is populated — the array-presence assertion above is the
|
|
// load-bearing one for iter 50.
|
|
if (r.data?.findings.length > 0) {
|
|
const first = r.data.findings[0];
|
|
assert(typeof first?.severity === 'string',
|
|
'mcp_scan positive: first finding has string severity');
|
|
assert(typeof first?.message === 'string',
|
|
'mcp_scan positive: first finding has string message');
|
|
}
|
|
// summary may be null if the upstream produced no Result: line —
|
|
// verify the field's presence (null OR object) but only deep-check
|
|
// when populated.
|
|
if (r.data?.summary) {
|
|
assert(typeof r.data.summary.totalCount === 'number',
|
|
'mcp_scan positive: data.summary.totalCount is numeric (when summary present)');
|
|
}
|
|
} else {
|
|
console.log(` ⊘ mcp_scan: metaharness absent — graceful skip`);
|
|
}
|
|
}
|
|
|
|
// metaharness_audit_trend — positive case via file inputs
|
|
const trendTool = tools.find((t) => t.name === 'metaharness_audit_trend');
|
|
if (trendTool) {
|
|
const basePath = pjoin(tmp, 'base.json');
|
|
const currPath = pjoin(tmp, 'curr.json');
|
|
const fingerprint = {
|
|
score: { harnessFit: 80, recommendedMode: 'CLI + MCP', archetype: 'typescript-sdk-harness', template: 'vertical:coding' },
|
|
genome: { repo_type: 'node_mcp_ci', agent_topology: ['a','b','c'], risk_score: 0.3, test_confidence: 0.85, publish_readiness: 0.9 },
|
|
};
|
|
writeFileSync(basePath, JSON.stringify({
|
|
startedAt: '2026-06-15T00:00:00Z',
|
|
composite: { worst: 'clean' },
|
|
components: { oiaManifest: {}, threatModel: {}, mcpScan: { json: { findings: [] } } },
|
|
fingerprint,
|
|
}));
|
|
writeFileSync(currPath, JSON.stringify({
|
|
startedAt: '2026-06-16T00:00:00Z',
|
|
composite: { worst: 'clean' },
|
|
components: { oiaManifest: {}, threatModel: {}, mcpScan: { json: { findings: [] } } },
|
|
fingerprint,
|
|
}));
|
|
// audit_trend tool only supports keys, not files at the MCP layer.
|
|
// Document its actual wrapper semantics so future-us doesn't get
|
|
// surprised:
|
|
// - bad keys → script exits 2 with stderr (no JSON payload)
|
|
// - runScript() can't parse a {degraded:true} marker, so it
|
|
// returns degraded:false / success:true / exitCode:2
|
|
// This is a real wrapper bug (success should not be true when
|
|
// exit!=0 AND no JSON came back), tracked separately. Asserting
|
|
// current behavior here protects against silent semantic shifts.
|
|
// iter 46 — file-input path. audit_trend now accepts baselineFile/currentFile.
|
|
const rFiles = await trendTool.handler({ baselineFile: basePath, currentFile: currPath });
|
|
assert(rFiles.success === true,
|
|
'audit_trend file-input path: success === true (iter 46)');
|
|
assert(rFiles.exitCode === 0, 'audit_trend file-input path: exitCode === 0');
|
|
assert(typeof rFiles.data?.delta === 'object',
|
|
'audit_trend file-input path: data.delta object present');
|
|
assert(rFiles.data?.delta?.structuralDistance?.verdict === 'near-identical',
|
|
`audit_trend file-input path: identical fingerprints → near-identical (got ${rFiles.data?.delta?.structuralDistance?.verdict})`);
|
|
|
|
// iter 54 — metaharness_drift_from_history positive case
|
|
const driftTool = tools.find((t) => t.name === 'metaharness_drift_from_history');
|
|
if (driftTool) {
|
|
// iter 71 — verify iter-66/67 fast-path flags are now MCP-callable
|
|
// Synthesize a baseline file on disk; pass via the new baselineFile input.
|
|
const baselinePath = pjoin(tmp, 'drift-baseline.json');
|
|
writeFileSync(baselinePath, JSON.stringify({
|
|
startedAt: '2026-06-16T00:00:00Z',
|
|
composite: { worst: 'clean' },
|
|
components: { oiaManifest: {}, threatModel: {}, mcpScan: { json: { findings: [] } } },
|
|
fingerprint: {
|
|
score: { harnessFit: 82, recommendedMode: 'CLI + MCP', archetype: 'typescript-sdk-harness', template: 'vertical:coding' },
|
|
genome: { repo_type: 'node_mcp_ci', agent_topology: ['m', 't'], risk_score: 0.3 },
|
|
},
|
|
}));
|
|
const rFastFast = await driftTool.handler({
|
|
path: '.', dryRun: true, threshold: 0.5, baselineFile: baselinePath,
|
|
});
|
|
if (!rFastFast.degraded) {
|
|
assert(rFastFast.data?.timing?.usedBaselineFile === true,
|
|
'drift_from_history MCP-layer: baselineFile fastpath fires (iter 71)');
|
|
assert(rFastFast.data?.timing?.skippedAuditList === true,
|
|
'drift_from_history MCP-layer: skippedAuditList=true via baselineFile (iter 71)');
|
|
}
|
|
|
|
// iter 85 — verify iter-78's alertOnNewSeverity MCP input plumbs
|
|
// through. baselineFile has no findings; current ruflo audit has
|
|
// 1 INFO finding. With alertOnNewSeverity='info' the gate fires
|
|
// and surfaces in the response.
|
|
const baselineNoFindings = pjoin(tmp, 'drift-baseline-no-findings.json');
|
|
writeFileSync(baselineNoFindings, JSON.stringify({
|
|
startedAt: '2026-06-16T00:00:00Z',
|
|
composite: { worst: 'clean' },
|
|
components: { oiaManifest: {}, threatModel: {}, mcpScan: { json: { findings: [] } } },
|
|
fingerprint: {
|
|
score: { harnessFit: 82, recommendedMode: 'CLI + MCP', archetype: 'typescript-sdk-harness', template: 'vertical:coding' },
|
|
genome: { repo_type: 'node_mcp_ci', agent_topology: ['m', 't'], risk_score: 0.3 },
|
|
},
|
|
}));
|
|
const rSevAlert = await driftTool.handler({
|
|
path: '.', dryRun: true, threshold: 0.5,
|
|
baselineFile: baselineNoFindings,
|
|
alertOnNewSeverity: 'info',
|
|
});
|
|
if (!rSevAlert.degraded) {
|
|
assert(rSevAlert.data?.alert?.newSeverityThreshold === 'info',
|
|
'drift_from_history MCP-layer: alertOnNewSeverity echoed in payload (iter 85)');
|
|
// Triggered AND exit code reflects (only if the audit actually had findings)
|
|
if (rSevAlert.data?.alert?.triggered === true) {
|
|
assert(rSevAlert.exitCode === 1,
|
|
`drift_from_history MCP-layer: alertOnNewSeverity exitCode=1 when triggered (got ${rSevAlert.exitCode})`);
|
|
assert(rSevAlert.success === false,
|
|
'drift_from_history MCP-layer: success===false when alert fires (iter 44 fix)');
|
|
}
|
|
}
|
|
|
|
const r54 = await driftTool.handler({ path: '.', dryRun: true, threshold: 0.5 });
|
|
if (!r54.degraded) {
|
|
assert(typeof r54.data === 'object' && r54.data !== null,
|
|
'drift_from_history positive: data is an object');
|
|
// Either it produced the structured drift report OR the no-history error
|
|
const isOk = r54.data?.command === 'drift-from-history';
|
|
const isNoHistory = typeof r54.data?.error === 'string' && r54.data.error.includes('no audit records');
|
|
assert(isOk || isNoHistory,
|
|
`drift_from_history positive: structured report OR no-history error (got ${JSON.stringify(r54.data).slice(0,80)})`);
|
|
if (isOk) {
|
|
assert(typeof r54.data.baseline?.key === 'string',
|
|
'drift_from_history: baseline.key is a string');
|
|
assert(typeof r54.data.alert?.threshold === 'number',
|
|
'drift_from_history: alert.threshold echoed numerically');
|
|
}
|
|
} else {
|
|
console.log(` ⊘ drift_from_history: degraded (metaharness or memory absent)`);
|
|
}
|
|
}
|
|
|
|
const r = await trendTool.handler({ baselineKey: 'missing-X', currentKey: 'missing-Y' });
|
|
assert(r.exitCode === 2,
|
|
'audit_trend bad-keys path exits 2 (script-level guard fires)');
|
|
assert(r.data === null || r.data === undefined,
|
|
'audit_trend bad-keys path: data null (no JSON emitted on stderr exit)');
|
|
// iter 44 — success semantic anchor. Pre-iter-44 wrapper returned
|
|
// success:true for this case (because no degraded marker). Now
|
|
// returns false because exitCode !== 0.
|
|
assert(r.success === false,
|
|
'audit_trend bad-keys path: success === false (iter 44 fix)');
|
|
}
|
|
|
|
// Cleanup
|
|
try { (await import('node:fs')).rmSync(tmp, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
|
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
if (failed > 0) {
|
|
console.log('\nFailures:');
|
|
for (const f of failures) console.log(` - ${f}`);
|
|
process.exit(1);
|
|
}
|
|
console.log('\n✓ All 15 MCP tools satisfy the runtime contract.');
|
|
}
|
|
|
|
main().catch((e) => {
|
|
console.error('test-mcp-tools crashed:', e.message || e);
|
|
process.exit(2);
|
|
});
|