Skip to content

Commit 3f1ae8d

Browse files
committed
Create p0-wade.test.js
1 parent 69d84ed commit 3f1ae8d

File tree

1 file changed

+350
-0
lines changed

1 file changed

+350
-0
lines changed

tests/p0/p0-wade.test.js

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
'use strict';
2+
3+
const { describe, it, before } = require('node:test');
4+
const assert = require('node:assert/strict');
5+
const path = require('path');
6+
const fs = require('fs');
7+
8+
const {
9+
loadAgentDefinition,
10+
PACKAGE_ROOT,
11+
AGENTS_DIR,
12+
WORKFLOWS_DIR,
13+
} = require('./helpers');
14+
const {
15+
AGENTS,
16+
WORKFLOWS,
17+
AGENT_IDS,
18+
AGENT_FILES,
19+
WAVE3_WORKFLOW_NAMES,
20+
} = require('../../scripts/update/lib/agent-registry');
21+
22+
const WADE_ID = 'lean-experiments-specialist';
23+
const WADE_WORKFLOW_NAMES = ['mvp', 'lean-experiment', 'proof-of-concept', 'proof-of-value'];
24+
const MVP_WORKFLOW = 'mvp';
25+
const STEP_PATTERN = /^step-\d{2}(-[^.]+)?\.md$/;
26+
27+
// ─── P0 Wade: Activation Sequence (FR7) ────────────────────────
28+
29+
describe('P0 Wade: Activation Sequence', () => {
30+
let def;
31+
let rawContent;
32+
33+
before(() => {
34+
def = loadAgentDefinition(WADE_ID);
35+
rawContent = fs.readFileSync(path.join(AGENTS_DIR, `${WADE_ID}.md`), 'utf8');
36+
});
37+
38+
it('persona role contains "Validated Learning Expert"', () => {
39+
assert.ok(
40+
def.persona.role.includes('Validated Learning Expert'),
41+
`Wade (${WADE_ID}): persona role should contain "Validated Learning Expert", got "${def.persona.role}"`
42+
);
43+
});
44+
45+
it('persona identity references the Externalize stream', () => {
46+
assert.ok(
47+
def.persona.identity.includes('Externalize'),
48+
`Wade (${WADE_ID}): persona identity should reference "Externalize" stream`
49+
);
50+
});
51+
52+
it('persona communication_style contains characteristic phrase', () => {
53+
const style = def.persona.communication_style;
54+
const hasPhrase = style.includes('riskiest assumption') || style.includes('smallest experiment');
55+
assert.ok(
56+
hasPhrase,
57+
`Wade (${WADE_ID}): communication_style should contain "riskiest assumption" or "smallest experiment"`
58+
);
59+
});
60+
61+
it('has exactly 9 menu items with expected cmd triggers', () => {
62+
assert.equal(
63+
def.menuItems.length,
64+
9,
65+
`Wade (${WADE_ID}): expected 9 menu items, got ${def.menuItems.length}`
66+
);
67+
const expectedTriggers = ['MH', 'CH', 'ME', 'LE', 'PC', 'PV', 'VE', 'PM', 'DA'];
68+
for (const trigger of expectedTriggers) {
69+
const found = def.menuItems.some(item => item.cmd.startsWith(trigger));
70+
assert.ok(
71+
found,
72+
`Wade (${WADE_ID}): missing menu item with cmd starting with "${trigger}"`
73+
);
74+
}
75+
});
76+
77+
it('exec-path menu items reference existing files on disk', () => {
78+
const execRegex = /<item\s[^>]*exec="([^"]+)"[^>]*>/g;
79+
const execPaths = [];
80+
let m;
81+
while ((m = execRegex.exec(rawContent)) !== null) {
82+
execPaths.push(m[1]);
83+
}
84+
assert.ok(
85+
execPaths.length >= 6,
86+
`Wade (${WADE_ID}): expected at least 6 exec paths, found ${execPaths.length}`
87+
);
88+
for (const execPath of execPaths) {
89+
const resolved = execPath.replace(/\{project-root\}/g, PACKAGE_ROOT);
90+
assert.ok(
91+
fs.existsSync(resolved),
92+
`Wade (${WADE_ID}): exec path not found on disk: ${resolved}`
93+
);
94+
}
95+
});
96+
97+
it('activation step 2 references config.yaml loading with error handling', () => {
98+
const step2Match = rawContent.match(/<step n="2">([\s\S]*?)<step n="3">/);
99+
assert.ok(
100+
step2Match,
101+
`Wade (${WADE_ID}): could not extract activation step 2 content`
102+
);
103+
assert.ok(
104+
step2Match[1].includes('config.yaml'),
105+
`Wade (${WADE_ID}): step 2 should reference "config.yaml" loading`
106+
);
107+
assert.ok(
108+
step2Match[1].includes('Configuration Error'),
109+
`Wade (${WADE_ID}): step 2 should contain "Configuration Error" handling`
110+
);
111+
});
112+
113+
it('rules section has at least 5 rules', () => {
114+
const ruleMatches = rawContent.match(/<r>/g);
115+
const ruleCount = ruleMatches ? ruleMatches.length : 0;
116+
assert.ok(
117+
ruleCount >= 5,
118+
`Wade (${WADE_ID}): expected at least 5 rules, found ${ruleCount}`
119+
);
120+
});
121+
});
122+
123+
// ─── P0 Wade: Workflow Execution Output (FR8) ──────────────────
124+
125+
describe('P0 Wade: Workflow Execution Output', () => {
126+
for (const wfName of WADE_WORKFLOW_NAMES) {
127+
describe(`${wfName}`, () => {
128+
const wfDir = path.join(WORKFLOWS_DIR, wfName);
129+
const stepsDir = path.join(wfDir, 'steps');
130+
let stepFiles;
131+
132+
before(() => {
133+
assert.ok(
134+
fs.existsSync(stepsDir) && fs.statSync(stepsDir).isDirectory(),
135+
`Wade (${WADE_ID}): ${wfName}/steps/ directory not found at ${stepsDir}`
136+
);
137+
stepFiles = fs.readdirSync(stepsDir)
138+
.filter(f => STEP_PATTERN.test(f))
139+
.sort();
140+
});
141+
142+
it('workflow.md contains type, description, and author fields', () => {
143+
const wfContent = fs.readFileSync(path.join(wfDir, 'workflow.md'), 'utf8');
144+
assert.ok(
145+
wfContent.includes('type:'),
146+
`Wade (${WADE_ID}): ${wfName}/workflow.md missing "type:" field`
147+
);
148+
assert.ok(
149+
wfContent.includes('description:'),
150+
`Wade (${WADE_ID}): ${wfName}/workflow.md missing "description:" field`
151+
);
152+
assert.ok(
153+
wfContent.includes('author:'),
154+
`Wade (${WADE_ID}): ${wfName}/workflow.md missing "author:" field`
155+
);
156+
});
157+
158+
it('template file exists', () => {
159+
const templatePath = path.join(wfDir, `${wfName}.template.md`);
160+
assert.ok(
161+
fs.existsSync(templatePath),
162+
`Wade (${WADE_ID}): ${wfName} template not found at ${templatePath}`
163+
);
164+
});
165+
166+
it('template file contains placeholder variables', () => {
167+
const templatePath = path.join(wfDir, `${wfName}.template.md`);
168+
const content = fs.readFileSync(templatePath, 'utf8');
169+
const placeholderPattern = /\{[a-z][-a-z]*\}/;
170+
assert.ok(
171+
placeholderPattern.test(content),
172+
`Wade (${WADE_ID}): ${wfName} template should contain {variable-name} placeholders`
173+
);
174+
});
175+
176+
// Cross-reference and synthesize tests run ONLY for MVP.
177+
// Wade's other 3 workflows (lean-experiment, proof-of-concept, proof-of-value)
178+
// have placeholder stub steps that lack cross-references and synthesize content.
179+
if (wfName === MVP_WORKFLOW) {
180+
it('steps 01-05 reference the next step in sequence', () => {
181+
assert.ok(
182+
stepFiles.length >= 2,
183+
`Wade (${WADE_ID}): ${wfName} needs at least 2 steps for cross-reference check, found ${stepFiles.length}`
184+
);
185+
for (let i = 0; i < stepFiles.length - 1; i++) {
186+
const stepContent = fs.readFileSync(path.join(stepsDir, stepFiles[i]), 'utf8');
187+
const nextNum = String(i + 2).padStart(2, '0');
188+
assert.ok(
189+
stepContent.includes(`step-${nextNum}`),
190+
`Wade (${WADE_ID}): ${wfName}/${stepFiles[i]} should reference step-${nextNum}`
191+
);
192+
}
193+
});
194+
195+
it('final step contains synthesize/artifact content', () => {
196+
const lastFile = stepFiles[stepFiles.length - 1];
197+
assert.ok(
198+
lastFile,
199+
`Wade (${WADE_ID}): ${wfName} has no step files to check for synthesize content`
200+
);
201+
const content = fs.readFileSync(path.join(stepsDir, lastFile), 'utf8').toLowerCase();
202+
const hasSynthesize = content.includes('synthesize') ||
203+
content.includes('artifact') ||
204+
content.includes('final');
205+
assert.ok(
206+
hasSynthesize,
207+
`Wade (${WADE_ID}): ${wfName}/${lastFile} should contain synthesize/artifact/final content`
208+
);
209+
});
210+
}
211+
212+
it('final step suggests next workflows', () => {
213+
const lastFile = stepFiles[stepFiles.length - 1];
214+
assert.ok(
215+
lastFile,
216+
`Wade (${WADE_ID}): ${wfName} has no step files to check for next workflow suggestions`
217+
);
218+
const content = fs.readFileSync(path.join(stepsDir, lastFile), 'utf8');
219+
const hasHandoff = content.includes('next') || content.includes('Next') ||
220+
content.includes('Suggest') || content.includes('suggest');
221+
assert.ok(
222+
hasHandoff,
223+
`Wade (${WADE_ID}): ${wfName}/${lastFile} should suggest next workflows`
224+
);
225+
});
226+
227+
it('has exactly 6 step files', () => {
228+
assert.equal(
229+
stepFiles.length,
230+
6,
231+
`Wade (${WADE_ID}): ${wfName} expected 6 steps, got ${stepFiles.length}`
232+
);
233+
});
234+
});
235+
}
236+
});
237+
238+
// ─── P0 Wade: Infrastructure Integration (FR10) ────────────────
239+
240+
describe('P0 Wade: Infrastructure Integration', () => {
241+
let wadeRegistry;
242+
let def;
243+
244+
before(() => {
245+
wadeRegistry = AGENTS.find(a => a.id === WADE_ID);
246+
def = loadAgentDefinition(WADE_ID);
247+
});
248+
249+
it('registry entry exists for Wade', () => {
250+
assert.ok(
251+
wadeRegistry,
252+
`Wade (${WADE_ID}): registry entry not found in AGENTS array`
253+
);
254+
});
255+
256+
it('registry persona has all 4 fields as non-empty strings', () => {
257+
const fields = ['role', 'identity', 'communication_style', 'expertise'];
258+
for (const field of fields) {
259+
assert.ok(
260+
typeof wadeRegistry.persona[field] === 'string' && wadeRegistry.persona[field].length > 0,
261+
`Wade (${WADE_ID}): registry persona.${field} should be a non-empty string`
262+
);
263+
}
264+
});
265+
266+
it('registry stream is "Externalize"', () => {
267+
assert.equal(
268+
wadeRegistry.stream,
269+
'Externalize',
270+
`Wade (${WADE_ID}): registry stream should be "Externalize", got "${wadeRegistry.stream}"`
271+
);
272+
});
273+
274+
it('WORKFLOWS contains exactly 4 entries for Wade with correct names', () => {
275+
const wadeWorkflows = WORKFLOWS.filter(w => w.agent === WADE_ID);
276+
assert.equal(
277+
wadeWorkflows.length,
278+
4,
279+
`Wade (${WADE_ID}): expected 4 workflows, got ${wadeWorkflows.length}`
280+
);
281+
const names = wadeWorkflows.map(w => w.name);
282+
assert.deepStrictEqual(
283+
names,
284+
WADE_WORKFLOW_NAMES,
285+
`Wade (${WADE_ID}): workflow names mismatch`
286+
);
287+
});
288+
289+
it('Wade ID appears in derived AGENT_IDS', () => {
290+
assert.ok(
291+
AGENT_IDS.includes(WADE_ID),
292+
`Wade (${WADE_ID}): ID not found in AGENT_IDS`
293+
);
294+
});
295+
296+
it('Wade file appears in derived AGENT_FILES', () => {
297+
assert.ok(
298+
AGENT_FILES.includes(`${WADE_ID}.md`),
299+
`Wade (${WADE_ID}): file not found in AGENT_FILES`
300+
);
301+
});
302+
303+
it('Wade workflows are NOT in WAVE3_WORKFLOW_NAMES', () => {
304+
const registryNames = WORKFLOWS.filter(w => w.agent === WADE_ID).map(w => w.name);
305+
for (const wfName of registryNames) {
306+
assert.ok(
307+
!WAVE3_WORKFLOW_NAMES.has(wfName),
308+
`Wade (${WADE_ID}): workflow "${wfName}" should NOT be in WAVE3_WORKFLOW_NAMES (Wave 1 agent)`
309+
);
310+
}
311+
});
312+
313+
it('persona cross-validation: registry and agent file roles share "Validated Learning" keyword', () => {
314+
assert.ok(
315+
wadeRegistry.persona.role.includes('Validated Learning'),
316+
`Wade (${WADE_ID}): registry persona.role should contain "Validated Learning"`
317+
);
318+
assert.ok(
319+
def.persona.role.includes('Validated Learning'),
320+
`Wade (${WADE_ID}): agent file <role> should contain "Validated Learning"`
321+
);
322+
});
323+
324+
it('persona cross-validation: communication styles share "validated learning" pattern', () => {
325+
assert.ok(
326+
wadeRegistry.persona.communication_style.includes('validated learning'),
327+
`Wade (${WADE_ID}): registry communication_style should contain "validated learning"`
328+
);
329+
assert.ok(
330+
def.persona.communication_style.includes('validated learning'),
331+
`Wade (${WADE_ID}): agent file <communication_style> should contain "validated learning"`
332+
);
333+
});
334+
335+
it('agent tag name matches registry name exactly', () => {
336+
assert.equal(
337+
def.agentAttrs.name,
338+
wadeRegistry.name,
339+
`Wade (${WADE_ID}): agent tag name "${def.agentAttrs.name}" should match registry name "${wadeRegistry.name}"`
340+
);
341+
});
342+
343+
it('agent tag icon matches registry icon', () => {
344+
assert.equal(
345+
def.agentAttrs.icon,
346+
wadeRegistry.icon,
347+
`Wade (${WADE_ID}): agent tag icon should match registry icon`
348+
);
349+
});
350+
});

0 commit comments

Comments
 (0)