mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-14 20:25:26 +02:00
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>
177 lines
6.7 KiB
JavaScript
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()
|