Skip to content

Commit 62a4656

Browse files
committed
Create p0-noah.test.js
1 parent 198806c commit 62a4656

File tree

1 file changed

+206
-0
lines changed

1 file changed

+206
-0
lines changed

tests/p0/p0-noah.test.js

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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+
STEP_PATTERN,
14+
} = require('./helpers');
15+
16+
const NOAH_ID = 'production-intelligence-specialist';
17+
const NOAH_WORKFLOW_NAMES = ['signal-interpretation', 'behavior-analysis', 'production-monitoring'];
18+
19+
// ─── P0 Noah: Activation Sequence (FR7) ────────────────────────
20+
21+
describe('P0 Noah: Activation Sequence', () => {
22+
let def;
23+
let rawContent;
24+
25+
before(() => {
26+
def = loadAgentDefinition(NOAH_ID);
27+
rawContent = fs.readFileSync(path.join(AGENTS_DIR, `${NOAH_ID}.md`), 'utf8');
28+
});
29+
30+
it('persona role contains "Signal Interpretation"', () => {
31+
assert.ok(
32+
def.persona.role.includes('Signal Interpretation'),
33+
`Noah (${NOAH_ID}): persona role should contain "Signal Interpretation", got "${def.persona.role}"`
34+
);
35+
});
36+
37+
it('persona identity references the Sensitize stream', () => {
38+
assert.ok(
39+
def.persona.identity.includes('Sensitize'),
40+
`Noah (${NOAH_ID}): persona identity should reference "Sensitize" stream`
41+
);
42+
});
43+
44+
it('persona communication_style contains characteristic phrase', () => {
45+
const style = def.persona.communication_style;
46+
assert.ok(
47+
style.includes('The signal indicates'),
48+
`Noah (${NOAH_ID}): communication_style should contain "The signal indicates"`
49+
);
50+
});
51+
52+
it('has exactly 7 menu items with expected cmd triggers', () => {
53+
assert.equal(
54+
def.menuItems.length,
55+
7,
56+
`Noah (${NOAH_ID}): expected 7 menu items, got ${def.menuItems.length}`
57+
);
58+
const expectedTriggers = ['MH', 'CH', 'SI', 'BA', 'MO', 'PM', 'DA'];
59+
for (const trigger of expectedTriggers) {
60+
const found = def.menuItems.some(item => item.cmd.startsWith(trigger));
61+
assert.ok(
62+
found,
63+
`Noah (${NOAH_ID}): missing menu item with cmd starting with "${trigger}"`
64+
);
65+
}
66+
});
67+
68+
it('exec-path menu items reference existing files on disk', () => {
69+
const execRegex = /<item\s[^>]*exec="([^"]+)"[^>]*>/g;
70+
const execPaths = [];
71+
let m;
72+
while ((m = execRegex.exec(rawContent)) !== null) {
73+
execPaths.push(m[1]);
74+
}
75+
assert.ok(
76+
execPaths.length >= 4,
77+
`Noah (${NOAH_ID}): expected at least 4 exec paths, found ${execPaths.length}`
78+
);
79+
for (const execPath of execPaths) {
80+
const resolved = execPath.replace(/\{project-root\}/g, PACKAGE_ROOT);
81+
assert.ok(
82+
fs.existsSync(resolved),
83+
`Noah (${NOAH_ID}): exec path not found on disk: ${resolved}`
84+
);
85+
}
86+
});
87+
88+
it('activation step 2 references config.yaml loading with error handling', () => {
89+
const step2Match = rawContent.match(/<step n="2">([\s\S]*?)<step n="3">/);
90+
assert.ok(
91+
step2Match,
92+
`Noah (${NOAH_ID}): could not extract activation step 2 content`
93+
);
94+
assert.ok(
95+
step2Match[1].includes('config.yaml'),
96+
`Noah (${NOAH_ID}): step 2 should reference "config.yaml" loading`
97+
);
98+
assert.ok(
99+
step2Match[1].includes('Configuration Error'),
100+
`Noah (${NOAH_ID}): step 2 should contain "Configuration Error" handling`
101+
);
102+
});
103+
104+
it('rules section has at least 5 rules', () => {
105+
const ruleMatches = rawContent.match(/<r>/g);
106+
const ruleCount = ruleMatches ? ruleMatches.length : 0;
107+
assert.ok(
108+
ruleCount >= 5,
109+
`Noah (${NOAH_ID}): expected at least 5 rules, found ${ruleCount}`
110+
);
111+
});
112+
});
113+
114+
// ─── P0 Noah: Workflow Execution Output (FR8) ──────────────────
115+
116+
describe('P0 Noah: Workflow Execution Output', () => {
117+
for (const wfName of NOAH_WORKFLOW_NAMES) {
118+
describe(`${wfName}`, () => {
119+
const wfDir = path.join(WORKFLOWS_DIR, wfName);
120+
const stepsDir = path.join(wfDir, 'steps');
121+
let stepFiles;
122+
123+
before(() => {
124+
assert.ok(
125+
fs.existsSync(stepsDir) && fs.statSync(stepsDir).isDirectory(),
126+
`Noah (${NOAH_ID}): ${wfName}/steps/ directory not found at ${stepsDir}`
127+
);
128+
stepFiles = fs.readdirSync(stepsDir)
129+
.filter(f => STEP_PATTERN.test(f))
130+
.sort();
131+
});
132+
133+
it('workflow.md contains type, description, and author fields', () => {
134+
const wfContent = fs.readFileSync(path.join(wfDir, 'workflow.md'), 'utf8');
135+
assert.ok(
136+
wfContent.includes('type:'),
137+
`Noah (${NOAH_ID}): ${wfName}/workflow.md missing "type:" field`
138+
);
139+
assert.ok(
140+
wfContent.includes('description:'),
141+
`Noah (${NOAH_ID}): ${wfName}/workflow.md missing "description:" field`
142+
);
143+
assert.ok(
144+
wfContent.includes('author:'),
145+
`Noah (${NOAH_ID}): ${wfName}/workflow.md missing "author:" field`
146+
);
147+
});
148+
149+
// Noah's workflows have NO template files — skip template tests
150+
151+
it('steps reference the next step in sequence', () => {
152+
assert.ok(
153+
stepFiles.length >= 2,
154+
`Noah (${NOAH_ID}): ${wfName} needs at least 2 steps for cross-reference check, found ${stepFiles.length}`
155+
);
156+
for (let i = 0; i < stepFiles.length - 1; i++) {
157+
const stepContent = fs.readFileSync(path.join(stepsDir, stepFiles[i]), 'utf8');
158+
const nextNum = String(i + 2).padStart(2, '0');
159+
assert.ok(
160+
stepContent.includes(`step-${nextNum}`),
161+
`Noah (${NOAH_ID}): ${wfName}/${stepFiles[i]} should reference step-${nextNum}`
162+
);
163+
}
164+
});
165+
166+
it('final step contains synthesize/artifact content', () => {
167+
const lastFile = stepFiles[stepFiles.length - 1];
168+
assert.ok(
169+
lastFile,
170+
`Noah (${NOAH_ID}): ${wfName} has no step files to check for synthesize content`
171+
);
172+
const content = fs.readFileSync(path.join(stepsDir, lastFile), 'utf8').toLowerCase();
173+
const hasSynthesize = content.includes('synthesize') ||
174+
content.includes('artifact') ||
175+
content.includes('final');
176+
assert.ok(
177+
hasSynthesize,
178+
`Noah (${NOAH_ID}): ${wfName}/${lastFile} should contain synthesize/artifact/final content`
179+
);
180+
});
181+
182+
it('final step suggests next workflows', () => {
183+
const lastFile = stepFiles[stepFiles.length - 1];
184+
assert.ok(
185+
lastFile,
186+
`Noah (${NOAH_ID}): ${wfName} has no step files to check for next workflow suggestions`
187+
);
188+
const content = fs.readFileSync(path.join(stepsDir, lastFile), 'utf8');
189+
const hasHandoff = content.includes('next') || content.includes('Next') ||
190+
content.includes('Suggest') || content.includes('suggest');
191+
assert.ok(
192+
hasHandoff,
193+
`Noah (${NOAH_ID}): ${wfName}/${lastFile} should suggest next workflows`
194+
);
195+
});
196+
197+
it('has exactly 5 step files', () => {
198+
assert.equal(
199+
stepFiles.length,
200+
5,
201+
`Noah (${NOAH_ID}): ${wfName} expected 5 steps, got ${stepFiles.length}`
202+
);
203+
});
204+
});
205+
}
206+
});

0 commit comments

Comments
 (0)