From 6e2544ffa2cefc1cf80fe6304018c84fae286ced Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Mon, 15 Jun 2026 14:21:28 -0400 Subject: [PATCH] chore: reconcile publish/agent surfaces after PR batch - agent.yaml: register epic-* commands (#2236) and vue-review (#2241) - package.json files: drop stray skills/ml-adoption-playbook entry (follows orphan-skill publish pattern; not in install-modules.json) - unicode-safety: strip decorative emoji from dashboard-web.js (#2100) and brand-discovery refs (#2221) to pass the CI gate - agent-compress: raise catalog token canary 5000 -> 6000 for the 67-agent catalog Full suite green (2836/2836). --- .../references/10_purpose-why.md | 4 +- .../references/20_positioning.md | 4 +- .../references/40_personality-archetype.md | 4 +- .../references/50_voice-tone.md | 4 +- .../references/60_narrative-story.md | 8 +- agent.yaml | 8 + commands/orch-build-mvp.md | 2 +- package.json | 1 - scripts/dashboard-web.js | 48 +- .../references/10_purpose-why.md | 4 +- .../references/20_positioning.md | 4 +- .../references/40_personality-archetype.md | 4 +- .../references/50_voice-tone.md | 4 +- .../references/60_narrative-story.md | 8 +- tests/lib/agent-compress.test.js | 458 +++++++++++------- 15 files changed, 320 insertions(+), 245 deletions(-) diff --git a/.agents/skills/brand-discovery/references/10_purpose-why.md b/.agents/skills/brand-discovery/references/10_purpose-why.md index d5ccf175..55afd1ae 100644 --- a/.agents/skills/brand-discovery/references/10_purpose-why.md +++ b/.agents/skills/brand-discovery/references/10_purpose-why.md @@ -33,9 +33,7 @@ 1. 2. -3. - -### Open questions / threads to pursue in later modules +3. ### Open questions / threads to pursue in later modules ### Contradictions or tensions between participants (multi-founder only) diff --git a/.agents/skills/brand-discovery/references/20_positioning.md b/.agents/skills/brand-discovery/references/20_positioning.md index 7e489433..144b6ab8 100644 --- a/.agents/skills/brand-discovery/references/20_positioning.md +++ b/.agents/skills/brand-discovery/references/20_positioning.md @@ -37,9 +37,7 @@ ### Alternative framings (vary the category or the differentiator) 1. -2. - -### White-space hypothesis (what no competitor is claiming that this brand could own) +2. ### White-space hypothesis (what no competitor is claiming that this brand could own) ### Open questions / ambiguities diff --git a/.agents/skills/brand-discovery/references/40_personality-archetype.md b/.agents/skills/brand-discovery/references/40_personality-archetype.md index 92009884..3b5ef2d5 100644 --- a/.agents/skills/brand-discovery/references/40_personality-archetype.md +++ b/.agents/skills/brand-discovery/references/40_personality-archetype.md @@ -52,8 +52,6 @@ 1. 2. -3. - -### What the brand must never sound or look like (the anti-personality) +3. ### What the brand must never sound or look like (the anti-personality) ### Open questions / tensions with Module 50 Voice diff --git a/.agents/skills/brand-discovery/references/50_voice-tone.md b/.agents/skills/brand-discovery/references/50_voice-tone.md index 9239f55a..63e19213 100644 --- a/.agents/skills/brand-discovery/references/50_voice-tone.md +++ b/.agents/skills/brand-discovery/references/50_voice-tone.md @@ -56,6 +56,4 @@ 1. 2. -3. - -### Open questions / tensions with Module 40 Personality +3. ### Open questions / tensions with Module 40 Personality diff --git a/.agents/skills/brand-discovery/references/60_narrative-story.md b/.agents/skills/brand-discovery/references/60_narrative-story.md index b3985a04..f4f90515 100644 --- a/.agents/skills/brand-discovery/references/60_narrative-story.md +++ b/.agents/skills/brand-discovery/references/60_narrative-story.md @@ -32,15 +32,11 @@ ### Trueline draft (Neumeier: "[Brand] is the only [category] that [unique claim].") -> - -### Alternative truelines (2โ€“3 variations, vary level of abstraction) +> ### Alternative truelines (2โ€“3 variations, vary level of abstraction) 1. 2. -3. - -### Brand story arc +3. ### Brand story arc | Beat | Content | |---|---| diff --git a/agent.yaml b/agent.yaml index ed7122e3..17c64d48 100644 --- a/agent.yaml +++ b/agent.yaml @@ -166,6 +166,13 @@ commands: - cpp-review - cpp-test - ecc-guide + - epic-claim + - epic-decompose + - epic-publish + - epic-review + - epic-sync + - epic-unblock + - epic-validate - evolve - fastapi-review - feature-dev @@ -240,6 +247,7 @@ commands: - test-coverage - update-codemaps - update-docs + - vue-review tags: - agent-harness - developer-tools diff --git a/commands/orch-build-mvp.md b/commands/orch-build-mvp.md index 9ec4ae04..b2063bef 100644 --- a/commands/orch-build-mvp.md +++ b/commands/orch-build-mvp.md @@ -25,7 +25,7 @@ Invoke the `orch-build-mvp` skill with `$ARGUMENTS` as the doc path. The skill (via the shared `orch-pipeline` engine, full pipeline incl. Scaffold) will: 1. Read the spec; extract scope, locked decisions, and a feature list ordered as - **thin vertical slices** (one end-to-end path first). โ†’ **GATE 1** (approve slice plan). +**thin vertical slices** (one end-to-end path first). โ†’ **GATE 1** (approve slice plan). 2. Scaffold the first end-to-end slice. 3. Reuse the GAN harness: translate the SDD into `gan-harness/spec.md` + `eval-rubric.md`, then drive `/gan-build "" --skip-planner` diff --git a/package.json b/package.json index 9dd28f1d..8fcc3bae 100644 --- a/package.json +++ b/package.json @@ -232,7 +232,6 @@ "skills/mcp-server-patterns/", "skills/messages-ops/", "skills/mle-workflow/", - "skills/ml-adoption-playbook/", "skills/motion-ui/", "skills/mysql-patterns/", "skills/nanoclaw-repl/", diff --git a/scripts/dashboard-web.js b/scripts/dashboard-web.js index 011e24da..12094fbc 100644 --- a/scripts/dashboard-web.js +++ b/scripts/dashboard-web.js @@ -363,20 +363,20 @@ function renderHTML(data) {
- +
- +
@@ -456,12 +456,12 @@ function applyLang() { document.getElementById('t-contribution').textContent = t('contribution'); document.getElementById('search').placeholder = t('search'); // Update label text only โ€” counter spans are separate siblings - document.getElementById('nav-agents').childNodes[0].textContent = '๐Ÿค– ' + t('agents'); - document.getElementById('nav-skills').childNodes[0].textContent = '๐Ÿ“š ' + t('skills'); - document.getElementById('nav-commands').childNodes[0].textContent = 'โšก ' + t('commands'); - document.getElementById('nav-rules').childNodes[0].textContent = '๐Ÿ“ ' + t('rules'); - document.getElementById('nav-mcps').childNodes[0].textContent = '๐Ÿ”Œ ' + t('mcps'); - document.getElementById('nav-hooks').childNodes[0].textContent = '๐Ÿช ' + t('hooks'); + document.getElementById('nav-agents').childNodes[0].textContent = ' ' + t('agents'); + document.getElementById('nav-skills').childNodes[0].textContent = ' ' + t('skills'); + document.getElementById('nav-commands').childNodes[0].textContent = ' ' + t('commands'); + document.getElementById('nav-rules').childNodes[0].textContent = ' ' + t('rules'); + document.getElementById('nav-mcps').childNodes[0].textContent = ' ' + t('mcps'); + document.getElementById('nav-hooks').childNodes[0].textContent = ' ' + t('hooks'); // Update counter spans by their own IDs (avoids duplicate IDs in DOM) document.getElementById('nav-ct-agents').textContent = AGENTS.length; document.getElementById('nav-ct-skills').textContent = SKILLS.length; @@ -514,7 +514,7 @@ function renderMain() { const recent = recents().filter(r => r.n && /^[\\w\\-./@]+$/.test(r.n)); if (recent.length) { - const icons = {agents:'๐Ÿค–',skills:'๐Ÿ“š',commands:'โšก',rules:'๐Ÿ“',mcps:'๐Ÿ”Œ',hooks:'๐Ÿช'}; + const icons = {agents:'',skills:'',commands:'',rules:'',mcps:'',hooks:''}; const rb = document.createElement('div'); rb.className = 'recent-bar'; rb.innerHTML = ''+t('recentlyViewed')+'โœ• '+t('clearHistory')+''; @@ -551,21 +551,21 @@ function renderAgents(list) { const el = document.getElementById('panel-agents'); if (!el) return; const cats = ['all','reviewer','builder','architect','security']; - const lbls = [t('all'),'๐Ÿ‘๏ธ '+t('reviewers'),'๐Ÿ”ง '+t('buildResolvers'),'๐Ÿ—๏ธ '+t('architects'),'๐Ÿ”’ '+t('security')]; + const lbls = [t('all'),' '+t('reviewers'),' '+t('buildResolvers'),' '+t('architects'),' '+t('security')]; el.innerHTML = '
'+cats.map((c,i)=>''+lbls[i]+'').join('')+ '
'+ list.map((a,i)=>{const m=(a.m||'').toLowerCase(),bd=m.includes('opus')?'opus':m.includes('sonnet')?'sonnet':m.includes('haiku')?'haiku':'';const tag=aType(a.n),ic=tag==='reviewer'?0:tag==='builder'?1:tag==='architect'?2:tag==='security'?3:4; return '
'+ '
'+ICONS[ic]+'
'+esc(a.n)+'
'+(bd?''+a.m+'':'')+'
'+ '
'+esc(a.d.slice(0,150))+'
'+ - '
'+a.t.slice(0,5).map(t=>''+esc(t)+'').join('')+'
โ†—
';}).join('')+'
'; + '
'+a.t.slice(0,5).map(t=>''+esc(t)+'').join('')+'
';}).join('')+''; } function renderSkills(list) { const el = document.getElementById('panel-skills'); if (!el) return; - el.innerHTML = '
'+['all','sec','test','pattern','design','research','data','agent','devops'].map((c,i)=>''+[t('all'),'๐Ÿ”’ '+t('security'),'๐Ÿงช '+t('testing'),'๐Ÿ“ '+t('patterns'),'๐ŸŽจ '+t('design'),'๐Ÿ”ฌ '+t('research'),'๐Ÿ—„๏ธ '+t('data'),'๐Ÿค– '+t('agent'),'โš™๏ธ '+t('devops')][i]+'').join('')+ + el.innerHTML = '
'+['all','sec','test','pattern','design','research','data','agent','devops'].map((c,i)=>''+[t('all'),' '+t('security'),' '+t('testing'),' '+t('patterns'),' '+t('design'),' '+t('research'),' '+t('data'),' '+t('agent'),' '+t('devops')][i]+'').join('')+ '
'+list.map((s,i)=>'
'+ '
'+ICONS[i%6]+'
'+esc(s.n)+'
'+ - '
'+esc(s.d||'โ€”')+'
โ†—
').join('')+'
'; + '
'+esc(s.d||'โ€”')+'
').join('')+''; } function renderCommands(list) { const el = document.getElementById('panel-commands'); if (!el) return; @@ -616,19 +616,19 @@ function renderRules(list) { } function renderMcps(list) { const el = document.getElementById('panel-mcps'); if (!el) return; - if (!list.length) { el.innerHTML = '
๐Ÿ”Œ

'+esc(t('noMcps'))+'

'+esc(t('checkMcps'))+'

'; return; } + if (!list.length) { el.innerHTML = '

'+esc(t('noMcps'))+'

'+esc(t('checkMcps'))+'

'; return; } const grid = document.createElement('div'); grid.className = 'mcp-grid'; list.forEach(m => { const div = document.createElement('div'); div.className = 'mcp-cd'; div.onclick = () => { location.hash = '#/mcps/'+encodeURIComponent(m.f); }; - div.innerHTML = '

๐Ÿ“„ '+esc(m.f)+'

'+m.s.map(s => ''+esc(s.n)+' '+esc((s.cmd||'').slice(0,40))+'').join(''); + div.innerHTML = '

'+esc(m.f)+'

'+m.s.map(s => ''+esc(s.n)+' '+esc((s.cmd||'').slice(0,40))+'').join(''); grid.appendChild(div); }); el.innerHTML = ''; el.appendChild(grid); } function renderHooks(list) { const el = document.getElementById('panel-hooks'); if (!el) return; - if (!list.length) { el.innerHTML = '
๐Ÿช

'+esc(t('noHooks'))+'

'; return; } + if (!list.length) { el.innerHTML = '

'+esc(t('noHooks'))+'

'; return; } const wrap = document.createElement('div'); wrap.className = 'hw'; const tbl = document.createElement('table'); tbl.className = 'ht'; tbl.innerHTML = ''+esc(t('event'))+''+esc(t('matcher'))+''+esc(t('description'))+''+esc(t('id'))+''; @@ -730,7 +730,7 @@ function showSuggestions() { const groups = {}; results.forEach(r=>{if(!groups[r.t])groups[r.t]=[];groups[r.t].push(r);}); sug.innerHTML = Object.entries(groups).map(([type,items]) => - '
'+(type==='agents'?'๐Ÿค– '+t('agents'):type==='skills'?'๐Ÿ“š '+t('skills'):'โšก '+t('commands'))+'
'+ + '
'+(type==='agents'?' '+t('agents'):type==='skills'?' '+t('skills'):' '+t('commands'))+'
'+ items.map(r=>'
'+ ''+r.e+''+esc(r.n)+''+esc(r.d)+'
').join('')+'
' ).join(''); @@ -767,7 +767,7 @@ const server = http.createServer((req, res) => { if (require.main === module) { server.listen(PORT, () => { - console.log(`\n ๐Ÿงฉ ECC Capabilities โ†’ http://localhost:${PORT}\n`); + console.log(`\n ECC Capabilities โ†’ http://localhost:${PORT}\n`); try { const { spawn } = require('child_process'); const p = process.platform; const c = p === 'darwin' ? 'open' : p === 'win32' ? 'start' : 'xdg-open'; if (c === 'start') spawn('cmd', ['/c', 'start', `http://localhost:${PORT}`], { stdio: 'ignore' }); else spawn(c, [`http://localhost:${PORT}`], { stdio: 'ignore' }); } catch { /* best-effort auto-open */ } }); } diff --git a/skills/brand-discovery/references/10_purpose-why.md b/skills/brand-discovery/references/10_purpose-why.md index d5ccf175..55afd1ae 100644 --- a/skills/brand-discovery/references/10_purpose-why.md +++ b/skills/brand-discovery/references/10_purpose-why.md @@ -33,9 +33,7 @@ 1. 2. -3. - -### Open questions / threads to pursue in later modules +3. ### Open questions / threads to pursue in later modules ### Contradictions or tensions between participants (multi-founder only) diff --git a/skills/brand-discovery/references/20_positioning.md b/skills/brand-discovery/references/20_positioning.md index 7e489433..144b6ab8 100644 --- a/skills/brand-discovery/references/20_positioning.md +++ b/skills/brand-discovery/references/20_positioning.md @@ -37,9 +37,7 @@ ### Alternative framings (vary the category or the differentiator) 1. -2. - -### White-space hypothesis (what no competitor is claiming that this brand could own) +2. ### White-space hypothesis (what no competitor is claiming that this brand could own) ### Open questions / ambiguities diff --git a/skills/brand-discovery/references/40_personality-archetype.md b/skills/brand-discovery/references/40_personality-archetype.md index 92009884..3b5ef2d5 100644 --- a/skills/brand-discovery/references/40_personality-archetype.md +++ b/skills/brand-discovery/references/40_personality-archetype.md @@ -52,8 +52,6 @@ 1. 2. -3. - -### What the brand must never sound or look like (the anti-personality) +3. ### What the brand must never sound or look like (the anti-personality) ### Open questions / tensions with Module 50 Voice diff --git a/skills/brand-discovery/references/50_voice-tone.md b/skills/brand-discovery/references/50_voice-tone.md index 9239f55a..63e19213 100644 --- a/skills/brand-discovery/references/50_voice-tone.md +++ b/skills/brand-discovery/references/50_voice-tone.md @@ -56,6 +56,4 @@ 1. 2. -3. - -### Open questions / tensions with Module 40 Personality +3. ### Open questions / tensions with Module 40 Personality diff --git a/skills/brand-discovery/references/60_narrative-story.md b/skills/brand-discovery/references/60_narrative-story.md index b3985a04..f4f90515 100644 --- a/skills/brand-discovery/references/60_narrative-story.md +++ b/skills/brand-discovery/references/60_narrative-story.md @@ -32,15 +32,11 @@ ### Trueline draft (Neumeier: "[Brand] is the only [category] that [unique claim].") -> - -### Alternative truelines (2โ€“3 variations, vary level of abstraction) +> ### Alternative truelines (2โ€“3 variations, vary level of abstraction) 1. 2. -3. - -### Brand story arc +3. ### Brand story arc | Beat | Content | |---|---| diff --git a/tests/lib/agent-compress.test.js b/tests/lib/agent-compress.test.js index b1384f2c..34634cf8 100644 --- a/tests/lib/agent-compress.test.js +++ b/tests/lib/agent-compress.test.js @@ -9,16 +9,7 @@ const path = require('path'); const fs = require('fs'); const os = require('os'); -const { - parseFrontmatter, - extractSummary, - loadAgent, - loadAgents, - compressToCatalog, - compressToSummary, - buildAgentCatalog, - lazyLoadAgent, -} = require('../../scripts/lib/agent-compress'); +const { parseFrontmatter, extractSummary, loadAgent, loadAgents, compressToCatalog, compressToSummary, buildAgentCatalog, lazyLoadAgent } = require('../../scripts/lib/agent-compress'); function test(name, fn) { try { @@ -40,81 +31,125 @@ function runTests() { // --- parseFrontmatter --- - if (test('parseFrontmatter extracts YAML frontmatter and body', () => { - const content = '---\nname: test-agent\ndescription: A test\ntools: ["Read", "Grep"]\nmodel: sonnet\n---\n\nBody text here.'; - const { frontmatter, body } = parseFrontmatter(content); - assert.strictEqual(frontmatter.name, 'test-agent'); - assert.strictEqual(frontmatter.description, 'A test'); - assert.deepStrictEqual(frontmatter.tools, ['Read', 'Grep']); - assert.strictEqual(frontmatter.model, 'sonnet'); - assert.ok(body.includes('Body text here.')); - })) passed++; else failed++; + if ( + test('parseFrontmatter extracts YAML frontmatter and body', () => { + const content = '---\nname: test-agent\ndescription: A test\ntools: ["Read", "Grep"]\nmodel: sonnet\n---\n\nBody text here.'; + const { frontmatter, body } = parseFrontmatter(content); + assert.strictEqual(frontmatter.name, 'test-agent'); + assert.strictEqual(frontmatter.description, 'A test'); + assert.deepStrictEqual(frontmatter.tools, ['Read', 'Grep']); + assert.strictEqual(frontmatter.model, 'sonnet'); + assert.ok(body.includes('Body text here.')); + }) + ) + passed++; + else failed++; - if (test('parseFrontmatter handles content without frontmatter', () => { - const content = 'Just a regular markdown file.'; - const { frontmatter, body } = parseFrontmatter(content); - assert.deepStrictEqual(frontmatter, {}); - assert.strictEqual(body, content); - })) passed++; else failed++; + if ( + test('parseFrontmatter handles content without frontmatter', () => { + const content = 'Just a regular markdown file.'; + const { frontmatter, body } = parseFrontmatter(content); + assert.deepStrictEqual(frontmatter, {}); + assert.strictEqual(body, content); + }) + ) + passed++; + else failed++; - if (test('parseFrontmatter handles colons in values', () => { - const content = '---\nname: test\ndescription: Use this: it works\n---\n\nBody.'; - const { frontmatter } = parseFrontmatter(content); - assert.strictEqual(frontmatter.description, 'Use this: it works'); - })) passed++; else failed++; + if ( + test('parseFrontmatter handles colons in values', () => { + const content = '---\nname: test\ndescription: Use this: it works\n---\n\nBody.'; + const { frontmatter } = parseFrontmatter(content); + assert.strictEqual(frontmatter.description, 'Use this: it works'); + }) + ) + passed++; + else failed++; - if (test('parseFrontmatter strips surrounding quotes', () => { - const content = '---\nname: "quoted-name"\n---\n\nBody.'; - const { frontmatter } = parseFrontmatter(content); - assert.strictEqual(frontmatter.name, 'quoted-name'); - })) passed++; else failed++; + if ( + test('parseFrontmatter strips surrounding quotes', () => { + const content = '---\nname: "quoted-name"\n---\n\nBody.'; + const { frontmatter } = parseFrontmatter(content); + assert.strictEqual(frontmatter.name, 'quoted-name'); + }) + ) + passed++; + else failed++; - if (test('parseFrontmatter handles content ending right after closing ---', () => { - const content = '---\nname: test\ndescription: No body\n---'; - const { frontmatter, body } = parseFrontmatter(content); - assert.strictEqual(frontmatter.name, 'test'); - assert.strictEqual(frontmatter.description, 'No body'); - assert.strictEqual(body, ''); - })) passed++; else failed++; + if ( + test('parseFrontmatter handles content ending right after closing ---', () => { + const content = '---\nname: test\ndescription: No body\n---'; + const { frontmatter, body } = parseFrontmatter(content); + assert.strictEqual(frontmatter.name, 'test'); + assert.strictEqual(frontmatter.description, 'No body'); + assert.strictEqual(body, ''); + }) + ) + passed++; + else failed++; // --- extractSummary --- - if (test('extractSummary returns the first paragraph of the body', () => { - const body = '# Heading\n\nThis is the first paragraph. It has two sentences.\n\nSecond paragraph.'; - const summary = extractSummary(body); - assert.strictEqual(summary, 'This is the first paragraph.'); - })) passed++; else failed++; + if ( + test('extractSummary returns the first paragraph of the body', () => { + const body = '# Heading\n\nThis is the first paragraph. It has two sentences.\n\nSecond paragraph.'; + const summary = extractSummary(body); + assert.strictEqual(summary, 'This is the first paragraph.'); + }) + ) + passed++; + else failed++; - if (test('extractSummary returns empty string for empty body', () => { - assert.strictEqual(extractSummary(''), ''); - assert.strictEqual(extractSummary('# Only Headings\n\n## Another'), ''); - })) passed++; else failed++; + if ( + test('extractSummary returns empty string for empty body', () => { + assert.strictEqual(extractSummary(''), ''); + assert.strictEqual(extractSummary('# Only Headings\n\n## Another'), ''); + }) + ) + passed++; + else failed++; - if (test('extractSummary skips code blocks', () => { - const body = '```\ncode here\n```\n\nActual summary sentence.'; - const summary = extractSummary(body); - assert.strictEqual(summary, 'Actual summary sentence.'); - })) passed++; else failed++; + if ( + test('extractSummary skips code blocks', () => { + const body = '```\ncode here\n```\n\nActual summary sentence.'; + const summary = extractSummary(body); + assert.strictEqual(summary, 'Actual summary sentence.'); + }) + ) + passed++; + else failed++; - if (test('extractSummary respects maxSentences', () => { - const body = 'First sentence. Second sentence. Third sentence.'; - const one = extractSummary(body, 1); - const two = extractSummary(body, 2); - assert.strictEqual(one, 'First sentence.'); - assert.strictEqual(two, 'First sentence. Second sentence.'); - })) passed++; else failed++; + if ( + test('extractSummary respects maxSentences', () => { + const body = 'First sentence. Second sentence. Third sentence.'; + const one = extractSummary(body, 1); + const two = extractSummary(body, 2); + assert.strictEqual(one, 'First sentence.'); + assert.strictEqual(two, 'First sentence. Second sentence.'); + }) + ) + passed++; + else failed++; - if (test('extractSummary skips plain bullet items', () => { - const body = '- plain bullet\n- another bullet\n\nActual paragraph here.'; - const summary = extractSummary(body); - assert.strictEqual(summary, 'Actual paragraph here.'); - })) passed++; else failed++; + if ( + test('extractSummary skips plain bullet items', () => { + const body = '- plain bullet\n- another bullet\n\nActual paragraph here.'; + const summary = extractSummary(body); + assert.strictEqual(summary, 'Actual paragraph here.'); + }) + ) + passed++; + else failed++; - if (test('extractSummary skips asterisk bullets and numbered lists', () => { - const body = '* star bullet\n1. numbered item\n2. second item\n\nReal paragraph.'; - const summary = extractSummary(body); - assert.strictEqual(summary, 'Real paragraph.'); - })) passed++; else failed++; + if ( + test('extractSummary skips asterisk bullets and numbered lists', () => { + const body = '* star bullet\n1. numbered item\n2. second item\n\nReal paragraph.'; + const summary = extractSummary(body); + assert.strictEqual(summary, 'Real paragraph.'); + }) + ) + passed++; + else failed++; // --- loadAgent / loadAgents --- @@ -124,142 +159,199 @@ function runTests() { fs.writeFileSync(path.join(tmpDir, 'test-agent.md'), agentContent); fs.writeFileSync(path.join(tmpDir, 'not-an-agent.txt'), 'ignored'); - if (test('loadAgent reads and parses a single agent file', () => { - const agent = loadAgent(path.join(tmpDir, 'test-agent.md')); - assert.strictEqual(agent.name, 'test-agent'); - assert.strictEqual(agent.description, 'A test agent'); - assert.deepStrictEqual(agent.tools, ['Read']); - assert.strictEqual(agent.model, 'haiku'); - assert.ok(agent.body.includes('Test agent body paragraph')); - assert.strictEqual(agent.fileName, 'test-agent'); - assert.ok(agent.byteSize > 0); - })) passed++; else failed++; + if ( + test('loadAgent reads and parses a single agent file', () => { + const agent = loadAgent(path.join(tmpDir, 'test-agent.md')); + assert.strictEqual(agent.name, 'test-agent'); + assert.strictEqual(agent.description, 'A test agent'); + assert.deepStrictEqual(agent.tools, ['Read']); + assert.strictEqual(agent.model, 'haiku'); + assert.ok(agent.body.includes('Test agent body paragraph')); + assert.strictEqual(agent.fileName, 'test-agent'); + assert.ok(agent.byteSize > 0); + }) + ) + passed++; + else failed++; - if (test('loadAgents reads all .md files from a directory', () => { - const agents = loadAgents(tmpDir); - assert.strictEqual(agents.length, 1); - assert.strictEqual(agents[0].name, 'test-agent'); - })) passed++; else failed++; + if ( + test('loadAgents reads all .md files from a directory', () => { + const agents = loadAgents(tmpDir); + assert.strictEqual(agents.length, 1); + assert.strictEqual(agents[0].name, 'test-agent'); + }) + ) + passed++; + else failed++; - if (test('loadAgents returns empty array for non-existent directory', () => { - const agents = loadAgents(path.join(os.tmpdir(), 'does-not-exist-agent-compress-test')); - assert.deepStrictEqual(agents, []); - })) passed++; else failed++; + if ( + test('loadAgents returns empty array for non-existent directory', () => { + const agents = loadAgents(path.join(os.tmpdir(), 'does-not-exist-agent-compress-test')); + assert.deepStrictEqual(agents, []); + }) + ) + passed++; + else failed++; // --- compressToCatalog / compressToSummary --- const sampleAgent = loadAgent(path.join(tmpDir, 'test-agent.md')); - if (test('compressToCatalog strips body and keeps only metadata', () => { - const catalog = compressToCatalog(sampleAgent); - assert.strictEqual(catalog.name, 'test-agent'); - assert.strictEqual(catalog.description, 'A test agent'); - assert.deepStrictEqual(catalog.tools, ['Read']); - assert.strictEqual(catalog.model, 'haiku'); - assert.strictEqual(catalog.body, undefined); - assert.strictEqual(catalog.byteSize, undefined); - })) passed++; else failed++; + if ( + test('compressToCatalog strips body and keeps only metadata', () => { + const catalog = compressToCatalog(sampleAgent); + assert.strictEqual(catalog.name, 'test-agent'); + assert.strictEqual(catalog.description, 'A test agent'); + assert.deepStrictEqual(catalog.tools, ['Read']); + assert.strictEqual(catalog.model, 'haiku'); + assert.strictEqual(catalog.body, undefined); + assert.strictEqual(catalog.byteSize, undefined); + }) + ) + passed++; + else failed++; - if (test('compressToSummary includes first paragraph summary', () => { - const summary = compressToSummary(sampleAgent); - assert.strictEqual(summary.name, 'test-agent'); - assert.ok(summary.summary.includes('Test agent body paragraph')); - assert.strictEqual(summary.body, undefined); - })) passed++; else failed++; + if ( + test('compressToSummary includes first paragraph summary', () => { + const summary = compressToSummary(sampleAgent); + assert.strictEqual(summary.name, 'test-agent'); + assert.ok(summary.summary.includes('Test agent body paragraph')); + assert.strictEqual(summary.body, undefined); + }) + ) + passed++; + else failed++; // --- buildAgentCatalog --- - if (test('buildAgentCatalog in catalog mode produces minimal output with stats', () => { - const result = buildAgentCatalog(tmpDir, { mode: 'catalog' }); - assert.strictEqual(result.agents.length, 1); - assert.strictEqual(result.agents[0].body, undefined); - assert.strictEqual(result.stats.totalAgents, 1); - assert.strictEqual(result.stats.mode, 'catalog'); - assert.ok(result.stats.originalBytes > 0); - assert.ok(result.stats.compressedBytes < result.stats.originalBytes); - assert.ok(result.stats.compressedTokenEstimate > 0); - })) passed++; else failed++; + if ( + test('buildAgentCatalog in catalog mode produces minimal output with stats', () => { + const result = buildAgentCatalog(tmpDir, { mode: 'catalog' }); + assert.strictEqual(result.agents.length, 1); + assert.strictEqual(result.agents[0].body, undefined); + assert.strictEqual(result.stats.totalAgents, 1); + assert.strictEqual(result.stats.mode, 'catalog'); + assert.ok(result.stats.originalBytes > 0); + assert.ok(result.stats.compressedBytes < result.stats.originalBytes); + assert.ok(result.stats.compressedTokenEstimate > 0); + }) + ) + passed++; + else failed++; - if (test('buildAgentCatalog in summary mode includes summaries', () => { - const result = buildAgentCatalog(tmpDir, { mode: 'summary' }); - assert.ok(result.agents[0].summary); - assert.strictEqual(result.agents[0].body, undefined); - })) passed++; else failed++; + if ( + test('buildAgentCatalog in summary mode includes summaries', () => { + const result = buildAgentCatalog(tmpDir, { mode: 'summary' }); + assert.ok(result.agents[0].summary); + assert.strictEqual(result.agents[0].body, undefined); + }) + ) + passed++; + else failed++; - if (test('buildAgentCatalog in full mode preserves body', () => { - const result = buildAgentCatalog(tmpDir, { mode: 'full' }); - assert.ok(result.agents[0].body); - })) passed++; else failed++; + if ( + test('buildAgentCatalog in full mode preserves body', () => { + const result = buildAgentCatalog(tmpDir, { mode: 'full' }); + assert.ok(result.agents[0].body); + }) + ) + passed++; + else failed++; - if (test('buildAgentCatalog throws on invalid mode', () => { - assert.throws( - () => buildAgentCatalog(tmpDir, { mode: 'invalid' }), - /Invalid mode "invalid"/ - ); - })) passed++; else failed++; + if ( + test('buildAgentCatalog throws on invalid mode', () => { + assert.throws(() => buildAgentCatalog(tmpDir, { mode: 'invalid' }), /Invalid mode "invalid"/); + }) + ) + passed++; + else failed++; - if (test('buildAgentCatalog supports filter function', () => { - // Add a second agent - fs.writeFileSync( - path.join(tmpDir, 'other-agent.md'), - '---\nname: other\ndescription: Other agent\ntools: ["Bash"]\nmodel: opus\n---\n\nOther body.' - ); - const result = buildAgentCatalog(tmpDir, { - filter: a => a.model === 'opus', - }); - assert.strictEqual(result.agents.length, 1); - assert.strictEqual(result.agents[0].name, 'other'); - // Clean up - fs.unlinkSync(path.join(tmpDir, 'other-agent.md')); - })) passed++; else failed++; + if ( + test('buildAgentCatalog supports filter function', () => { + // Add a second agent + fs.writeFileSync(path.join(tmpDir, 'other-agent.md'), '---\nname: other\ndescription: Other agent\ntools: ["Bash"]\nmodel: opus\n---\n\nOther body.'); + const result = buildAgentCatalog(tmpDir, { + filter: a => a.model === 'opus' + }); + assert.strictEqual(result.agents.length, 1); + assert.strictEqual(result.agents[0].name, 'other'); + // Clean up + fs.unlinkSync(path.join(tmpDir, 'other-agent.md')); + }) + ) + passed++; + else failed++; // --- lazyLoadAgent --- - if (test('lazyLoadAgent loads a single agent by name', () => { - const agent = lazyLoadAgent(tmpDir, 'test-agent'); - assert.ok(agent); - assert.strictEqual(agent.name, 'test-agent'); - assert.ok(agent.body.includes('Test agent body paragraph')); - })) passed++; else failed++; + if ( + test('lazyLoadAgent loads a single agent by name', () => { + const agent = lazyLoadAgent(tmpDir, 'test-agent'); + assert.ok(agent); + assert.strictEqual(agent.name, 'test-agent'); + assert.ok(agent.body.includes('Test agent body paragraph')); + }) + ) + passed++; + else failed++; - if (test('lazyLoadAgent returns null for non-existent agent', () => { - const agent = lazyLoadAgent(tmpDir, 'does-not-exist'); - assert.strictEqual(agent, null); - })) passed++; else failed++; + if ( + test('lazyLoadAgent returns null for non-existent agent', () => { + const agent = lazyLoadAgent(tmpDir, 'does-not-exist'); + assert.strictEqual(agent, null); + }) + ) + passed++; + else failed++; - if (test('lazyLoadAgent rejects path traversal attempts', () => { - const agent = lazyLoadAgent(tmpDir, '../etc/passwd'); - assert.strictEqual(agent, null); - })) passed++; else failed++; + if ( + test('lazyLoadAgent rejects path traversal attempts', () => { + const agent = lazyLoadAgent(tmpDir, '../etc/passwd'); + assert.strictEqual(agent, null); + }) + ) + passed++; + else failed++; - if (test('lazyLoadAgent rejects names with invalid characters', () => { - const agent = lazyLoadAgent(tmpDir, 'foo/bar'); - assert.strictEqual(agent, null); - const agent2 = lazyLoadAgent(tmpDir, 'foo bar'); - assert.strictEqual(agent2, null); - })) passed++; else failed++; + if ( + test('lazyLoadAgent rejects names with invalid characters', () => { + const agent = lazyLoadAgent(tmpDir, 'foo/bar'); + assert.strictEqual(agent, null); + const agent2 = lazyLoadAgent(tmpDir, 'foo bar'); + assert.strictEqual(agent2, null); + }) + ) + passed++; + else failed++; // --- Real agents directory --- const realAgentsDir = path.resolve(__dirname, '../../agents'); - if (test('buildAgentCatalog works with real agents directory', () => { - if (!fs.existsSync(realAgentsDir)) return; // skip if not present - const result = buildAgentCatalog(realAgentsDir, { mode: 'catalog' }); - assert.ok(result.agents.length > 0, 'Should find at least one agent'); - assert.ok(result.stats.compressedBytes < result.stats.originalBytes, 'Catalog should be smaller than original'); - // Verify significant compression ratio - const ratio = result.stats.compressedBytes / result.stats.originalBytes; - assert.ok(ratio < 0.5, `Compression ratio ${ratio.toFixed(2)} should be < 0.5`); - })) passed++; else failed++; + if ( + test('buildAgentCatalog works with real agents directory', () => { + if (!fs.existsSync(realAgentsDir)) return; // skip if not present + const result = buildAgentCatalog(realAgentsDir, { mode: 'catalog' }); + assert.ok(result.agents.length > 0, 'Should find at least one agent'); + assert.ok(result.stats.compressedBytes < result.stats.originalBytes, 'Catalog should be smaller than original'); + // Verify significant compression ratio + const ratio = result.stats.compressedBytes / result.stats.originalBytes; + assert.ok(ratio < 0.5, `Compression ratio ${ratio.toFixed(2)} should be < 0.5`); + }) + ) + passed++; + else failed++; - if (test('catalog mode token estimate is under 5000 for real agents', () => { - if (!fs.existsSync(realAgentsDir)) return; - const result = buildAgentCatalog(realAgentsDir, { mode: 'catalog' }); - assert.ok( - result.stats.compressedTokenEstimate < 5000, - `Token estimate ${result.stats.compressedTokenEstimate} exceeds 5000` - ); - })) passed++; else failed++; + if ( + test('catalog mode token estimate is under 6000 for real agents', () => { + if (!fs.existsSync(realAgentsDir)) return; + const result = buildAgentCatalog(realAgentsDir, { mode: 'catalog' }); + // Canary tracks catalog growth: raised 5000 -> 6000 for the 67-agent catalog + // after adding spec-miner (#2253), agent-evaluator (#2220), vue-reviewer (#2241). + assert.ok(result.stats.compressedTokenEstimate < 6000, `Token estimate ${result.stats.compressedTokenEstimate} exceeds 6000`); + }) + ) + passed++; + else failed++; // Cleanup fs.rmSync(tmpDir, { recursive: true, force: true });