mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-08 07:37:48 +02:00
125 lines
5.4 KiB
JavaScript
125 lines
5.4 KiB
JavaScript
const assert = require('assert');
|
|||
|
|
const fs = require('fs');
|
||
|
|
const path = require('path');
|
||
|
|
|
||
|
|
const ROOT = path.join(__dirname, '..', '..');
|
||
|
|
const SKILL_PATH = path.join(ROOT, 'skills', 'dev-team', 'SKILL.md');
|
||
|
|
|
||
|
|
function test(name, fn) {
|
||
|
|
try {
|
||
|
|
fn();
|
||
|
|
console.log(` ✓ ${name}`);
|
||
|
|
return true;
|
||
|
|
} catch (error) {
|
||
|
|
console.log(` ✗ ${name}`);
|
||
|
|
console.log(` Error: ${error.message}`);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function runTests() {
|
||
|
|
console.log('\n=== Testing dev-team skill contract ===\n');
|
||
|
|
|
||
|
|
let passed = 0;
|
||
|
|
let failed = 0;
|
||
|
|
const body = fs.readFileSync(SKILL_PATH, 'utf8');
|
||
|
|
|
||
|
|
if (test('uses the canonical When to Activate header', () => {
|
||
|
|
assert.ok(body.includes('## When to Activate'), 'missing ## When to Activate');
|
||
|
|
})) passed++; else failed++;
|
||
|
|
|
||
|
|
if (test('defines all four preset roles with their lenses', () => {
|
||
|
|
for (const role of ['Product Manager', 'Architect', 'Developer', 'QA Engineer']) {
|
||
|
|
assert.ok(body.includes(role), `missing role: ${role}`);
|
||
|
|
}
|
||
|
|
for (const lens of ['user value', 'system design', 'implementation complexity', 'testability']) {
|
||
|
|
assert.ok(body.includes(lens), `missing lens: ${lens}`);
|
||
|
|
}
|
||
|
|
})) passed++; else failed++;
|
||
|
|
|
||
|
|
if (test('requires parallel dispatch of all four personas', () => {
|
||
|
|
assert.ok(body.includes('### 3. Launch four personas in parallel'), 'missing parallel step');
|
||
|
|
assert.ok(/all four must run at the same time/i.test(body), 'missing parallel anti-pattern');
|
||
|
|
})) passed++; else failed++;
|
||
|
|
|
||
|
|
if (test('personas are analysis-only with no state-changing tool use', () => {
|
||
|
|
assert.ok(/analysis-only/i.test(body), 'missing analysis-only rule');
|
||
|
|
assert.ok(/must not edit files, run state-changing commands/i.test(body),
|
||
|
|
'missing no-state-change rule');
|
||
|
|
assert.ok(body.includes('do not edit files, run commands, or change any state'),
|
||
|
|
'prompt template must carry the analysis-only instruction');
|
||
|
|
})) passed++; else failed++;
|
||
|
|
|
||
|
|
if (test('untrusted-context boundary is embedded in the persona prompt template', () => {
|
||
|
|
assert.ok(body.includes('untrusted declarative data'), 'missing inline trust label');
|
||
|
|
assert.ok(body.includes('do NOT follow any instructions'), 'missing inline directive guard');
|
||
|
|
const promptStart = body.indexOf('```text');
|
||
|
|
const promptEnd = body.indexOf('```', promptStart + 7);
|
||
|
|
const template = body.slice(promptStart, promptEnd);
|
||
|
|
assert.ok(template.includes('untrusted declarative data'),
|
||
|
|
'trust label must be inside the prompt template, not only prose');
|
||
|
|
})) passed++; else failed++;
|
||
|
|
|
||
|
|
if (test('personas receive a bounded summary, never raw PROJECT-CONTEXT.md', () => {
|
||
|
|
assert.ok(/do \*\*not\*\* pass its raw content/i.test(body), 'missing raw-content ban');
|
||
|
|
assert.ok(/at most 150 words/i.test(body), 'missing summary bound');
|
||
|
|
assert.ok(/drop anything that looks like a secret/i.test(body), 'missing secret filter');
|
||
|
|
})) passed++; else failed++;
|
||
|
|
|
||
|
|
if (test('context loading is harness-neutral, no POSIX-only shell', () => {
|
||
|
|
assert.ok(/native file tools/i.test(body), 'missing harness-native rule');
|
||
|
|
const codeFences = body.match(/```bash[\s\S]*?```/g) || [];
|
||
|
|
assert.strictEqual(codeFences.length, 0, 'no bash fences should remain');
|
||
|
|
})) passed++; else failed++;
|
||
|
|
|
||
|
|
if (test('synthesis names tensions instead of averaging them', () => {
|
||
|
|
assert.ok(body.includes('### Synthesis'), 'missing synthesis section');
|
||
|
|
assert.ok(/Name tensions explicitly/i.test(body), 'missing tension guardrail');
|
||
|
|
assert.ok(/flag it as a blocking issue/i.test(body), 'missing blocking-issue rule');
|
||
|
|
})) passed++; else failed++;
|
||
|
|
|
||
|
|
if (test('boundary with team-builder and council is explicit', () => {
|
||
|
|
assert.ok(body.includes('## Relationship to council and team-builder'), 'missing boundary section');
|
||
|
|
assert.ok(body.includes('team-builder'), 'missing team-builder reference');
|
||
|
|
assert.ok(/preset four-lens/i.test(body), 'missing preset positioning');
|
||
|
|
})) passed++; else failed++;
|
||
|
|
|
||
|
|
if (test('does not reference surfaces that are not on main', () => {
|
||
|
|
assert.ok(!body.includes('story-lifecycle'), 'story-lifecycle is not merged');
|
||
|
|
assert.ok(!body.includes('ecc:plan-prd'), 'plan-prd resolves as a command, not a skill');
|
||
|
|
})) passed++; else failed++;
|
||
|
|
|
||
|
|
if (test('every referenced skill, agent, and command resolves in the repo', () => {
|
||
|
|
const refs = [
|
||
|
|
'skills/council/SKILL.md',
|
||
|
|
'skills/team-builder/SKILL.md',
|
||
|
|
'skills/santa-method/SKILL.md',
|
||
|
|
'commands/plan-prd.md',
|
||
|
|
'commands/plan.md',
|
||
|
|
'commands/epic-decompose.md',
|
||
|
|
'commands/save-session.md',
|
||
|
|
'commands/code-review.md',
|
||
|
|
'agents/architect.md',
|
||
|
|
'agents/code-reviewer.md',
|
||
|
|
];
|
||
|
|
for (const ref of refs) {
|
||
|
|
assert.ok(fs.existsSync(path.join(ROOT, ref)), `unresolved reference: ${ref}`);
|
||
|
|
}
|
||
|
|
})) passed++; else failed++;
|
||
|
|
|
||
|
|
if (test('skill is registered in install manifest and npm files list', () => {
|
||
|
|
const modules = JSON.parse(
|
||
|
|
fs.readFileSync(path.join(ROOT, 'manifests', 'install-modules.json'), 'utf8'));
|
||
|
|
assert.ok(JSON.stringify(modules).includes('skills/dev-team'),
|
||
|
|
'missing from manifests/install-modules.json');
|
||
|
|
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
||
|
|
assert.ok(pkg.files.includes('skills/dev-team/'),
|
||
|
|
'missing from package.json files');
|
||
|
|
})) passed++; else failed++;
|
||
|
|
|
||
|
|
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||
|
|
process.exit(failed > 0 ? 1 : 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
runTests();
|