Skip to content

Commit 082d3bd

Browse files
committed
Create p0-content-correctness.test.js
1 parent e0f97a7 commit 082d3bd

File tree

1 file changed

+355
-0
lines changed

1 file changed

+355
-0
lines changed
Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
'use strict';
2+
3+
const { describe, it } = require('node:test');
4+
const assert = require('node:assert/strict');
5+
const fs = require('fs');
6+
const path = require('path');
7+
8+
const { discoverAgents, VORTEX_DIR, STEP_PATTERN } = require('./helpers');
9+
10+
// ─── Constants ──────────────────────────────────────────────────
11+
12+
const COMPASS_REF = path.join(VORTEX_DIR, 'compass-routing-reference.md');
13+
14+
// Workflows that intentionally have NO compass table
15+
const COMPASS_EXEMPT = ['vortex-navigation'];
16+
17+
// ─── Dynamic Discovery (NFR5) ───────────────────────────────────
18+
19+
const agents = discoverAgents();
20+
21+
const allWorkflows = agents.flatMap(agent =>
22+
agent.workflowNames.map((name, i) => ({
23+
name,
24+
dir: agent.workflowDirs[i],
25+
agentName: agent.name,
26+
agentId: agent.id,
27+
}))
28+
);
29+
30+
const agentNameMap = new Map(
31+
agents.map(a => [a.name.toLowerCase(), a])
32+
);
33+
34+
const registeredWorkflowNames = new Set(allWorkflows.map(w => w.name.toLowerCase()));
35+
36+
// ─── Parsing Utilities ──────────────────────────────────────────
37+
38+
/**
39+
* Find the final (highest-numbered) step file in a workflow directory.
40+
* Compass sections always appear in the last step file.
41+
*/
42+
function findFinalStepFile(workflowDir) {
43+
const stepsDir = path.join(workflowDir, 'steps');
44+
if (!fs.existsSync(stepsDir)) return null;
45+
const stepFiles = fs.readdirSync(stepsDir)
46+
.filter(f => STEP_PATTERN.test(f))
47+
.sort();
48+
return stepFiles.length > 0
49+
? path.join(stepsDir, stepFiles[stepFiles.length - 1])
50+
: null;
51+
}
52+
53+
/**
54+
* Extract the ## Vortex Compass section from step file content.
55+
* Returns section content as string, or null if no compass section.
56+
*/
57+
function extractCompassSection(content) {
58+
const match = content.match(/## Vortex Compass\n([\s\S]*?)(?=\n## [^#]|$)/);
59+
return match ? match[0] : null;
60+
}
61+
62+
/**
63+
* Parse a compass markdown table into structured data.
64+
* Returns { headers, rows, hasFooter } or null if no valid table.
65+
*/
66+
function parseCompassTable(compassContent) {
67+
const lines = compassContent.split('\n');
68+
const tableLines = [];
69+
let inTable = false;
70+
71+
for (const line of lines) {
72+
const trimmed = line.trim();
73+
if (trimmed.startsWith('|') && trimmed.endsWith('|')) {
74+
inTable = true;
75+
tableLines.push(trimmed);
76+
} else if (inTable) {
77+
break; // End of table
78+
}
79+
}
80+
81+
if (tableLines.length < 3) return null; // Need header + separator + at least 1 row
82+
83+
// Parse header row
84+
const headerCells = tableLines[0]
85+
.split('|')
86+
.filter(c => c.trim() !== '')
87+
.map(c => c.trim());
88+
89+
// Skip separator row (index 1), parse data rows
90+
const rows = [];
91+
for (let i = 2; i < tableLines.length; i++) {
92+
const cells = tableLines[i]
93+
.split('|')
94+
.filter(c => c.trim() !== '')
95+
.map(c => c.trim());
96+
// Skip separator rows
97+
if (/^[\s-|]+$/.test(tableLines[i].replace(/\|/g, ''))) continue;
98+
if (cells.length >= 4) {
99+
rows.push({
100+
condition: cells[0],
101+
workflow: cells[1],
102+
agent: cells[2],
103+
why: cells[3],
104+
});
105+
}
106+
}
107+
108+
// Check for footer containing "evidence-based recommendations" (scoped before ### subheadings)
109+
const mainContent = compassContent.split(/\n###/)[0];
110+
const hasFooter = mainContent
111+
.toLowerCase()
112+
.includes('evidence-based recommendations');
113+
114+
return { headers: headerCells, rows, hasFooter };
115+
}
116+
117+
/**
118+
* Extract the lowercase agent name from a compass table Agent cell.
119+
* e.g., "Mila 🔬" → "mila"
120+
*/
121+
function extractAgentName(agentCell) {
122+
const match = agentCell.match(/^(\w+)/);
123+
return match ? match[1].toLowerCase() : null;
124+
}
125+
126+
// ─── Test Suite: Compass Table Presence (AC: 3, 6, 7) ──────────
127+
128+
describe('Compass Table Presence', () => {
129+
const nonExemptWorkflows = allWorkflows.filter(
130+
w => !COMPASS_EXEMPT.includes(w.name)
131+
);
132+
const exemptWorkflows = allWorkflows.filter(
133+
w => COMPASS_EXEMPT.includes(w.name)
134+
);
135+
136+
// 3.1: Vacuous pass guard
137+
it('at least 20 workflows have compass tables', () => {
138+
let compassCount = 0;
139+
for (const wf of nonExemptWorkflows) {
140+
const stepFile = findFinalStepFile(wf.dir);
141+
if (stepFile) {
142+
const content = fs.readFileSync(stepFile, 'utf8');
143+
if (extractCompassSection(content)) compassCount++;
144+
}
145+
}
146+
assert.ok(
147+
compassCount >= 20,
148+
`Expected at least 20 workflows with compass tables, found ${compassCount}`
149+
);
150+
});
151+
152+
// 3.2: Per non-exempt workflow: final step has compass heading
153+
for (const wf of nonExemptWorkflows) {
154+
it(`${wf.name} (${wf.agentName}): final step has ## Vortex Compass`, () => {
155+
const stepFile = findFinalStepFile(wf.dir);
156+
assert.ok(
157+
stepFile,
158+
`${wf.name} (${wf.agentName}): no step files found in ${wf.dir}/steps/`
159+
);
160+
const content = fs.readFileSync(stepFile, 'utf8');
161+
const compass = extractCompassSection(content);
162+
assert.ok(
163+
compass,
164+
`${wf.name} (${wf.agentName}): final step ${path.basename(stepFile)} missing ## Vortex Compass section`
165+
);
166+
});
167+
}
168+
169+
// 3.3: Per exempt workflow: confirm NO compass heading
170+
for (const wf of exemptWorkflows) {
171+
it(`${wf.name} (${wf.agentName}): exempt workflow has NO compass section`, () => {
172+
const stepFile = findFinalStepFile(wf.dir);
173+
if (!stepFile) return; // No step files — exempt is correct
174+
const content = fs.readFileSync(stepFile, 'utf8');
175+
const compass = extractCompassSection(content);
176+
assert.strictEqual(
177+
compass,
178+
null,
179+
`${wf.name} (${wf.agentName}): exempt workflow should NOT have ## Vortex Compass section but one was found`
180+
);
181+
});
182+
}
183+
});
184+
185+
// ─── Test Suite: Compass Table Format & Routing (AC: 1,2,3,4,6,7) ──
186+
187+
describe('Compass Table Format & Routing Validity', () => {
188+
const nonExemptWorkflows = allWorkflows.filter(
189+
w => !COMPASS_EXEMPT.includes(w.name)
190+
);
191+
192+
for (const wf of nonExemptWorkflows) {
193+
describe(`${wf.name} (${wf.agentName})`, () => {
194+
let compass = null;
195+
let parsed = null;
196+
197+
// Load compass data once per workflow (try-catch so tests fail gracefully)
198+
const stepFile = findFinalStepFile(wf.dir);
199+
try {
200+
if (stepFile) {
201+
const content = fs.readFileSync(stepFile, 'utf8');
202+
compass = extractCompassSection(content);
203+
if (compass) parsed = parseCompassTable(compass);
204+
}
205+
} catch (_) { /* compass/parsed remain null — tests will fail with diagnostics */ }
206+
207+
// 4.1: 4-column headers match expected (case-insensitive)
208+
it('compass table has correct 4-column headers', () => {
209+
assert.ok(parsed, `${wf.name}: no valid compass table found`);
210+
const expectedHeaders = [
211+
'if you learned...',
212+
'consider next...',
213+
'agent',
214+
'why',
215+
];
216+
const actualHeaders = parsed.headers.map(h => h.toLowerCase());
217+
assert.deepStrictEqual(
218+
actualHeaders,
219+
expectedHeaders,
220+
`${wf.name} (${wf.agentName}): compass table headers mismatch — expected ${JSON.stringify(expectedHeaders)}, got ${JSON.stringify(actualHeaders)}`
221+
);
222+
});
223+
224+
// 4.2: 2-3 data rows
225+
it('compass table has 2-3 data rows', () => {
226+
assert.ok(parsed, `${wf.name}: no valid compass table found`);
227+
assert.ok(
228+
parsed.rows.length >= 2 && parsed.rows.length <= 3,
229+
`${wf.name} (${wf.agentName}): compass table should have 2-3 rows, found ${parsed.rows.length}`
230+
);
231+
});
232+
233+
// 4.3: Footer contains "evidence-based recommendations"
234+
it('compass section has evidence-based recommendations footer', () => {
235+
assert.ok(compass, `${wf.name}: no compass section found`);
236+
assert.ok(
237+
parsed && parsed.hasFooter,
238+
`${wf.name} (${wf.agentName}): compass section missing footer with "evidence-based recommendations"`
239+
);
240+
});
241+
242+
// 4.4: Agent references match registered agents
243+
it('all agent references match registered agents', () => {
244+
assert.ok(parsed, `${wf.name}: no valid compass table found`);
245+
for (let i = 0; i < parsed.rows.length; i++) {
246+
const row = parsed.rows[i];
247+
const agentRef = extractAgentName(row.agent);
248+
assert.ok(
249+
agentRef && agentNameMap.has(agentRef),
250+
`${wf.name} (${wf.agentName}): compass row ${i + 1} references unknown agent '${agentRef}' — registered: [${[...agentNameMap.keys()].join(', ')}]`
251+
);
252+
}
253+
});
254+
255+
// 4.5: Workflow references match registered workflows
256+
it('all workflow references match registered workflows', () => {
257+
assert.ok(parsed, `${wf.name}: no valid compass table found`);
258+
for (let i = 0; i < parsed.rows.length; i++) {
259+
const row = parsed.rows[i];
260+
const wfRef = row.workflow.trim().toLowerCase();
261+
assert.ok(
262+
registeredWorkflowNames.has(wfRef),
263+
`${wf.name} (${wf.agentName}): compass row ${i + 1} references unknown workflow '${wfRef}' — registered: [${[...registeredWorkflowNames].join(', ')}]`
264+
);
265+
}
266+
});
267+
});
268+
}
269+
});
270+
271+
// ─── Test Suite: Cross-Reference Integrity (AC: 1, 2, 3, 5) ────
272+
273+
describe('Cross-Reference Integrity', () => {
274+
// 5.1: Vacuous pass guard — routing reference exists
275+
it('compass-routing-reference.md exists and is non-empty', () => {
276+
assert.ok(
277+
fs.existsSync(COMPASS_REF),
278+
`compass-routing-reference.md not found at ${COMPASS_REF}`
279+
);
280+
const content = fs.readFileSync(COMPASS_REF, 'utf8');
281+
assert.ok(
282+
content.trim().length > 0,
283+
'compass-routing-reference.md exists but is empty'
284+
);
285+
});
286+
287+
// 5.2: Reference mentions all 7 agent short names
288+
it('compass routing reference mentions all registered agent names', () => {
289+
const content = fs.readFileSync(COMPASS_REF, 'utf8').toLowerCase();
290+
for (const [agentName] of agentNameMap) {
291+
assert.ok(
292+
content.includes(agentName),
293+
`compass-routing-reference.md does not mention agent '${agentName}' — registered agents: [${[...agentNameMap.keys()].join(', ')}]`
294+
);
295+
}
296+
});
297+
298+
// 5.3: Reference mentions all 22 workflow names
299+
it('compass routing reference mentions all registered workflow names', () => {
300+
const content = fs.readFileSync(COMPASS_REF, 'utf8').toLowerCase();
301+
const missing = [];
302+
for (const wfName of registeredWorkflowNames) {
303+
if (!content.includes(wfName.toLowerCase())) {
304+
missing.push(wfName);
305+
}
306+
}
307+
assert.strictEqual(
308+
missing.length,
309+
0,
310+
`compass-routing-reference.md missing ${missing.length} workflow(s): [${missing.join(', ')}] — total registered: ${registeredWorkflowNames.size}`
311+
);
312+
});
313+
314+
// 5.4: Per agent — all workflowDirs exist as filesystem directories
315+
for (const agent of agents) {
316+
for (let i = 0; i < agent.workflowDirs.length; i++) {
317+
const wfDir = agent.workflowDirs[i];
318+
const wfName = agent.workflowNames[i];
319+
it(`${agent.name}: workflow directory exists for '${wfName}'`, () => {
320+
assert.ok(
321+
fs.existsSync(wfDir) && fs.statSync(wfDir).isDirectory(),
322+
`${agent.name} (${agent.id}): registry says workflow '${wfName}' exists but directory not found at ${wfDir}`
323+
);
324+
});
325+
}
326+
}
327+
328+
// 5.5: Per workflow with compass — routing targets reference at least one different agent's workflow
329+
const nonExemptWorkflows = allWorkflows.filter(
330+
w => !COMPASS_EXEMPT.includes(w.name)
331+
);
332+
333+
for (const wf of nonExemptWorkflows) {
334+
it(`${wf.name} (${wf.agentName}): compass routes to at least one different agent's workflow`, () => {
335+
const stepFile = findFinalStepFile(wf.dir);
336+
assert.ok(stepFile, `${wf.name}: no step files found`);
337+
const content = fs.readFileSync(stepFile, 'utf8');
338+
const compass = extractCompassSection(content);
339+
assert.ok(compass, `${wf.name}: no compass section found`);
340+
const parsed = parseCompassTable(compass);
341+
assert.ok(parsed, `${wf.name}: could not parse compass table`);
342+
343+
const ownerAgent = wf.agentName.toLowerCase();
344+
const hasCrossAgentRoute = parsed.rows.some(row => {
345+
const targetAgent = extractAgentName(row.agent);
346+
return targetAgent && targetAgent !== ownerAgent;
347+
});
348+
349+
assert.ok(
350+
hasCrossAgentRoute,
351+
`${wf.name} (${wf.agentName}): compass table only routes to own agent — expected at least one cross-agent route`
352+
);
353+
});
354+
}
355+
});

0 commit comments

Comments
 (0)