Files
ECC/tests/pi/agent-emit.test.js
T
haelyraandalizahidraja eb7ffd8d64 feat(agents): integrate isolated Pi conversion foundation from PR 3022
Carry the canonical IR, Pi emitter and source-schema doctor from https://github.com/affaan-m/ECC/pull/3022. Cursor and OpenCode emitters remain with the open source PR pending capability fixes.

Co-authored-by: alizahidraja <alizahidrajaa@gmail.com>
2026-10-10 17:43:18 -04:00

177 lines
6.7 KiB
JavaScript

const AGENT_COUNT = require('fs').readdirSync(require('path').join(__dirname,'../../agents')).filter(f => f.endsWith('.md')).length;
/**
* Tests for the ECC Agent IR → Pi emitter (scripts/lib/agent-emit-pi.js).
*
* Verifies that every IR object becomes a valid Pi subagent definition against
* the verified `@tintinweb/pi-subagents@0.19.0` surface: `name` / `description`
* / `tools` (comma-separated allowlist) / `prompt_mode`, with the prompt in the
* body. Also enforces the permission boundary and the empty-allowlist guard.
*/
const assert = require("assert")
const yaml = require("js-yaml")
const { parseAllAgents } = require("../../scripts/lib/agent-ir")
const { emitPiAgent, emitAllPiAgents, RESERVED_AGENT_NAMES } = require("../../scripts/lib/agent-emit-pi")
const { CLAUDE_TO_PI_TOOLS, mapToolToPi } = require("../../scripts/lib/agent-tool-map")
const VALID_PI_TOOLS = new Set(Object.values(CLAUDE_TO_PI_TOOLS))
function runTest(name, fn) {
try {
fn()
console.log(` ✓ ${name}`)
return true
} catch (error) {
console.log(` ✗ ${name}`)
console.error(` ${error.message}`)
return false
}
}
function parseEmittedFrontmatter(markdown) {
const match = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---/)
assert.ok(match, "emitted markdown must start with a frontmatter block")
const out = {}
for (const line of match[1].split(/\r?\n/)) {
const idx = line.indexOf(":")
if (idx <= 0) continue
const key = line.slice(0, idx).trim()
out[key] = line.slice(idx + 1).trim()
}
return out
}
function main() {
let passed = 0
let failed = 0
let irs
let results
let warnings
try {
irs = parseAllAgents()
;({ results, warnings } = emitAllPiAgents(irs))
} catch (error) {
console.log(` ✗ setup failed: ${error.message}`)
console.log("\nPassed: 0")
console.log("Failed: 1")
process.exit(1)
}
const byId = new Map(irs.map(ir => [ir.id, ir]))
const tests = [
["emits all AGENT_COUNT agents", () => {
assert.strictEqual(results.length, AGENT_COUNT)
}],
["frontmatter uses the verified prompt_mode contract (no package, no systemPromptMode)", () => {
for (const r of results) {
const fm = parseEmittedFrontmatter(r.markdown)
assert.strictEqual(fm.prompt_mode, "replace", `${r.id}: prompt_mode must be replace`)
assert.strictEqual(fm.systemPromptMode, undefined, `${r.id}: systemPromptMode must not be emitted`)
assert.strictEqual(fm.package, undefined, `${r.id}: package must not be emitted (name is the dispatch id)`)
assert.strictEqual(fm.model, undefined, `${r.id}: model must be omitted (Pi default applies)`)
assert.strictEqual(fm.name, r.name, `${r.id}: name mismatch`)
assert.ok(fm.description.length > 0, `${r.id}: empty description`)
}
}],
["tool allowlist contains only verified Pi built-ins", () => {
for (const r of results) {
for (const tool of r.tools) {
assert.ok(VALID_PI_TOOLS.has(tool), `${r.id}: invalid Pi tool '${tool}'`)
}
}
}],
["no Claude tool name leaks into the Pi allowlist", () => {
const claudeNames = new Set(["Read", "Grep", "Glob", "Bash", "Edit", "Write", "WebSearch", "WebFetch"])
for (const r of results) {
for (const tool of r.tools) {
assert.ok(!claudeNames.has(tool), `${r.id}: leaked Claude tool '${tool}'`)
}
}
}],
["read-only agents never gain bash", () => {
for (const r of results) {
const source = byId.get(r.id)
if (!source.tools.includes("Bash")) {
assert.ok(!r.tools.includes("bash"), `${r.id}: read-only agent must not emit 'bash'`)
}
}
}],
["mcp__* tools are never mapped to a tool", () => {
assert.ok(!results.some(r => r.tools.includes("mcp")), "no 'mcp' tool should be emitted")
assert.ok(warnings.some(w => w.includes("docs-lookup") && w.includes("MCP tool")), "docs-lookup MCP must warn")
}],
["every unsupported source tool is warned, never silently dropped", () => {
const unsupportedCount = irs.flatMap(ir => ir.tools).filter(t => mapToolToPi(t).unsupported).length
const warnedCount = warnings.filter(w => /not a Pi built-in|unmapped tool/.test(w)).length
assert.strictEqual(warnedCount, unsupportedCount, "each unsupported source tool must warn exactly once")
assert.ok(unsupportedCount > 0, "expected WebSearch/WebFetch/mcp__* to be unsupported")
}],
["empty allowlist is emitted as tools: none (never blank, never escalates)", () => {
const noTools = { id: "x", name: "x", description: "x", tools: ["WebSearch"], body: "hi" }
const { markdown, tools } = emitPiAgent(noTools)
assert.deepStrictEqual(tools, [], "WebSearch-only agent must have an empty allowlist")
assert.match(markdown, /^tools: none$/m, "empty allowlist must be emitted as 'tools: none'")
}],
["reserved built-in agent names are warned", () => {
const colliding = { id: "general-purpose", name: "general-purpose", description: "x", tools: ["Read"], body: "hi" }
const { warnings: w } = emitPiAgent(colliding)
assert.ok(w.some(x => x.includes("collides with a Pi built-in agent")), "built-in name collision must warn")
assert.ok(RESERVED_AGENT_NAMES.has("general-purpose"), "reserved set is pinned")
}],
["body is preserved losslessly", () => {
for (const ir of irs) {
const emitted = results.find(r => r.id === ir.id)
const body = ir.body.replace(/^\n+/, "").trimEnd()
assert.ok(emitted.markdown.includes(body), `${ir.id}: body not preserved`)
}
}],
["planner maps to read, grep, find", () => {
const planner = results.find(r => r.id === "planner")
assert.deepStrictEqual(planner.tools, ["read", "grep", "find"])
}],
["emission is deterministic", () => {
const a = emitAllPiAgents(irs).results.map(r => r.markdown).join("\n")
const b = emitAllPiAgents(irs).results.map(r => r.markdown).join("\n")
assert.strictEqual(a, b)
}],
["emitted frontmatter is valid YAML and round-trips key fields", () => {
for (const r of results) {
const match = r.markdown.match(/^---\r?\n([\s\S]*?)\r?\n---/)
const fm = yaml.load(match[1])
assert.strictEqual(fm.name, r.name, `${r.id}: name round-trip`)
assert.strictEqual(fm.prompt_mode, "replace", `${r.id}: prompt_mode round-trip`)
assert.deepStrictEqual(String(fm.tools).split(", "), r.tools, `${r.id}: tools round-trip`)
}
}],
]
for (const [name, fn] of tests) {
if (runTest(name, fn)) {
passed += 1
} else {
failed += 1
}
}
console.log(`\nPassed: ${passed}`)
console.log(`Failed: ${failed}`)
process.exit(failed > 0 ? 1 : 0)
}
main()