chore: import upstream snapshot with attribution
CI / Test (ubuntu-latest, Node 18.x, bun) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, npm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, pnpm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, yarn) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 20.x, bun) (push) Failing after 17m13s
CI / Test (ubuntu-latest, Node 20.x, npm) (push) Failing after 18m42s
CI / Test (ubuntu-latest, Node 20.x, pnpm) (push) Failing after 15m0s
CI / Test (ubuntu-latest, Node 20.x, yarn) (push) Failing after 49m44s
CI / Test (ubuntu-latest, Node 22.x, bun) (push) Failing after 51m55s
CI / Test (ubuntu-latest, Node 22.x, pnpm) (push) Failing after 21m57s
CI / Test (ubuntu-latest, Node 22.x, npm) (push) Failing after 37m39s
CI / Test (ubuntu-latest, Node 22.x, yarn) (push) Failing after 34m7s
CI / Validate Components (push) Failing after 37m15s
CI / Python Tests (push) Failing after 10m1s
CI / Security Scan (push) Failing after 10m1s
CI / Lint (push) Failing after 17m12s
CI / Coverage (push) Failing after 20m19s
CI / Test (macos-latest, Node 18.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, yarn) (push) Has been cancelled
CI / Test (ubuntu-latest, Node 18.x, bun) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, npm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, pnpm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, yarn) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 20.x, bun) (push) Failing after 17m13s
CI / Test (ubuntu-latest, Node 20.x, npm) (push) Failing after 18m42s
CI / Test (ubuntu-latest, Node 20.x, pnpm) (push) Failing after 15m0s
CI / Test (ubuntu-latest, Node 20.x, yarn) (push) Failing after 49m44s
CI / Test (ubuntu-latest, Node 22.x, bun) (push) Failing after 51m55s
CI / Test (ubuntu-latest, Node 22.x, pnpm) (push) Failing after 21m57s
CI / Test (ubuntu-latest, Node 22.x, npm) (push) Failing after 37m39s
CI / Test (ubuntu-latest, Node 22.x, yarn) (push) Failing after 34m7s
CI / Validate Components (push) Failing after 37m15s
CI / Python Tests (push) Failing after 10m1s
CI / Security Scan (push) Failing after 10m1s
CI / Lint (push) Failing after 17m12s
CI / Coverage (push) Failing after 20m19s
CI / Test (macos-latest, Node 18.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, yarn) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const SKILL_PATH = path.join(__dirname, '..', '..', 'skills', 'canary-watch', 'SKILL.md');
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` \u2713 ${name}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(` \u2717 ${name}`);
|
||||
console.log(` Error: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
console.log('\n=== Testing canary-watch skill docs ===\n');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
const body = fs.readFileSync(SKILL_PATH, 'utf8');
|
||||
|
||||
if (test('description monitoring claims are backed by watch sections', () => {
|
||||
for (const phrase of [
|
||||
'HTTP endpoints',
|
||||
'SSE streams',
|
||||
'static assets',
|
||||
'console errors',
|
||||
'performance regressions',
|
||||
]) {
|
||||
assert.ok(body.toLowerCase().includes(phrase.toLowerCase()), `missing phrase: ${phrase}`);
|
||||
}
|
||||
assert.ok(body.includes('Static Assets'), 'watch list should include static assets');
|
||||
assert.ok(body.includes('SSE Streams'), 'watch list should include SSE streams');
|
||||
assert.ok(body.includes('SSE endpoint cannot connect'), 'critical thresholds should cover SSE failures');
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
runTests();
|
||||
@@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||
|
||||
const configureEccDocs = [
|
||||
'skills/configure-ecc/SKILL.md',
|
||||
'docs/zh-CN/skills/configure-ecc/SKILL.md',
|
||||
'docs/ja-JP/skills/configure-ecc/SKILL.md',
|
||||
];
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
passed++;
|
||||
} catch (error) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${error.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
function readConfigureEccDoc(relativePath) {
|
||||
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
}
|
||||
|
||||
console.log('\n=== Testing configure-ecc install path guidance ===\n');
|
||||
|
||||
for (const relativePath of configureEccDocs) {
|
||||
test(`${relativePath} separates core and niche skill source roots`, () => {
|
||||
const content = readConfigureEccDoc(relativePath);
|
||||
|
||||
assert.ok(
|
||||
content.includes('$ECC_ROOT/.agents/skills/<skill-name>'),
|
||||
'Expected configure-ecc to document the core skill source root'
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('$ECC_ROOT/skills/<skill-name>'),
|
||||
'Expected configure-ecc to document the niche skill source root'
|
||||
);
|
||||
});
|
||||
|
||||
test(`${relativePath} documents defensive copy form for trailing slash sources`, () => {
|
||||
const content = readConfigureEccDoc(relativePath);
|
||||
|
||||
assert.ok(
|
||||
content.includes('${src%/}'),
|
||||
'Expected configure-ecc to strip trailing slash before copying'
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('$(basename "${src%/}")'),
|
||||
'Expected configure-ecc to preserve the skill directory name explicitly'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (failed > 0) {
|
||||
console.log(`\nFailed: ${failed}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\nPassed: ${passed}`);
|
||||
@@ -0,0 +1,64 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
passed++;
|
||||
} catch (error) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${error.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
const skillDocs = [
|
||||
'skills/continuous-learning-v2/SKILL.md',
|
||||
'docs/zh-CN/skills/continuous-learning-v2/SKILL.md',
|
||||
'docs/tr/skills/continuous-learning-v2/SKILL.md',
|
||||
'docs/ko-KR/skills/continuous-learning-v2/SKILL.md',
|
||||
'docs/ja-JP/skills/continuous-learning-v2/SKILL.md',
|
||||
'docs/zh-TW/skills/continuous-learning-v2/SKILL.md',
|
||||
];
|
||||
|
||||
console.log('\n=== Testing continuous-learning-v2 install docs ===\n');
|
||||
|
||||
for (const relativePath of skillDocs) {
|
||||
const content = fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
|
||||
test(`${relativePath} does not tell plugin users to register observe.sh through CLAUDE_PLUGIN_ROOT`, () => {
|
||||
assert.ok(
|
||||
!content.includes('${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/hooks/observe.sh'),
|
||||
'Plugin quick start should not tell users to copy observe.sh into settings.json'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const englishSkill = fs.readFileSync(
|
||||
path.join(repoRoot, 'skills/continuous-learning-v2/SKILL.md'),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
test('English continuous-learning-v2 skill says plugin installs auto-load hooks/hooks.json', () => {
|
||||
assert.ok(englishSkill.includes('auto-loads the plugin `hooks/hooks.json`'));
|
||||
});
|
||||
|
||||
test('English continuous-learning-v2 skill tells plugin users to remove duplicated settings.json hooks', () => {
|
||||
assert.ok(englishSkill.includes('remove that duplicate `PreToolUse` / `PostToolUse` block'));
|
||||
});
|
||||
|
||||
if (failed > 0) {
|
||||
console.log(`\nFailed: ${failed}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\nPassed: ${passed}`);
|
||||
@@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||
const promptDir = path.join(repoRoot, '.github', 'prompts');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
passed++;
|
||||
} catch (error) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${error.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
function read(relativePath) {
|
||||
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
}
|
||||
|
||||
function parseSimpleFrontmatter(source, relativePath) {
|
||||
const normalizedSource = source.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n');
|
||||
const match = normalizedSource.match(/^---\n([\s\S]*?)\n---\n/);
|
||||
assert.ok(match, `${relativePath} must start with YAML frontmatter`);
|
||||
|
||||
const fields = {};
|
||||
for (const line of match[1].split('\n')) {
|
||||
const field = line.match(/^([A-Za-z0-9_-]+):\s*(.+)$/);
|
||||
assert.ok(field, `${relativePath} contains unsupported frontmatter line: ${line}`);
|
||||
fields[field[1]] = field[2];
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
console.log('\n=== Testing GitHub Copilot support surface ===\n');
|
||||
|
||||
test('VS Code settings enable Copilot prompt files', () => {
|
||||
const settings = JSON.parse(read('.vscode/settings.json'));
|
||||
assert.strictEqual(settings['chat.promptFiles'], true);
|
||||
assert.ok(!Object.prototype.hasOwnProperty.call(settings, 'github.copilot.chat.reviewSelection.instructions'));
|
||||
});
|
||||
|
||||
test('Copilot prompt files use current VS Code frontmatter', () => {
|
||||
const promptFiles = fs
|
||||
.readdirSync(promptDir)
|
||||
.filter(file => file.endsWith('.prompt.md'))
|
||||
.sort();
|
||||
|
||||
assert.deepStrictEqual(promptFiles, ['build-fix.prompt.md', 'plan.prompt.md', 'refactor.prompt.md', 'security-review.prompt.md', 'tdd.prompt.md']);
|
||||
|
||||
for (const file of promptFiles) {
|
||||
const relativePath = `.github/prompts/${file}`;
|
||||
const source = read(relativePath);
|
||||
const fields = parseSimpleFrontmatter(source, relativePath);
|
||||
|
||||
assert.strictEqual(fields.agent, 'agent', `${relativePath} must use agent: agent`);
|
||||
assert.ok(fields.description, `${relativePath} must describe its purpose`);
|
||||
assert.ok(!Object.prototype.hasOwnProperty.call(fields, 'mode'), `${relativePath} must not use legacy mode frontmatter`);
|
||||
}
|
||||
});
|
||||
|
||||
test('Copilot docs advertise slash prompt invocation instead of hash commands', () => {
|
||||
const sources = ['.github/copilot-instructions.md', 'README.md'].map(read).join('\n');
|
||||
|
||||
for (const command of ['plan', 'tdd', 'security-review', 'build-fix', 'refactor']) {
|
||||
assert.ok(!sources.includes(`#${command}`), `Expected no stale #${command} command syntax`);
|
||||
}
|
||||
|
||||
assert.ok(sources.includes('/plan'));
|
||||
assert.ok(sources.includes('/tdd'));
|
||||
assert.ok(sources.includes('/security-review'));
|
||||
});
|
||||
|
||||
test('Copilot instructions include a prompt defense baseline', () => {
|
||||
const instructions = read('.github/copilot-instructions.md');
|
||||
assert.ok(instructions.includes('## Prompt Defense Baseline'));
|
||||
assert.ok(instructions.includes('untrusted input'));
|
||||
assert.ok(instructions.includes('Never print tokens'));
|
||||
});
|
||||
|
||||
test('README documents prompt-file settings and surfaces', () => {
|
||||
const readme = read('README.md');
|
||||
assert.ok(readme.includes('chat.promptFiles'));
|
||||
assert.ok(readme.includes('.github/prompts/'));
|
||||
assert.ok(readme.includes('.vscode/settings.json'));
|
||||
});
|
||||
|
||||
if (failed > 0) {
|
||||
console.log(`\nFailed: ${failed}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\nPassed: ${passed}`);
|
||||
@@ -0,0 +1,544 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||
const releaseDir = path.join(repoRoot, 'docs', 'releases', '2.0.0-rc.1');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
passed++;
|
||||
} catch (error) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${error.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
function read(relativePath) {
|
||||
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
}
|
||||
|
||||
function walkMarkdown(rootPath) {
|
||||
const files = [];
|
||||
for (const entry of fs.readdirSync(rootPath, { withFileTypes: true })) {
|
||||
const nextPath = path.join(rootPath, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...walkMarkdown(nextPath));
|
||||
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
||||
files.push(nextPath);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
console.log('\n=== Testing ECC 2.0 release surface ===\n');
|
||||
|
||||
const expectedReleaseFiles = [
|
||||
'release-notes.md',
|
||||
'x-thread.md',
|
||||
'linkedin-post.md',
|
||||
'article-outline.md',
|
||||
'launch-checklist.md',
|
||||
'telegram-handoff.md',
|
||||
'demo-prompts.md',
|
||||
'quickstart.md',
|
||||
'preview-pack-manifest.md',
|
||||
'publication-readiness.md',
|
||||
'video-suite-production.md',
|
||||
'partner-sponsor-talks-pack.md',
|
||||
'owner-approval-packet-2026-05-19.md',
|
||||
'release-name-plugin-publication-checklist-2026-05-18.md'
|
||||
];
|
||||
|
||||
test('release candidate directory includes the public launch pack', () => {
|
||||
for (const fileName of expectedReleaseFiles) {
|
||||
assert.ok(fs.existsSync(path.join(releaseDir, fileName)), `Missing ${fileName}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('README links to Hermes setup and current release notes', () => {
|
||||
const readme = read('README.md');
|
||||
assert.ok(readme.includes('docs/HERMES-SETUP.md'), 'README must link to Hermes setup');
|
||||
assert.ok(readme.includes('docs/releases/2.0.0/release-notes.md'), 'README must link to the 2.0.0 release notes');
|
||||
});
|
||||
|
||||
test('cross-harness architecture doc exists and names core harnesses', () => {
|
||||
const source = read('docs/architecture/cross-harness.md');
|
||||
for (const harness of ['Claude Code', 'Codex', 'OpenCode', 'Cursor', 'Gemini', 'Hermes']) {
|
||||
assert.ok(source.includes(harness), `Expected cross-harness doc to mention ${harness}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('Hermes import skill exists and declares sanitization rules', () => {
|
||||
const source = read('skills/hermes-imports/SKILL.md');
|
||||
assert.ok(source.includes('name: hermes-imports'));
|
||||
assert.ok(source.includes('Sanitization Checklist'));
|
||||
assert.ok(source.includes('Do not ship raw workspace exports'));
|
||||
});
|
||||
|
||||
test('release docs do not contain private local workspace paths', () => {
|
||||
const offenders = [];
|
||||
for (const filePath of walkMarkdown(releaseDir)) {
|
||||
const source = fs.readFileSync(filePath, 'utf8');
|
||||
if (source.includes('/Users/') || source.includes('/.hermes/')) {
|
||||
offenders.push(path.relative(repoRoot, filePath));
|
||||
}
|
||||
}
|
||||
assert.deepStrictEqual(offenders, []);
|
||||
});
|
||||
|
||||
test('release docs do not contain unresolved public-link placeholders', () => {
|
||||
const offenders = [];
|
||||
for (const filePath of walkMarkdown(releaseDir)) {
|
||||
const source = fs.readFileSync(filePath, 'utf8');
|
||||
if (source.includes('<repo-link>')) {
|
||||
offenders.push(path.relative(repoRoot, filePath));
|
||||
}
|
||||
}
|
||||
assert.deepStrictEqual(offenders, []);
|
||||
});
|
||||
|
||||
test('business launch copy stays aligned with the rc.1 public surface', () => {
|
||||
const source = read('docs/business/social-launch-copy.md');
|
||||
assert.ok(source.includes('ECC v2.0.0-rc.1'), 'business launch copy should use the rc.1 release');
|
||||
assert.ok(source.includes('preview pack is ready for final release review'), 'business launch copy should stay pre-publication until release URLs exist');
|
||||
assert.ok(source.includes('https://github.com/affaan-m/ECC'), 'business launch copy should include the public repo URL');
|
||||
assert.ok(source.includes('https://github.com/affaan-m/ECC/blob/main/docs/releases/2.0.0-rc.1/release-notes.md'), 'business launch copy should link to the rc.1 release notes');
|
||||
assert.ok(!source.includes('<repo-link>'), 'business launch copy should not contain repo placeholders');
|
||||
assert.ok(!source.includes('v1.8.0'), 'business launch copy should not stay pinned to v1.8.0');
|
||||
});
|
||||
|
||||
test('announcement drafts avoid live-release claims before publication', () => {
|
||||
const announcementFiles = ['docs/releases/2.0.0-rc.1/linkedin-post.md', 'docs/releases/2.0.0-rc.1/partner-sponsor-talks-pack.md', 'docs/business/social-launch-copy.md'];
|
||||
|
||||
for (const relativePath of announcementFiles) {
|
||||
const source = read(relativePath);
|
||||
assert.ok(!/ECC v2\.0\.0-rc\.1 is live\./.test(source), `${relativePath} must not claim rc.1 is live before the release gate completes`);
|
||||
}
|
||||
});
|
||||
|
||||
test('Hermes setup uses release-candidate wording for the rc.1 surface', () => {
|
||||
const source = read('docs/HERMES-SETUP.md');
|
||||
assert.ok(source.includes('Public Release Candidate Scope'));
|
||||
assert.ok(source.includes('ECC v2.0.0-rc.1 documents the Hermes surface'));
|
||||
assert.ok(!source.includes('Public Preview Scope'));
|
||||
});
|
||||
|
||||
test('Hermes setup cross-links adjacent migration and architecture docs', () => {
|
||||
const source = read('docs/HERMES-SETUP.md');
|
||||
assert.ok(source.includes('HERMES-OPENCLAW-MIGRATION.md'));
|
||||
assert.ok(source.includes('architecture/cross-harness.md'));
|
||||
assert.ok(source.includes('Plan and scaffold migration artifacts'));
|
||||
assert.ok(!source.includes('0.5. Generate and review artifacts with `ecc migrate plan` /'));
|
||||
});
|
||||
|
||||
test('release docs preserve the ECC/Hermes boundary', () => {
|
||||
const releaseNotes = read('docs/releases/2.0.0-rc.1/release-notes.md');
|
||||
assert.ok(releaseNotes.includes('ECC is the reusable substrate'));
|
||||
assert.ok(releaseNotes.includes('Hermes as the operator shell'));
|
||||
});
|
||||
|
||||
test('release notes route new contributors through the rc.1 quickstart', () => {
|
||||
const releaseNotes = read('docs/releases/2.0.0-rc.1/release-notes.md');
|
||||
assert.ok(releaseNotes.includes('[rc.1 quickstart](quickstart.md)'));
|
||||
});
|
||||
|
||||
test('preview pack manifest assembles release, Hermes, and publication gates', () => {
|
||||
const manifest = read('docs/releases/2.0.0-rc.1/preview-pack-manifest.md');
|
||||
|
||||
for (const artifact of [
|
||||
'docs/HERMES-SETUP.md',
|
||||
'skills/hermes-imports/SKILL.md',
|
||||
'docs/architecture/harness-adapter-compliance.md',
|
||||
'scripts/preview-pack-smoke.js',
|
||||
'scripts/release-approval-gate.js',
|
||||
'docs/releases/2.0.0-rc.1/publication-readiness.md',
|
||||
'docs/releases/2.0.0-rc.1/naming-and-publication-matrix.md',
|
||||
'docs/releases/2.0.0-rc.1/release-url-ledger-2026-05-19.md',
|
||||
'docs/releases/2.0.0-rc.1/owner-approval-packet-2026-05-19.md',
|
||||
'docs/releases/2.0.0-rc.1/video-suite-production.md',
|
||||
'docs/releases/2.0.0-rc.1/publication-evidence-2026-05-19.md',
|
||||
'docs/releases/2.0.0-rc.1/release-name-plugin-publication-checklist-2026-05-18.md'
|
||||
]) {
|
||||
assert.ok(manifest.includes(artifact), `preview pack manifest missing ${artifact}`);
|
||||
}
|
||||
|
||||
for (const blocker of [
|
||||
'GitHub prerelease `v2.0.0-rc.1`',
|
||||
'npm `ecc-universal@2.0.0-rc.1`',
|
||||
'Claude plugin tag',
|
||||
'Codex repo-marketplace distribution evidence',
|
||||
'ECC Tools billing/product readiness'
|
||||
]) {
|
||||
assert.ok(manifest.includes(blocker), `preview pack manifest missing blocker ${blocker}`);
|
||||
}
|
||||
|
||||
assert.ok(manifest.includes('no raw workspace exports'));
|
||||
assert.ok(manifest.includes('Final Verification Commands'));
|
||||
assert.ok(manifest.includes('npm run preview-pack:smoke'));
|
||||
assert.ok(manifest.includes('npm run release:approval-gate -- --format json'));
|
||||
assert.ok(manifest.includes('npm run release:video-suite -- --format json'));
|
||||
assert.ok(manifest.includes('Reference-Inspired Adapter Direction'));
|
||||
});
|
||||
|
||||
test('owner approval packet consolidates the final gated decisions', () => {
|
||||
const packet = read('docs/releases/2.0.0-rc.1/owner-approval-packet-2026-05-19.md');
|
||||
const manifest = read('docs/releases/2.0.0-rc.1/preview-pack-manifest.md');
|
||||
const publicationReadiness = read('docs/releases/2.0.0-rc.1/publication-readiness.md');
|
||||
const hypergrowth = read('docs/releases/2.0.0/ecc-2-hypergrowth-release-command-center.md');
|
||||
|
||||
for (const marker of [
|
||||
'Owner Approval Packet',
|
||||
'Source commit',
|
||||
'Decision Register',
|
||||
'GitHub prerelease',
|
||||
'npm `next` publish',
|
||||
'Claude plugin tag',
|
||||
'Video upload',
|
||||
'Final URL Fill-In',
|
||||
'Do Not Approve If',
|
||||
'No outbound email, personal-account post, package publish, plugin tag, or billing announcement is authorized by this packet alone.'
|
||||
]) {
|
||||
assert.ok(packet.includes(marker), `owner approval packet missing ${marker}`);
|
||||
}
|
||||
|
||||
for (const command of [
|
||||
'node scripts/platform-audit.js --json',
|
||||
'npm run preview-pack:smoke -- --format json',
|
||||
'npm run release:approval-gate -- --format json',
|
||||
'npm run release:video-suite -- --format json',
|
||||
'node tests/run-all.js'
|
||||
]) {
|
||||
assert.ok(packet.includes(command), `owner approval packet missing command ${command}`);
|
||||
}
|
||||
|
||||
for (const urlSurface of ['GitHub prerelease URL', 'npm rc package URL', 'Claude plugin tag URL', 'Primary launch video URL', 'ECC Tools billing/readiness URL']) {
|
||||
assert.ok(packet.includes(urlSurface), `owner approval packet missing ${urlSurface}`);
|
||||
}
|
||||
|
||||
assert.ok(manifest.includes('owner-approval-packet-2026-05-19.md'));
|
||||
assert.ok(publicationReadiness.includes('owner-approval-packet-2026-05-19.md'));
|
||||
assert.ok(hypergrowth.includes('owner-approval-packet-2026-05-19.md'));
|
||||
});
|
||||
|
||||
test('GA roadmap mirrors the current May 19 release evidence', () => {
|
||||
const roadmap = read('docs/ECC-2.0-GA-ROADMAP.md');
|
||||
|
||||
for (const marker of [
|
||||
'owner-approval-packet-2026-05-19.md',
|
||||
'preview-pack smoke digest `eebb8a66c33e`',
|
||||
'local 2568-test suite',
|
||||
'PR #2001',
|
||||
'GitHub Actions run `26102500291`',
|
||||
'PR #2002',
|
||||
'GitHub Actions run `26103853507`',
|
||||
'PR #2009',
|
||||
'GitHub Actions run `26111313938`',
|
||||
'PR #2019',
|
||||
'30f60710',
|
||||
'26135974576',
|
||||
'467d148a-712a-4777-aad9-95593e9f1739',
|
||||
'7642ee9c-3107-400c-a229-53e2895a8914',
|
||||
'ecc-may-19-post-pr-2002-sync-64cef8f668e0',
|
||||
'owner approval packet'
|
||||
]) {
|
||||
assert.ok(roadmap.includes(marker), `GA roadmap missing current evidence marker ${marker}`);
|
||||
}
|
||||
|
||||
assert.ok(!roadmap.includes('preview-pack smoke digest `bc2bf157616e`'));
|
||||
assert.ok(!roadmap.includes('preview-pack smoke digest `531328aaaa53`'));
|
||||
assert.ok(!roadmap.includes('local 2544-test suite'));
|
||||
});
|
||||
|
||||
test('rc.1 quickstart gives a clone-to-cross-harness path', () => {
|
||||
const quickstart = read('docs/releases/2.0.0-rc.1/quickstart.md');
|
||||
for (const heading of ['Clone', 'Install', 'Verify', 'First Skill', 'Switch Harness']) {
|
||||
assert.ok(quickstart.includes(`## ${heading}`), `Missing ${heading} section`);
|
||||
}
|
||||
assert.ok(quickstart.includes('git clone https://github.com/affaan-m/ECC.git'));
|
||||
assert.ok(quickstart.includes('cd ECC'));
|
||||
assert.ok(quickstart.includes('node tests/run-all.js'));
|
||||
assert.ok(quickstart.includes('skills/hermes-imports/SKILL.md'));
|
||||
});
|
||||
|
||||
test('cross-harness doc includes a worked skill portability example', () => {
|
||||
const source = read('docs/architecture/cross-harness.md');
|
||||
assert.ok(source.includes('## Worked Example'));
|
||||
assert.ok(source.includes('same skill source'));
|
||||
for (const harness of ['Claude Code', 'Codex', 'OpenCode']) {
|
||||
assert.ok(source.includes(harness), `Expected worked example to mention ${harness}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('release docs use release-candidate wording consistently', () => {
|
||||
const releaseNotes = read('docs/releases/2.0.0-rc.1/release-notes.md');
|
||||
assert.ok(releaseNotes.includes('## Release Candidate Boundaries'));
|
||||
assert.ok(!releaseNotes.includes('## Preview Boundaries'));
|
||||
});
|
||||
|
||||
test('launch checklist records the ecc2 alpha version policy', () => {
|
||||
const cargoToml = read('ecc2/Cargo.toml');
|
||||
const launchChecklist = read('docs/releases/2.0.0-rc.1/launch-checklist.md');
|
||||
assert.ok(cargoToml.includes('version = "0.1.0"'));
|
||||
assert.ok(launchChecklist.includes('`ecc2/Cargo.toml` stays at `0.1.0`'));
|
||||
assert.ok(!launchChecklist.includes('confirm whether `ecc2/Cargo.toml` moves'));
|
||||
});
|
||||
|
||||
test('release video suite manifest gates the content launch lane', () => {
|
||||
const videoManifest = read('docs/releases/2.0.0-rc.1/video-suite-production.md');
|
||||
const launchChecklist = read('docs/releases/2.0.0-rc.1/launch-checklist.md');
|
||||
const hypergrowth = read('docs/releases/2.0.0/ecc-2-hypergrowth-release-command-center.md');
|
||||
const packageJson = JSON.parse(read('package.json'));
|
||||
|
||||
for (const marker of [
|
||||
'ECC 2.0 Video Suite Production Manifest',
|
||||
'ECC_VIDEO_SOURCE_ROOT',
|
||||
'ECC_VIDEO_RELEASE_SUITE_ROOT',
|
||||
'video-use compatible workflow',
|
||||
'Self-Eval Gate',
|
||||
'Do Not Publish If',
|
||||
'renders/ecc-2-primary-launch-rough-v1.mp4',
|
||||
'timelines/primary-launch-v1.timeline.json',
|
||||
'Primary launch video'
|
||||
]) {
|
||||
assert.ok(videoManifest.includes(marker), `video suite manifest missing ${marker}`);
|
||||
}
|
||||
|
||||
for (const asset of ['longform-full-wide.mp4', 'sf-thread-2-whatisecc.mp4', 'thread-2-ghapp-money.mp4', 'coverage-montage-wide.mp4', 'star_history.png', 'x_analytics.png']) {
|
||||
assert.ok(videoManifest.includes(asset), `video suite manifest missing asset ${asset}`);
|
||||
}
|
||||
|
||||
assert.ok(launchChecklist.includes('npm run release:video-suite -- --format json'));
|
||||
assert.ok(hypergrowth.includes('Pick final video cuts, upload after approval, and attach public URLs'));
|
||||
assert.strictEqual(packageJson.scripts['release:video-suite'], 'node scripts/release-video-suite.js');
|
||||
assert.ok(packageJson.files.includes('scripts/release-video-suite.js'));
|
||||
});
|
||||
|
||||
test('release approval gate blocks publication until owner decisions and URLs are final', () => {
|
||||
const manifest = read('docs/releases/2.0.0-rc.1/preview-pack-manifest.md');
|
||||
const packet = read('docs/releases/2.0.0-rc.1/owner-approval-packet-2026-05-19.md');
|
||||
const ledger = read('docs/releases/2.0.0-rc.1/release-url-ledger-2026-05-19.md');
|
||||
const script = read('scripts/release-approval-gate.js');
|
||||
const packageJson = JSON.parse(read('package.json'));
|
||||
|
||||
for (const marker of [
|
||||
'ecc.release-approval-gate.v1',
|
||||
'owner-decisions-approved',
|
||||
'release-url-ledger-finalized',
|
||||
'announcement-copy-finalized',
|
||||
'No outbound email, personal-account post, package publish, plugin tag, or billing announcement'
|
||||
]) {
|
||||
assert.ok(script.includes(marker), `release approval gate missing ${marker}`);
|
||||
}
|
||||
|
||||
assert.ok(manifest.includes('scripts/release-approval-gate.js'));
|
||||
assert.ok(manifest.includes('npm run release:approval-gate -- --format json'));
|
||||
assert.ok(packet.includes('npm run release:approval-gate -- --format json'));
|
||||
assert.ok(ledger.includes('npm run release:approval-gate -- --format json'));
|
||||
assert.strictEqual(packageJson.scripts['release:approval-gate'], 'node scripts/release-approval-gate.js');
|
||||
assert.ok(packageJson.files.includes('scripts/release-approval-gate.js'));
|
||||
});
|
||||
|
||||
test('partner sponsor talks pack gates the hypergrowth outbound lane', () => {
|
||||
const partnerPack = read('docs/releases/2.0.0-rc.1/partner-sponsor-talks-pack.md');
|
||||
const manifest = read('docs/releases/2.0.0-rc.1/preview-pack-manifest.md');
|
||||
const releaseNotes = read('docs/releases/2.0.0-rc.1/release-notes.md');
|
||||
const launchChecklist = read('docs/releases/2.0.0-rc.1/launch-checklist.md');
|
||||
const hypergrowth = read('docs/releases/2.0.0/ecc-2-hypergrowth-release-command-center.md');
|
||||
|
||||
for (const marker of [
|
||||
'Partner, Sponsor, and Talks Pack',
|
||||
'$1,728/mo',
|
||||
'$10,000/mo',
|
||||
'$8,272/mo',
|
||||
'Pilot sponsor',
|
||||
'Business sponsor',
|
||||
'Strategic partner',
|
||||
'Consulting sprint',
|
||||
'Talk or podcast',
|
||||
'Sponsor Outbound',
|
||||
'Platform Partner DM',
|
||||
'Consulting Intro',
|
||||
'Talk And Podcast Pitch',
|
||||
'GitHub Discussion Announcement',
|
||||
'Video CTA Hooks',
|
||||
'Do Not Send Or Publish If',
|
||||
'The user has not approved outbound sponsor, partner, consulting, or media'
|
||||
]) {
|
||||
assert.ok(partnerPack.includes(marker), `partner pack missing ${marker}`);
|
||||
}
|
||||
|
||||
assert.ok(partnerPack.includes('SPONSORS.md'));
|
||||
assert.ok(partnerPack.includes('SPONSORING.md'));
|
||||
assert.ok(manifest.includes('partner-sponsor-talks-pack.md'));
|
||||
assert.ok(releaseNotes.includes('partner/sponsor/talk outreach'));
|
||||
assert.ok(launchChecklist.includes('partner-sponsor-talks-pack.md'));
|
||||
assert.ok(hypergrowth.includes('partner-sponsor-talks-pack.md'));
|
||||
});
|
||||
|
||||
test('release video suite public docs do not expose private media paths', () => {
|
||||
const releaseVideoDocs = ['docs/releases/2.0.0-rc.1/video-suite-production.md', 'docs/releases/2.0.0/ecc-2-hypergrowth-release-command-center.md'];
|
||||
|
||||
const offenders = [];
|
||||
for (const relativePath of releaseVideoDocs) {
|
||||
const source = read(relativePath);
|
||||
if (/\/Users\/[A-Za-z0-9._-]+|\/home\/(?!user|runner)[A-Za-z0-9._-]+/.test(source)) {
|
||||
offenders.push(relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
assert.deepStrictEqual(offenders, []);
|
||||
});
|
||||
|
||||
test('publication readiness checklist gates public release actions on evidence', () => {
|
||||
const source = read('docs/releases/2.0.0-rc.1/publication-readiness.md');
|
||||
const may15Evidence = read('docs/releases/2.0.0-rc.1/publication-evidence-2026-05-15.md');
|
||||
const discussionPlaybook = read('docs/architecture/discussion-response-playbook.md');
|
||||
|
||||
for (const section of ['## Release Identity Matrix', '## Publication Gates', '## Required Command Evidence', '## Do Not Publish If', '## Announcement Order']) {
|
||||
assert.ok(source.includes(section), `publication readiness missing ${section}`);
|
||||
}
|
||||
|
||||
for (const field of ['Fresh check', 'Evidence artifact', 'Owner', 'Status', 'Blocker field', 'Recorded output']) {
|
||||
assert.ok(source.includes(field), `publication readiness missing ${field}`);
|
||||
}
|
||||
|
||||
for (const surface of ['GitHub release', 'npm package', 'Claude plugin', 'Codex plugin', 'Codex repo marketplace', 'OpenCode package', 'ECC Tools billing reference', 'Announcement copy']) {
|
||||
assert.ok(source.includes(surface), `publication readiness missing ${surface}`);
|
||||
}
|
||||
|
||||
assert.ok(source.includes('publication-evidence-2026-05-15.md'));
|
||||
assert.ok(source.includes('Preview-pack smoke'));
|
||||
assert.ok(source.includes('npm run preview-pack:smoke'));
|
||||
assert.ok(may15Evidence.includes('PR #1921'));
|
||||
assert.ok(may15Evidence.includes('PR #1933'));
|
||||
assert.ok(may15Evidence.includes('PR #1934'));
|
||||
assert.ok(may15Evidence.includes('PR #1935'));
|
||||
assert.ok(may15Evidence.includes('AgentShield PR #83'));
|
||||
assert.ok(may15Evidence.includes('AgentShield PR #85'));
|
||||
assert.ok(may15Evidence.includes('AgentShield PR #86'));
|
||||
assert.ok(may15Evidence.includes('ci-context.json'));
|
||||
assert.ok(may15Evidence.includes('ECC Tools PR #73'));
|
||||
assert.ok(may15Evidence.includes('ECC-Tools PR #75'));
|
||||
assert.ok(may15Evidence.includes('| Platform audit |'));
|
||||
assert.ok(may15Evidence.includes('Ready; open PRs 0/20'));
|
||||
assert.ok(may15Evidence.includes('passed 15/15'));
|
||||
assert.ok(may15Evidence.includes('restore-only'));
|
||||
assert.ok(may15Evidence.includes('462/462'));
|
||||
assert.ok(may15Evidence.includes('## Codex Marketplace Evidence'));
|
||||
assert.ok(may15Evidence.includes('codex plugin marketplace add <local-checkout>'));
|
||||
assert.ok(may15Evidence.includes('Plugin Directory publishing is still blocked'));
|
||||
assert.ok(may15Evidence.includes('announcementGate.ready === true'));
|
||||
assert.ok(source.includes('ECC-Tools #92 main CI'));
|
||||
assert.ok(source.includes('ECC-Tools #93 main CI'));
|
||||
assert.ok(source.includes('do not claim official Plugin Directory listing before OpenAI submission evidence'));
|
||||
assert.ok(source.includes('release-name-plugin-publication-checklist-2026-05-18.md'));
|
||||
assert.ok(source.includes('Release name and plugin publication checklist'));
|
||||
assert.ok(may15Evidence.includes('| Trunk discussions | GraphQL discussion count and maintainer-touch sweep | 58 total discussions;'));
|
||||
assert.ok(source.includes('platform audit sampled 59 trunk discussions'));
|
||||
assert.ok(source.includes('0 needing maintainer touch'));
|
||||
assert.ok(source.includes('discussion-response-playbook.md'));
|
||||
for (const expected of ['Public Support', 'Maintainer Coordination', 'Stale Or Concluded', 'Release Announcement', 'Security Escalation', 'classified as informational']) {
|
||||
assert.ok(discussionPlaybook.includes(expected), `discussion playbook missing ${expected}`);
|
||||
}
|
||||
assert.ok(may15Evidence.includes('env -u GITHUB_TOKEN'));
|
||||
assert.ok(may15Evidence.includes('ITO-44'));
|
||||
assert.ok(may15Evidence.includes('0 open PRs, 0 open issues'));
|
||||
});
|
||||
|
||||
test('release name and plugin publication checklist freezes rc.1 surfaces', () => {
|
||||
const checklist = read('docs/releases/2.0.0-rc.1/release-name-plugin-publication-checklist-2026-05-18.md');
|
||||
const launchChecklist = read('docs/releases/2.0.0-rc.1/launch-checklist.md');
|
||||
const referenceArchitecture = read('docs/ECC-2.0-REFERENCE-ARCHITECTURE.md');
|
||||
|
||||
for (const value of [
|
||||
'Ship `v2.0.0-rc.1` as **ECC**',
|
||||
'`affaan-m/ECC`',
|
||||
'`ecc-universal`',
|
||||
'`ecc` on npm is occupied',
|
||||
'`@affaan-m/ecc` is unclaimed on npm',
|
||||
'Claude plugin',
|
||||
'Codex plugin',
|
||||
'do not claim official directory listing until OpenAI publishing path is available',
|
||||
'Do not rename the npm package until rc.1 is published',
|
||||
'Do not announce billing, Marketplace, or native payments'
|
||||
]) {
|
||||
assert.ok(checklist.includes(value), `release name/plugin checklist missing ${value}`);
|
||||
}
|
||||
|
||||
for (const command of [
|
||||
'claude plugin validate .claude-plugin/plugin.json',
|
||||
'claude plugin tag .claude-plugin --dry-run',
|
||||
'codex plugin marketplace add --help',
|
||||
'npm publish --tag next --dry-run',
|
||||
'npm run preview-pack:smoke',
|
||||
'npm run release:approval-gate -- --format json'
|
||||
]) {
|
||||
assert.ok(checklist.includes(command), `release name/plugin checklist missing command ${command}`);
|
||||
}
|
||||
|
||||
assert.ok(launchChecklist.includes('release-name-plugin-publication-checklist-2026-05-18.md'));
|
||||
assert.ok(referenceArchitecture.includes('Keep the release/name/plugin publication checklist current'));
|
||||
});
|
||||
|
||||
test('active release identity surfaces use canonical ECC repo URLs', () => {
|
||||
const activeFiles = [
|
||||
'README.md',
|
||||
'.codex-plugin/README.md',
|
||||
'.codex-plugin/plugin.json',
|
||||
'.opencode/README.md',
|
||||
'.opencode/package.json',
|
||||
'docs/business/metrics-and-sponsorship.md',
|
||||
'docs/releases/2.0.0-rc.1/quickstart.md',
|
||||
'docs/releases/2.0.0-rc.1/x-thread.md',
|
||||
'docs/releases/2.0.0-rc.1/publication-readiness.md',
|
||||
'docs/releases/2.0.0-rc.1/naming-and-publication-matrix.md',
|
||||
'docs/releases/2.0.0-rc.1/release-url-ledger-2026-05-19.md',
|
||||
'ecc2/Cargo.toml',
|
||||
'scripts/platform-audit.js',
|
||||
'scripts/discussion-audit.js'
|
||||
];
|
||||
|
||||
const offenders = [];
|
||||
for (const relativePath of activeFiles) {
|
||||
const source = read(relativePath);
|
||||
if (source.includes('affaan-m/everything-claude-code')) {
|
||||
offenders.push(relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
assert.deepStrictEqual(offenders, []);
|
||||
});
|
||||
|
||||
test('release checklist and roadmap link to publication readiness evidence gate', () => {
|
||||
const launchChecklist = read('docs/releases/2.0.0-rc.1/launch-checklist.md');
|
||||
const roadmap = read('docs/ECC-2.0-GA-ROADMAP.md');
|
||||
|
||||
assert.ok(launchChecklist.includes('publication-readiness.md'));
|
||||
assert.ok(launchChecklist.includes('fresh evidence'));
|
||||
assert.ok(roadmap.includes('docs/releases/2.0.0-rc.1/publication-readiness.md'));
|
||||
assert.ok(roadmap.includes('npm dist-tag'));
|
||||
});
|
||||
|
||||
test('localized changelogs include rc.1 and 1.10.0 release entries', () => {
|
||||
for (const relativePath of ['docs/tr/CHANGELOG.md', 'docs/zh-CN/CHANGELOG.md']) {
|
||||
const source = read(relativePath);
|
||||
assert.ok(source.includes('## 2.0.0-rc.1 - 2026-04-28'), `${relativePath} missing rc.1 entry`);
|
||||
assert.ok(source.includes('## 1.10.0 - 2026-04-05'), `${relativePath} missing 1.10.0 entry`);
|
||||
}
|
||||
});
|
||||
|
||||
if (failed > 0) {
|
||||
console.log(`\nFailed: ${failed}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\nPassed: ${passed}`);
|
||||
@@ -0,0 +1,417 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||
const fixtureRoot = path.join(repoRoot, 'examples', 'evaluator-rag-prototype');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
passed++;
|
||||
} catch (error) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${error.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
function read(relativePath) {
|
||||
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
}
|
||||
|
||||
function readJson(fileName) {
|
||||
return JSON.parse(fs.readFileSync(path.join(fixtureRoot, fileName), 'utf8'));
|
||||
}
|
||||
|
||||
function readFixtureJson(relativePath) {
|
||||
return JSON.parse(fs.readFileSync(path.join(fixtureRoot, relativePath), 'utf8'));
|
||||
}
|
||||
|
||||
console.log('\n=== Testing evaluator RAG prototype ===\n');
|
||||
|
||||
test('architecture doc records the artifact contract and reference pressure', () => {
|
||||
const source = read('docs/architecture/evaluator-rag-prototype.md');
|
||||
|
||||
for (const required of [
|
||||
'Scenario spec',
|
||||
'Trace',
|
||||
'Report',
|
||||
'Candidate playbook',
|
||||
'Verifier result',
|
||||
'Meta-Harness',
|
||||
'Autocontext',
|
||||
'Claude HUD',
|
||||
'Hermes Agent',
|
||||
'dmux, Orca, Superset, and Ghast',
|
||||
'ECC Tools'
|
||||
]) {
|
||||
assert.ok(source.includes(required), `Missing doc requirement: ${required}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('fixtures use one scenario id and declare read-only behavior', () => {
|
||||
const scenario = readJson('scenario.json');
|
||||
const trace = readJson('trace.json');
|
||||
const report = readJson('report.json');
|
||||
const verifier = readJson('verifier-result.json');
|
||||
|
||||
assert.strictEqual(scenario.schema_version, 'ecc.evaluator-rag.scenario.v1');
|
||||
assert.strictEqual(trace.schema_version, 'ecc.evaluator-rag.trace.v1');
|
||||
assert.strictEqual(report.schema_version, 'ecc.evaluator-rag.report.v1');
|
||||
assert.strictEqual(verifier.schema_version, 'ecc.evaluator-rag.verifier.v1');
|
||||
|
||||
for (const artifact of [trace, report, verifier]) {
|
||||
assert.strictEqual(artifact.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(artifact.read_only, true);
|
||||
}
|
||||
});
|
||||
|
||||
test('trace covers the full self-improving harness loop', () => {
|
||||
const trace = readJson('trace.json');
|
||||
const phases = trace.events.map(event => event.phase);
|
||||
|
||||
for (const phase of ['observation', 'retrieval', 'proposal', 'verification', 'promotion']) {
|
||||
assert.ok(phases.includes(phase), `Missing trace phase ${phase}`);
|
||||
}
|
||||
|
||||
assert.ok(trace.events.some(event => event.promoted_candidate_id === 'maintainer-salvage-branch'));
|
||||
});
|
||||
|
||||
test('scenario blocks unsafe write actions and release actions', () => {
|
||||
const scenario = readJson('scenario.json');
|
||||
const forbidden = scenario.forbidden_actions.join('\n');
|
||||
|
||||
for (const blocked of [
|
||||
'closing, reopening, or commenting on PRs',
|
||||
'merging PRs',
|
||||
'creating release tags',
|
||||
'publishing packages or plugins',
|
||||
'copying private paths, secrets, or raw personal context',
|
||||
'blindly cherry-picking bulk localization'
|
||||
]) {
|
||||
assert.ok(forbidden.includes(blocked), `Missing forbidden action: ${blocked}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('verifier accepts maintainer salvage and rejects blind translation imports', () => {
|
||||
const verifier = readJson('verifier-result.json');
|
||||
const accepted = verifier.candidates.find(candidate => candidate.candidate_id === 'maintainer-salvage-branch');
|
||||
const rejected = verifier.candidates.find(candidate => candidate.candidate_id === 'blind-cherry-pick-translations');
|
||||
|
||||
assert.ok(accepted, 'Missing accepted maintainer salvage candidate');
|
||||
assert.ok(rejected, 'Missing rejected blind cherry-pick candidate');
|
||||
assert.strictEqual(accepted.decision, 'accepted');
|
||||
assert.strictEqual(rejected.decision, 'rejected');
|
||||
assert.strictEqual(verifier.promoted_candidate_id, accepted.candidate_id);
|
||||
assert.ok(accepted.score > rejected.score);
|
||||
assert.ok(rejected.reasons.join('\n').includes('translator/manual review'));
|
||||
});
|
||||
|
||||
test('candidate playbook preserves stale-salvage operating rules', () => {
|
||||
const playbook = read('examples/evaluator-rag-prototype/candidate-playbook.md');
|
||||
|
||||
for (const required of [
|
||||
'docs/stale-pr-salvage-ledger.md',
|
||||
'source PR',
|
||||
'maintainer-owned branch',
|
||||
'Preserve attribution',
|
||||
'translator/manual review',
|
||||
'private operator context',
|
||||
'git diff --check'
|
||||
]) {
|
||||
assert.ok(playbook.includes(required), `Missing playbook rule: ${required}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('roadmap points to the evaluator RAG prototype and hosted PR check', () => {
|
||||
const roadmap = read('docs/ECC-2.0-GA-ROADMAP.md');
|
||||
|
||||
assert.ok(roadmap.includes('docs/architecture/evaluator-rag-prototype.md'));
|
||||
assert.ok(roadmap.includes('examples/evaluator-rag-prototype/'));
|
||||
assert.ok(roadmap.includes('Deterministic hosted PR check, cached output scoring, retrieval planning, judge contract, and gated model execution integrated'));
|
||||
});
|
||||
|
||||
test('billing readiness scenario rejects launch copy overclaims', () => {
|
||||
const scenario = readFixtureJson('billing-marketplace-readiness/scenario.json');
|
||||
const trace = readFixtureJson('billing-marketplace-readiness/trace.json');
|
||||
const report = readFixtureJson('billing-marketplace-readiness/report.json');
|
||||
const verifier = readFixtureJson('billing-marketplace-readiness/verifier-result.json');
|
||||
const playbook = read('examples/evaluator-rag-prototype/billing-marketplace-readiness/candidate-playbook.md');
|
||||
|
||||
assert.strictEqual(scenario.scenario_id, 'billing-marketplace-readiness');
|
||||
assert.strictEqual(trace.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(report.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(verifier.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(trace.read_only, true);
|
||||
assert.strictEqual(report.read_only, true);
|
||||
assert.strictEqual(verifier.read_only, true);
|
||||
|
||||
for (const blocked of [
|
||||
'creating or editing GitHub Marketplace listings',
|
||||
'changing plan limits, subscriptions, seats, or entitlements',
|
||||
'posting announcement copy',
|
||||
'claiming live billing readiness from dry-run evidence alone'
|
||||
]) {
|
||||
assert.ok(scenario.forbidden_actions.includes(blocked), `Missing billing forbidden action: ${blocked}`);
|
||||
}
|
||||
|
||||
const accepted = verifier.candidates.find(candidate => candidate.candidate_id === 'evidence-backed-billing-check');
|
||||
const rejected = verifier.candidates.find(candidate => candidate.candidate_id === 'announcement-first-billing-copy');
|
||||
|
||||
assert.ok(accepted, 'Missing accepted billing evidence candidate');
|
||||
assert.ok(rejected, 'Missing rejected announcement-overclaim candidate');
|
||||
assert.strictEqual(accepted.decision, 'accepted');
|
||||
assert.strictEqual(rejected.decision, 'rejected');
|
||||
assert.strictEqual(verifier.promoted_candidate_id, accepted.candidate_id);
|
||||
assert.ok(rejected.reasons.join('\n').includes('roadmap acceptance criteria'));
|
||||
assert.ok(playbook.includes('remove-before-publication'));
|
||||
assert.ok(playbook.includes('https://github.com/marketplace/ecc-tools'));
|
||||
});
|
||||
|
||||
test('ci failure diagnosis scenario rejects rerun-only fixes', () => {
|
||||
const scenario = readFixtureJson('ci-failure-diagnosis/scenario.json');
|
||||
const trace = readFixtureJson('ci-failure-diagnosis/trace.json');
|
||||
const report = readFixtureJson('ci-failure-diagnosis/report.json');
|
||||
const verifier = readFixtureJson('ci-failure-diagnosis/verifier-result.json');
|
||||
const playbook = read('examples/evaluator-rag-prototype/ci-failure-diagnosis/candidate-playbook.md');
|
||||
|
||||
assert.strictEqual(scenario.scenario_id, 'ci-failure-diagnosis');
|
||||
assert.strictEqual(trace.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(report.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(verifier.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(trace.read_only, true);
|
||||
assert.strictEqual(report.read_only, true);
|
||||
assert.strictEqual(verifier.read_only, true);
|
||||
|
||||
for (const blocked of [
|
||||
'rerunning CI until it passes without diagnosing the failure',
|
||||
'pushing speculative fixes without a captured failing log excerpt',
|
||||
'weakening or deleting tests to silence a failure',
|
||||
'merging or publishing while required checks are red'
|
||||
]) {
|
||||
assert.ok(scenario.forbidden_actions.includes(blocked), `Missing CI forbidden action: ${blocked}`);
|
||||
}
|
||||
|
||||
for (const required of [
|
||||
'failing job and step are named',
|
||||
'captured log excerpt is linked or summarized',
|
||||
'changed-file context is compared to the failing step',
|
||||
'local reproduction or regression command is named'
|
||||
]) {
|
||||
assert.ok(scenario.acceptance_gates.includes(required), `Missing CI acceptance gate: ${required}`);
|
||||
}
|
||||
|
||||
const accepted = verifier.candidates.find(candidate => candidate.candidate_id === 'log-backed-minimal-fix');
|
||||
const rejected = verifier.candidates.find(candidate => candidate.candidate_id === 'rerun-only-green-wait');
|
||||
|
||||
assert.ok(accepted, 'Missing accepted log-backed CI candidate');
|
||||
assert.ok(rejected, 'Missing rejected rerun-only CI candidate');
|
||||
assert.strictEqual(accepted.decision, 'accepted');
|
||||
assert.strictEqual(rejected.decision, 'rejected');
|
||||
assert.strictEqual(verifier.promoted_candidate_id, accepted.candidate_id);
|
||||
assert.ok(rejected.reasons.join('\n').includes('failing log excerpt'));
|
||||
assert.ok(playbook.includes('gh run view <run-id> --log-failed'));
|
||||
assert.ok(playbook.includes('Full required GitHub Actions matrix before merge'));
|
||||
});
|
||||
|
||||
test('harness config quality scenario rejects unsupported parity claims', () => {
|
||||
const scenario = readFixtureJson('harness-config-quality/scenario.json');
|
||||
const trace = readFixtureJson('harness-config-quality/trace.json');
|
||||
const report = readFixtureJson('harness-config-quality/report.json');
|
||||
const verifier = readFixtureJson('harness-config-quality/verifier-result.json');
|
||||
const playbook = read('examples/evaluator-rag-prototype/harness-config-quality/candidate-playbook.md');
|
||||
|
||||
assert.strictEqual(scenario.scenario_id, 'harness-config-quality');
|
||||
assert.strictEqual(trace.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(report.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(verifier.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(trace.read_only, true);
|
||||
assert.strictEqual(report.read_only, true);
|
||||
assert.strictEqual(verifier.read_only, true);
|
||||
|
||||
for (const blocked of [
|
||||
'claiming native support for instruction-backed or reference-only harnesses',
|
||||
'copying Claude hook semantics into Codex, Gemini, Zed, or OpenCode without adapter evidence',
|
||||
'silently overwriting existing user MCP, hook, plugin, command, or rule config',
|
||||
'publishing packages or plugins from this evaluator run'
|
||||
]) {
|
||||
assert.ok(scenario.forbidden_actions.includes(blocked), `Missing harness forbidden action: ${blocked}`);
|
||||
}
|
||||
|
||||
for (const required of [
|
||||
'adapter state is retrieved from the matrix',
|
||||
'install or onramp path is named',
|
||||
'verification command is named',
|
||||
'config-preservation behavior is explicit'
|
||||
]) {
|
||||
assert.ok(scenario.acceptance_gates.includes(required), `Missing harness acceptance gate: ${required}`);
|
||||
}
|
||||
|
||||
const accepted = verifier.candidates.find(candidate => candidate.candidate_id === 'adapter-matrix-backed-drift-check');
|
||||
const rejected = verifier.candidates.find(candidate => candidate.candidate_id === 'unsupported-hook-parity-claim');
|
||||
|
||||
assert.ok(accepted, 'Missing accepted adapter-matrix candidate');
|
||||
assert.ok(rejected, 'Missing rejected unsupported parity candidate');
|
||||
assert.strictEqual(accepted.decision, 'accepted');
|
||||
assert.strictEqual(rejected.decision, 'rejected');
|
||||
assert.strictEqual(verifier.promoted_candidate_id, accepted.candidate_id);
|
||||
assert.ok(rejected.reasons.join('\n').includes('native support'));
|
||||
assert.ok(playbook.includes('npm run harness:adapters -- --check'));
|
||||
assert.ok(playbook.includes('node tests/docs/mcp-management-docs.test.js'));
|
||||
});
|
||||
|
||||
test('AgentShield policy exception scenario rejects blanket suppression', () => {
|
||||
const scenario = readFixtureJson('agentshield-policy-exception/scenario.json');
|
||||
const trace = readFixtureJson('agentshield-policy-exception/trace.json');
|
||||
const report = readFixtureJson('agentshield-policy-exception/report.json');
|
||||
const verifier = readFixtureJson('agentshield-policy-exception/verifier-result.json');
|
||||
const playbook = read('examples/evaluator-rag-prototype/agentshield-policy-exception/candidate-playbook.md');
|
||||
|
||||
assert.strictEqual(scenario.scenario_id, 'agentshield-policy-exception');
|
||||
assert.strictEqual(trace.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(report.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(verifier.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(trace.read_only, true);
|
||||
assert.strictEqual(report.read_only, true);
|
||||
assert.strictEqual(verifier.read_only, true);
|
||||
|
||||
for (const blocked of [
|
||||
'approving policy exceptions without SARIF or report evidence',
|
||||
'treating expired exceptions as active',
|
||||
'blanket-suppressing AgentShield policy packs or organization-policy gates',
|
||||
'editing AgentShield code or policy files from this ECC evaluator run'
|
||||
]) {
|
||||
assert.ok(scenario.forbidden_actions.includes(blocked), `Missing AgentShield forbidden action: ${blocked}`);
|
||||
}
|
||||
|
||||
for (const required of [
|
||||
'SARIF or report evidence is named',
|
||||
'owner, ticket, scope, and expiry state are recorded',
|
||||
'expired exceptions stay rejected or enforced',
|
||||
'remediation versus time-boxed exception decision is explicit'
|
||||
]) {
|
||||
assert.ok(scenario.acceptance_gates.includes(required), `Missing AgentShield acceptance gate: ${required}`);
|
||||
}
|
||||
|
||||
const accepted = verifier.candidates.find(candidate => candidate.candidate_id === 'sarif-backed-timeboxed-exception-review');
|
||||
const rejected = verifier.candidates.find(candidate => candidate.candidate_id === 'blanket-policy-suppression');
|
||||
|
||||
assert.ok(accepted, 'Missing accepted AgentShield exception candidate');
|
||||
assert.ok(rejected, 'Missing rejected blanket suppression candidate');
|
||||
assert.strictEqual(accepted.decision, 'accepted');
|
||||
assert.strictEqual(rejected.decision, 'rejected');
|
||||
assert.strictEqual(verifier.promoted_candidate_id, accepted.candidate_id);
|
||||
assert.ok(rejected.reasons.join('\n').includes('blanket-suppresses'));
|
||||
assert.ok(playbook.includes('agentshield-policy/*'));
|
||||
assert.ok(playbook.includes('owner, ticket, scope, expiry'));
|
||||
assert.ok(playbook.includes('npx ecc-agentshield scan --format json'));
|
||||
});
|
||||
|
||||
test('skill quality evidence scenario rejects vague rewrites', () => {
|
||||
const scenario = readFixtureJson('skill-quality-evidence/scenario.json');
|
||||
const trace = readFixtureJson('skill-quality-evidence/trace.json');
|
||||
const report = readFixtureJson('skill-quality-evidence/report.json');
|
||||
const verifier = readFixtureJson('skill-quality-evidence/verifier-result.json');
|
||||
const playbook = read('examples/evaluator-rag-prototype/skill-quality-evidence/candidate-playbook.md');
|
||||
|
||||
assert.strictEqual(scenario.scenario_id, 'skill-quality-evidence');
|
||||
assert.strictEqual(trace.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(report.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(verifier.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(trace.read_only, true);
|
||||
assert.strictEqual(report.read_only, true);
|
||||
assert.strictEqual(verifier.read_only, true);
|
||||
|
||||
for (const blocked of [
|
||||
'promoting a skill rewrite without examples, validation, or observed failure evidence',
|
||||
'adding broad multi-domain skills that duplicate existing focused skills',
|
||||
'copying private operator context, secrets, tokens, or personal paths into skills',
|
||||
'claiming a skill-quality improvement without a reference set or regression command'
|
||||
]) {
|
||||
assert.ok(scenario.forbidden_actions.includes(blocked), `Missing skill-quality forbidden action: ${blocked}`);
|
||||
}
|
||||
|
||||
for (const required of [
|
||||
'changed skill or guidance surface is named',
|
||||
'observed failure, user feedback, or reference-set gap is recorded',
|
||||
'validation command is named',
|
||||
'example or regression evidence is attached'
|
||||
]) {
|
||||
assert.ok(scenario.acceptance_gates.includes(required), `Missing skill-quality acceptance gate: ${required}`);
|
||||
}
|
||||
|
||||
const accepted = verifier.candidates.find(candidate => candidate.candidate_id === 'evidence-backed-skill-amendment');
|
||||
const rejected = verifier.candidates.find(candidate => candidate.candidate_id === 'vague-skill-rewrite');
|
||||
|
||||
assert.ok(accepted, 'Missing accepted skill-quality candidate');
|
||||
assert.ok(rejected, 'Missing rejected vague rewrite candidate');
|
||||
assert.strictEqual(accepted.decision, 'accepted');
|
||||
assert.strictEqual(rejected.decision, 'rejected');
|
||||
assert.strictEqual(verifier.promoted_candidate_id, accepted.candidate_id);
|
||||
assert.ok(rejected.reasons.join('\n').includes('does not include working examples'));
|
||||
assert.ok(playbook.includes('docs/SKILL-DEVELOPMENT-GUIDE.md'));
|
||||
assert.ok(playbook.includes('node scripts/ci/validate-skills.js'));
|
||||
assert.ok(playbook.includes('observed skill-run failure'));
|
||||
});
|
||||
|
||||
test('deep analyzer evidence scenario rejects no-corpus analyzer changes', () => {
|
||||
const scenario = readFixtureJson('deep-analyzer-evidence/scenario.json');
|
||||
const trace = readFixtureJson('deep-analyzer-evidence/trace.json');
|
||||
const report = readFixtureJson('deep-analyzer-evidence/report.json');
|
||||
const verifier = readFixtureJson('deep-analyzer-evidence/verifier-result.json');
|
||||
const playbook = read('examples/evaluator-rag-prototype/deep-analyzer-evidence/candidate-playbook.md');
|
||||
|
||||
assert.strictEqual(scenario.scenario_id, 'deep-analyzer-evidence');
|
||||
assert.strictEqual(trace.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(report.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(verifier.scenario_id, scenario.scenario_id);
|
||||
assert.strictEqual(trace.read_only, true);
|
||||
assert.strictEqual(report.read_only, true);
|
||||
assert.strictEqual(verifier.read_only, true);
|
||||
|
||||
for (const blocked of [
|
||||
'promoting repository, commit, architecture, or deep-analysis changes without analyzer corpus evidence',
|
||||
'suppressing the Deep Analyzer Evidence risk bucket without co-located corpus, snapshot, fixture, or benchmark evidence',
|
||||
'changing analyzer thresholds or classifications without expected-output comparison',
|
||||
'posting PR comments, check runs, or Linear sync updates from this read-only evaluator run'
|
||||
]) {
|
||||
assert.ok(scenario.forbidden_actions.includes(blocked), `Missing deep-analyzer forbidden action: ${blocked}`);
|
||||
}
|
||||
|
||||
for (const required of [
|
||||
'changed analyzer surface is named',
|
||||
'maintained corpus or reference-set path is included',
|
||||
'expected analyzer outputs are compared',
|
||||
'representative repository shape or commit history is described',
|
||||
'regression command is named'
|
||||
]) {
|
||||
assert.ok(scenario.acceptance_gates.includes(required), `Missing deep-analyzer acceptance gate: ${required}`);
|
||||
}
|
||||
|
||||
const accepted = verifier.candidates.find(candidate => candidate.candidate_id === 'corpus-backed-analyzer-change');
|
||||
const rejected = verifier.candidates.find(candidate => candidate.candidate_id === 'threshold-only-analyzer-rewrite');
|
||||
|
||||
assert.ok(accepted, 'Missing accepted deep-analyzer candidate');
|
||||
assert.ok(rejected, 'Missing rejected threshold-only analyzer candidate');
|
||||
assert.strictEqual(accepted.decision, 'accepted');
|
||||
assert.strictEqual(rejected.decision, 'rejected');
|
||||
assert.strictEqual(verifier.promoted_candidate_id, accepted.candidate_id);
|
||||
assert.ok(rejected.reasons.join('\n').includes('does not compare expected outputs'));
|
||||
assert.ok(playbook.includes('../ECC-Tools/src/analyzers/fixtures/deep-analyzer-corpus.ts'));
|
||||
assert.ok(playbook.includes('npm test -- src/analyzers/deep-analyzer-corpus.test.ts src/lib/analyzer.compare.test.ts'));
|
||||
assert.ok(playbook.includes('Deep Analyzer Evidence'));
|
||||
});
|
||||
|
||||
if (failed > 0) {
|
||||
console.log(`\nFailed: ${failed}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\nPassed: ${passed}`);
|
||||
@@ -0,0 +1,154 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const { execFileSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const {
|
||||
ADAPTER_RECORDS,
|
||||
extractMatrixBlock,
|
||||
renderMarkdownTable,
|
||||
validateAdapterRecords,
|
||||
} = require('../../scripts/lib/harness-adapter-compliance');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||
const scriptPath = path.join(repoRoot, 'scripts', 'harness-adapter-compliance.js');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
passed++;
|
||||
} catch (error) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${error.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
function read(relativePath) {
|
||||
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
}
|
||||
|
||||
console.log('\n=== Testing harness adapter compliance docs ===\n');
|
||||
|
||||
test('adapter compliance matrix covers the required harness surfaces', () => {
|
||||
const source = read('docs/architecture/harness-adapter-compliance.md');
|
||||
for (const harness of [
|
||||
'Claude Code',
|
||||
'Codex',
|
||||
'OpenCode',
|
||||
'Cursor',
|
||||
'Gemini',
|
||||
'Zed',
|
||||
'dmux',
|
||||
'Orca',
|
||||
'Superset',
|
||||
'Ghast',
|
||||
'Terminal-only'
|
||||
]) {
|
||||
assert.ok(source.includes(harness), `Expected matrix to include ${harness}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('adapter compliance source data validates required evidence fields', () => {
|
||||
assert.deepStrictEqual(validateAdapterRecords(), []);
|
||||
|
||||
const zedRecord = ADAPTER_RECORDS.find(record => record.id === 'zed');
|
||||
assert.ok(zedRecord, 'Expected Zed adapter record');
|
||||
assert.strictEqual(zedRecord.state, 'Adapter-backed');
|
||||
assert.ok(
|
||||
zedRecord.install_or_onramp.includes('`./install.sh --profile minimal --target zed`'),
|
||||
'Expected Zed installer onramp'
|
||||
);
|
||||
|
||||
for (const record of ADAPTER_RECORDS) {
|
||||
assert.ok(record.install_or_onramp.length > 0, `${record.id} needs an install or onramp`);
|
||||
assert.ok(record.verification_commands.length > 0, `${record.id} needs verification commands`);
|
||||
assert.ok(record.risk_notes.length > 0, `${record.id} needs risk notes`);
|
||||
assert.ok(record.source_docs.length > 0, `${record.id} needs source docs`);
|
||||
}
|
||||
});
|
||||
|
||||
test('adapter compliance matrix is generated from source data', () => {
|
||||
const source = read('docs/architecture/harness-adapter-compliance.md');
|
||||
assert.strictEqual(extractMatrixBlock(source), renderMarkdownTable());
|
||||
});
|
||||
|
||||
test('adapter compliance matrix extraction tolerates Windows line endings', () => {
|
||||
const source = read('docs/architecture/harness-adapter-compliance.md')
|
||||
.replace(/\r\n/g, '\n')
|
||||
.replace(/\n/g, '\r\n');
|
||||
assert.strictEqual(extractMatrixBlock(source), renderMarkdownTable());
|
||||
});
|
||||
|
||||
test('adapter compliance matrix includes the required evidence columns', () => {
|
||||
const source = read('docs/architecture/harness-adapter-compliance.md');
|
||||
for (const heading of [
|
||||
'Supported assets',
|
||||
'Unsupported or different surfaces',
|
||||
'Install or onramp',
|
||||
'Verification command',
|
||||
'Risk notes'
|
||||
]) {
|
||||
assert.ok(source.includes(heading), `Expected matrix to include ${heading}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('scorecard onramp names the local verification commands', () => {
|
||||
const source = read('docs/architecture/harness-adapter-compliance.md');
|
||||
for (const command of [
|
||||
'npm run harness:adapters -- --check',
|
||||
'npm run harness:audit -- --format json',
|
||||
'npm run observability:ready',
|
||||
'node scripts/session-inspect.js --list-adapters',
|
||||
'node scripts/loop-status.js --json --write-dir .ecc/loop-status'
|
||||
]) {
|
||||
assert.ok(source.includes(command), `Expected onramp to include ${command}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('adapter compliance CLI check passes against the committed doc', () => {
|
||||
const output = execFileSync('node', [scriptPath, '--check'], {
|
||||
cwd: repoRoot,
|
||||
encoding: 'utf8',
|
||||
});
|
||||
|
||||
assert.ok(output.includes('Harness Adapter Compliance: PASS'));
|
||||
assert.ok(output.includes(`Adapters: ${ADAPTER_RECORDS.length}`));
|
||||
});
|
||||
|
||||
test('adapter compliance CLI emits machine-readable scorecard data', () => {
|
||||
const output = execFileSync('node', [scriptPath, '--format=json'], {
|
||||
cwd: repoRoot,
|
||||
encoding: 'utf8',
|
||||
});
|
||||
const parsed = JSON.parse(output);
|
||||
|
||||
assert.strictEqual(parsed.schema_version, 'ecc.harness-adapter-compliance.v1');
|
||||
assert.strictEqual(parsed.valid, true);
|
||||
assert.strictEqual(parsed.adapter_count, ADAPTER_RECORDS.length);
|
||||
assert.ok(parsed.adapters.some(record => record.id === 'terminal-only'));
|
||||
});
|
||||
|
||||
test('cross-harness architecture links to the adapter compliance matrix', () => {
|
||||
const source = read('docs/architecture/cross-harness.md');
|
||||
assert.ok(source.includes('harness-adapter-compliance.md'));
|
||||
});
|
||||
|
||||
test('GA roadmap records the matrix and validator as current evidence', () => {
|
||||
const source = read('docs/ECC-2.0-GA-ROADMAP.md');
|
||||
assert.ok(source.includes('docs/architecture/harness-adapter-compliance.md'));
|
||||
assert.ok(source.includes('npm run harness:adapters -- --check'));
|
||||
assert.ok(source.includes('scripts/lib/harness-adapter-compliance.js'));
|
||||
});
|
||||
|
||||
if (failed > 0) {
|
||||
console.log(`\nFailed: ${failed}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\nPassed: ${passed}`);
|
||||
@@ -0,0 +1,125 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
passed++;
|
||||
} catch (error) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${error.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
const publicInstallDocs = [
|
||||
'README.md',
|
||||
'README.zh-CN.md',
|
||||
'docs/pt-BR/README.md',
|
||||
'docs/zh-CN/README.md',
|
||||
'docs/ja-JP/skills/configure-ecc/SKILL.md',
|
||||
'docs/zh-CN/skills/configure-ecc/SKILL.md',
|
||||
];
|
||||
|
||||
console.log('\n=== Testing public install identifiers ===\n');
|
||||
|
||||
for (const relativePath of publicInstallDocs) {
|
||||
const content = fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
|
||||
test(`${relativePath} does not use the overlong legacy marketplace plugin identifier`, () => {
|
||||
assert.ok(!content.includes('everything-claude-code@everything-claude-code'));
|
||||
});
|
||||
|
||||
test(`${relativePath} documents the short marketplace plugin identifier`, () => {
|
||||
assert.ok(content.includes('ecc@ecc'));
|
||||
});
|
||||
}
|
||||
|
||||
const pluginAndManualInstallDocs = [
|
||||
'README.md',
|
||||
'README.zh-CN.md',
|
||||
'docs/zh-CN/README.md',
|
||||
];
|
||||
|
||||
const publicCommandNamespaceDocs = [
|
||||
'README.md',
|
||||
'README.zh-CN.md',
|
||||
'docs/pt-BR/README.md',
|
||||
'docs/tr/README.md',
|
||||
'docs/ko-KR/README.md',
|
||||
'docs/ja-JP/README.md',
|
||||
'docs/zh-CN/README.md',
|
||||
'docs/zh-TW/README.md',
|
||||
];
|
||||
|
||||
const manualClaudeSkillInstallDocs = [
|
||||
'README.md',
|
||||
'docs/de-DE/README.md',
|
||||
'docs/ru/README.md',
|
||||
];
|
||||
|
||||
for (const relativePath of pluginAndManualInstallDocs) {
|
||||
const content = fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
|
||||
test(`${relativePath} warns not to run the full installer after plugin install`, () => {
|
||||
assert.ok(
|
||||
content.includes('--profile full'),
|
||||
'Expected docs to mention the full installer explicitly'
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('/plugin install'),
|
||||
'Expected docs to mention plugin install explicitly'
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('不要再运行')
|
||||
|| content.includes('do not run'),
|
||||
'Expected docs to warn that plugin install and full install are not sequential'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
for (const relativePath of publicCommandNamespaceDocs) {
|
||||
const content = fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
|
||||
test(`${relativePath} uses the canonical plugin command namespace`, () => {
|
||||
assert.ok(
|
||||
!content.includes('/everything-claude-code:'),
|
||||
'Expected docs not to advertise the overlong legacy plugin command namespace'
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('/ecc:plan'),
|
||||
'Expected docs to show the short plugin command namespace'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
for (const relativePath of manualClaudeSkillInstallDocs) {
|
||||
const content = fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
|
||||
test(`${relativePath} keeps manual Claude skill installs top-level`, () => {
|
||||
assert.ok(
|
||||
!/^\s*#?\s*(mkdir\s+-p|md\s+.*|cp\s+.*|copy\s+.*|cpi\s+.*|New-Item\s+.*|Copy-Item\s+.*)\s+.*(~|\$HOME)[\\/]\.claude[\\/]skills[\\/]ecc([\\/]|\b)/mi.test(content),
|
||||
'Claude Code does not discover skills installed by commands targeting ~/.claude/skills/ecc'
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('~/.claude/skills/'),
|
||||
'Expected manual install docs to copy skills into direct ~/.claude/skills children'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (failed > 0) {
|
||||
console.log(`\nFailed: ${failed}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\nPassed: ${passed}`);
|
||||
@@ -0,0 +1,155 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||
const legacyShimsDir = path.join(repoRoot, 'legacy-command-shims', 'commands');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
passed++;
|
||||
} catch (error) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${error.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
function read(relativePath) {
|
||||
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
}
|
||||
|
||||
function findLegacyDocumentDirs(dir) {
|
||||
const results = [];
|
||||
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
if (entry.name === 'node_modules' || entry.name === '.git') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const nextPath = path.join(dir, entry.name);
|
||||
|
||||
if (!entry.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.name.startsWith('_legacy-documents-')) {
|
||||
results.push(path.relative(repoRoot, nextPath));
|
||||
}
|
||||
|
||||
results.push(...findLegacyDocumentDirs(nextPath));
|
||||
}
|
||||
|
||||
return results.sort();
|
||||
}
|
||||
|
||||
console.log('\n=== Testing legacy artifact inventory ===\n');
|
||||
|
||||
test('legacy artifact inventory documents classification states', () => {
|
||||
const source = read('docs/legacy-artifact-inventory.md');
|
||||
|
||||
for (const state of [
|
||||
'Landed',
|
||||
'Milestone-tracked',
|
||||
'Salvage branch',
|
||||
'Translator/manual review',
|
||||
'Archive/no-action',
|
||||
]) {
|
||||
assert.ok(source.includes(state), `Missing classification state ${state}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('any _legacy-documents directories are explicitly inventoried', () => {
|
||||
const source = read('docs/legacy-artifact-inventory.md');
|
||||
const dirs = findLegacyDocumentDirs(repoRoot);
|
||||
|
||||
for (const dir of dirs) {
|
||||
assert.ok(source.includes(dir), `Missing legacy artifact inventory row for ${dir}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('workspace-level legacy repos are inventoried without personal paths', () => {
|
||||
const source = read('docs/legacy-artifact-inventory.md');
|
||||
|
||||
for (const dir of [
|
||||
'../_legacy-documents-ecc-context-2026-04-30',
|
||||
'../_legacy-documents-ecc-everything-claude-code-2026-04-30',
|
||||
]) {
|
||||
assert.ok(source.includes(dir), `Missing workspace legacy repo ${dir}`);
|
||||
}
|
||||
|
||||
assert.ok(source.includes('Workspace-Level Legacy Repos'));
|
||||
assert.ok(!source.includes('/Users/'), 'Inventory should avoid machine-local absolute paths');
|
||||
});
|
||||
|
||||
test('workspace legacy import rules block raw private context', () => {
|
||||
const source = read('docs/legacy-artifact-inventory.md');
|
||||
|
||||
for (const required of [
|
||||
'Do not read, print, stage, or copy `.env` files',
|
||||
'tokens',
|
||||
'OAuth secrets',
|
||||
'personal paths',
|
||||
'private operator context',
|
||||
'Do not import raw marketing drafts',
|
||||
'public-safe ideas',
|
||||
]) {
|
||||
assert.ok(source.includes(required), `Missing import guardrail: ${required}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('legacy command shims remain classified as an opt-in archive', () => {
|
||||
const source = read('docs/legacy-artifact-inventory.md');
|
||||
const readme = read('legacy-command-shims/README.md');
|
||||
|
||||
assert.ok(source.includes('legacy-command-shims/'));
|
||||
assert.ok(source.includes('Archive/no-action'));
|
||||
assert.ok(readme.includes('no longer loaded by the default plugin command surface'));
|
||||
assert.ok(readme.includes('short-term migration compatibility'));
|
||||
});
|
||||
|
||||
test('legacy command shim table tracks the current archive contents', () => {
|
||||
const source = read('docs/legacy-artifact-inventory.md');
|
||||
const shims = fs.readdirSync(legacyShimsDir)
|
||||
.filter(fileName => fileName.endsWith('.md'))
|
||||
.sort();
|
||||
|
||||
assert.strictEqual(shims.length, 12);
|
||||
|
||||
for (const shim of shims) {
|
||||
assert.ok(source.includes(`\`${shim}\``), `Missing legacy shim ${shim}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('stale salvage backlog records the remaining manual-review tail', () => {
|
||||
const source = read('docs/legacy-artifact-inventory.md');
|
||||
|
||||
for (const pr of [
|
||||
'#1687 zh-CN localization tail',
|
||||
'#1609 Persian README translation',
|
||||
'#1563 zh-TW README sync',
|
||||
'#1564 Turkish README sync',
|
||||
'#1565 pt-BR README sync',
|
||||
]) {
|
||||
assert.ok(source.includes(pr), `Missing manual-review inventory row for ${pr}`);
|
||||
}
|
||||
|
||||
assert.ok(source.includes('Translator/manual review'));
|
||||
assert.ok(source.includes('#1746-#1752'));
|
||||
assert.ok(source.includes('ITO-55'));
|
||||
assert.ok(source.includes('no automatic import remains release-blocking'));
|
||||
});
|
||||
|
||||
if (failed > 0) {
|
||||
console.log(`\nFailed: ${failed}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\nPassed: ${passed}`);
|
||||
@@ -0,0 +1,77 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` \u2713 ${name}`);
|
||||
passed++;
|
||||
} catch (error) {
|
||||
console.log(` \u2717 ${name}`);
|
||||
console.log(` Error: ${error.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
function read(relativePath) {
|
||||
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
}
|
||||
|
||||
console.log('\n=== Testing MCP management docs ===\n');
|
||||
|
||||
test('token optimization guide separates Claude MCP disables from ECC config filters', () => {
|
||||
const source = read('docs/token-optimization.md');
|
||||
|
||||
assert.ok(
|
||||
source.includes('Use `/mcp` to disable Claude Code MCP servers'),
|
||||
'Token guide should direct Claude Code users to /mcp for runtime MCP disables'
|
||||
);
|
||||
assert.ok(
|
||||
source.includes('Claude Code persists those runtime disables in `~/.claude.json`'),
|
||||
'Token guide should name ~/.claude.json as the observed runtime disable store'
|
||||
);
|
||||
assert.ok(
|
||||
source.includes('`ECC_DISABLED_MCPS` only affects ECC-generated MCP config output'),
|
||||
'Token guide should scope ECC_DISABLED_MCPS to config generation'
|
||||
);
|
||||
assert.ok(
|
||||
!source.includes('Use `disabledMcpServers` in project config to disable servers per-project'),
|
||||
'Token guide should not tell users that project settings disable Claude runtime MCP servers'
|
||||
);
|
||||
});
|
||||
|
||||
test('README MCP guidance avoids settings.json disable instructions', () => {
|
||||
const source = read('README.md');
|
||||
|
||||
assert.ok(
|
||||
source.includes('Use `/mcp` for Claude Code runtime disables; Claude Code persists those choices in `~/.claude.json`.'),
|
||||
'README should route runtime MCP disables through /mcp and ~/.claude.json'
|
||||
);
|
||||
assert.ok(
|
||||
source.includes('`ECC_DISABLED_MCPS` is an ECC install/sync filter, not a live Claude Code toggle.'),
|
||||
'README should explain ECC_DISABLED_MCPS scope'
|
||||
);
|
||||
assert.ok(
|
||||
!source.includes('// In your project\'s .claude/settings.json\n{\n "disabledMcpServers"'),
|
||||
'README should not show disabledMcpServers under .claude/settings.json'
|
||||
);
|
||||
assert.ok(
|
||||
!source.includes('Use `disabledMcpServers` in project config to disable unused ones'),
|
||||
'README quick reference should not repeat stale project-config guidance'
|
||||
);
|
||||
});
|
||||
|
||||
if (failed > 0) {
|
||||
console.log(`\nFailed: ${failed}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\nPassed: ${passed}`);
|
||||
@@ -0,0 +1,130 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
passed++;
|
||||
} catch (error) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${error.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
function read(relativePath) {
|
||||
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
}
|
||||
|
||||
console.log('\n=== Testing ECC platform value loop docs ===\n');
|
||||
|
||||
test('platform value loop doc defines the three-layer ECC 2.0 direction', () => {
|
||||
const source = read('docs/architecture/platform-value-loop.md');
|
||||
|
||||
for (const marker of [
|
||||
'Meta-harness',
|
||||
'Dedicated ECC agent',
|
||||
'Control pane / agentic IDE',
|
||||
'reproducible demo',
|
||||
'ECC can be used full-stack as a meta-harness + agent + control pane',
|
||||
]) {
|
||||
assert.ok(source.includes(marker), `platform value loop doc missing ${marker}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('platform value loop doc records the OSS-to-managed value thesis', () => {
|
||||
const source = read('docs/architecture/platform-value-loop.md');
|
||||
|
||||
for (const marker of [
|
||||
'open-source infrastructure playbook',
|
||||
'team memory and session routing',
|
||||
'managed evals, release gates, and evidence packs',
|
||||
'security review, supply-chain findings, and policy enforcement',
|
||||
'sponsors',
|
||||
'Pro interest',
|
||||
'consulting leads',
|
||||
]) {
|
||||
assert.ok(source.includes(marker), `platform value loop doc missing value marker ${marker}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('product integration contract keeps external products useful but separate', () => {
|
||||
const source = read('docs/architecture/platform-value-loop.md');
|
||||
|
||||
for (const marker of [
|
||||
'Skill pack',
|
||||
'Gated API',
|
||||
'Fixtures and docs',
|
||||
'Eval and risk gates',
|
||||
'Case study',
|
||||
'a public workflow that works without private credentials',
|
||||
'a separate gated path for live product data or actions',
|
||||
'a clear business boundary so billing and ownership are not blurred',
|
||||
]) {
|
||||
assert.ok(source.includes(marker), `platform value loop doc missing contract marker ${marker}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('Ito example preserves non-advisory and gated-access boundaries', () => {
|
||||
const source = read('docs/architecture/platform-value-loop.md');
|
||||
|
||||
for (const marker of [
|
||||
'Ito is a separate prediction-market basket product',
|
||||
'visualize market/concept relationships and backtesting outputs',
|
||||
'ITO_API_KEY',
|
||||
'do not place trades',
|
||||
'do not provide investment advice',
|
||||
'do not merge ECC Tools billing with Ito billing',
|
||||
]) {
|
||||
assert.ok(source.includes(marker), `platform value loop doc missing Ito boundary ${marker}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('release docs link the platform value loop into the rc surface', () => {
|
||||
const crossHarness = read('docs/architecture/cross-harness.md');
|
||||
const previewManifest = read('docs/releases/2.0.0-rc.1/preview-pack-manifest.md');
|
||||
const itoPack = read('docs/releases/2.0.0-rc.1/ito-prediction-market-skill-pack.md');
|
||||
const hypergrowth = read('docs/releases/2.0.0/ecc-2-hypergrowth-release-command-center.md');
|
||||
|
||||
for (const source of [crossHarness, previewManifest, itoPack, hypergrowth]) {
|
||||
assert.ok(
|
||||
source.includes('platform-value-loop.md'),
|
||||
'expected release/cross-harness surface to link platform-value-loop.md'
|
||||
);
|
||||
}
|
||||
|
||||
assert.ok(previewManifest.includes('Product integration and full-stack platform thesis'));
|
||||
assert.ok(hypergrowth.includes('Product integrations should behave like repeatable distribution loops'));
|
||||
});
|
||||
|
||||
test('platform value loop does not overclaim release status or trading ability', () => {
|
||||
const source = read('docs/architecture/platform-value-loop.md');
|
||||
const forbidden = [
|
||||
'ORCA/CONDUCTOR-grade parity is live',
|
||||
'control pane is GA',
|
||||
'native-payments readiness is live',
|
||||
'official plugin-directory listing is live',
|
||||
'public ECC skills place trades',
|
||||
];
|
||||
|
||||
for (const phrase of forbidden) {
|
||||
assert.ok(!source.includes(phrase), `platform value loop should not include overclaim: ${phrase}`);
|
||||
}
|
||||
});
|
||||
|
||||
if (failed > 0) {
|
||||
console.log(`\nFailed: ${failed}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\nPassed: ${passed}`);
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
passed++;
|
||||
} catch (error) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${error.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
function read(relativePath) {
|
||||
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
}
|
||||
|
||||
console.log('\n=== Testing stale PR salvage ledger ===\n');
|
||||
|
||||
test('stale PR salvage ledger defines every disposition state', () => {
|
||||
const source = read('docs/stale-pr-salvage-ledger.md');
|
||||
|
||||
for (const state of [
|
||||
'Salvaged',
|
||||
'Already present',
|
||||
'Superseded',
|
||||
'Skipped',
|
||||
'Translator/manual review',
|
||||
]) {
|
||||
assert.ok(source.includes(state), `Missing salvage state ${state}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('stale PR salvage ledger preserves representative source attribution', () => {
|
||||
const source = read('docs/stale-pr-salvage-ledger.md');
|
||||
|
||||
for (const pr of [
|
||||
'#1309',
|
||||
'#1232',
|
||||
'#1304',
|
||||
'#1322',
|
||||
'#1326',
|
||||
'#1310',
|
||||
'#1325',
|
||||
'#1413',
|
||||
'#1414',
|
||||
'#1478',
|
||||
'#1493',
|
||||
'#1528/#1529/#1547',
|
||||
'#1603',
|
||||
'#1658',
|
||||
'#1659',
|
||||
'#1674',
|
||||
'#1687',
|
||||
'#1705/#1780',
|
||||
'#1757',
|
||||
]) {
|
||||
assert.ok(source.includes(pr), `Missing source PR attribution for ${pr}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('stale PR salvage ledger records skipped junk and superseded work', () => {
|
||||
const source = read('docs/stale-pr-salvage-ledger.md');
|
||||
|
||||
for (const pr of ['#1306', '#1337', '#1341', '#1416/#1465', '#1475']) {
|
||||
assert.ok(source.includes(pr), `Missing skipped or superseded PR ${pr}`);
|
||||
}
|
||||
|
||||
assert.ok(source.includes('Accidental fork-sync PRs'));
|
||||
assert.ok(source.includes('too low-signal'));
|
||||
});
|
||||
|
||||
test('stale PR salvage ledger keeps localization tails manual-review only', () => {
|
||||
const source = read('docs/stale-pr-salvage-ledger.md');
|
||||
|
||||
assert.ok(source.includes('The remaining plausibly useful backlog is translation/localization work'));
|
||||
assert.ok(source.includes('#1687 zh-CN localization tail'));
|
||||
assert.ok(source.includes('#1609 Persian README translation'));
|
||||
assert.ok(source.includes('#1563 zh-TW README sync'));
|
||||
assert.ok(source.includes('translator/manual review'));
|
||||
assert.ok(source.includes('Linear ITO-55'));
|
||||
assert.ok(source.includes('Do not import stale top-level docs'));
|
||||
assert.ok(source.includes('not a release-blocking salvage task'));
|
||||
});
|
||||
|
||||
test('legacy inventory and roadmap link to the durable salvage ledger', () => {
|
||||
const inventory = read('docs/legacy-artifact-inventory.md');
|
||||
const roadmap = read('docs/ECC-2.0-GA-ROADMAP.md');
|
||||
|
||||
assert.ok(inventory.includes('docs/stale-pr-salvage-ledger.md'));
|
||||
assert.ok(roadmap.includes('docs/stale-pr-salvage-ledger.md'));
|
||||
assert.ok(roadmap.includes('#1687, #1609, #1563, #1564'));
|
||||
assert.ok(roadmap.includes('Linear ITO-55'));
|
||||
assert.ok(roadmap.includes('#1609'));
|
||||
assert.ok(roadmap.includes('no automatic import remains release-blocking'));
|
||||
});
|
||||
|
||||
test('stale PR salvage ledger records the May 12 gap pass', () => {
|
||||
const source = read('docs/stale-pr-salvage-ledger.md');
|
||||
|
||||
for (const pr of [
|
||||
'#1310',
|
||||
'#1325',
|
||||
'#1360',
|
||||
'#1414',
|
||||
'#1415',
|
||||
'#1478',
|
||||
'#1438',
|
||||
'#1504',
|
||||
'#1508',
|
||||
'#1563/#1564/#1565',
|
||||
'#1567',
|
||||
'#1570',
|
||||
'#1584',
|
||||
'#1589',
|
||||
'#1594',
|
||||
'#1597',
|
||||
'#1602',
|
||||
'#1603',
|
||||
'#1604',
|
||||
'#1609',
|
||||
'#1613',
|
||||
'#1631',
|
||||
'#1648',
|
||||
'#1658',
|
||||
'#1693',
|
||||
]) {
|
||||
assert.ok(source.includes(pr), `Missing May 12 gap-pass PR ${pr}`);
|
||||
}
|
||||
|
||||
assert.ok(source.includes('Django/Celery maintainer branch'));
|
||||
assert.ok(source.includes('already preserved in #1770'));
|
||||
assert.ok(source.includes('already preserved in #1769'));
|
||||
assert.ok(source.includes('already preserved in #1766'));
|
||||
assert.ok(source.includes('GateGuard subagent file-gate bypass'));
|
||||
assert.ok(source.includes('HTTP MCP reachability handling'));
|
||||
assert.ok(source.includes('current managed installer/profile flow'));
|
||||
assert.ok(source.includes('false-positive proof gate'));
|
||||
assert.ok(source.includes('session_id` from stdin JSON'));
|
||||
assert.ok(source.includes('Already present as `skills/redis-patterns/`'));
|
||||
});
|
||||
|
||||
if (failed > 0) {
|
||||
console.log(`\nFailed: ${failed}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\nPassed: ${passed}`);
|
||||
Reference in New Issue
Block a user