Validate Orchestrator Implementation Plan โ
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Replace the monolithic validate npm script chain with a Node.js orchestrator that provides beautiful terminal output, standardized production-only audit across all workspaces, and --workspace/--scope filtering for targeted validation during development.
Architecture: A single tooling/scripts/validate.js script orchestrates all validation checks. It spawns child processes for each check, wrapping output in ANSI-colored section banners with timing. The existing audit-production.js is enhanced to scan all workspace package.json files (not just root) for production dependencies. Per-workspace validate scripts in admin/discord/firebase are updated to remove their redundant raw npm audit calls. CI workflow is updated to call the same orchestrator. CLAUDE.md is updated with new commands.
Tech Stack: Node.js (ESM), child_process (spawn for streaming output), ANSI escape codes (no external deps), npm workspaces
File Structure โ
| Action | File | Responsibility |
|---|---|---|
| Create | tooling/scripts/validate.js | Main orchestrator โ runs checks sequentially, wraps output in section banners, supports --workspace and --scope filters, prints summary table |
| Modify | tooling/scripts/audit-production.js | Enhance to scan ALL workspace package.json files for production deps, not just root |
| Modify | package.json (root) | Update validate script to call node tooling/scripts/validate.js, add validate:workspace and validate:scope convenience aliases |
| Modify | apps/admin/package.json | Remove npm audit from validate script (now centralized) |
| Modify | services/bots/discord/package.json | Remove npm audit from validate script (now centralized) |
| Modify | services/functions/firebase/package.json | Remove npm audit from validate script (now centralized) |
| Modify | .github/workflows/ci.yml | Replace ad-hoc audit commands with npm run validate -- --scope audit or keep using npm run audit (already centralized) |
| Modify | CLAUDE.md | Update Common Commands section with new validate flags and document the audit standardization |
Task 1: Enhance audit-production.js to scan all workspaces โ
Files:
- Modify:
tooling/scripts/audit-production.js
This is the foundation โ the audit script must know about ALL workspace production deps before the orchestrator can rely on it.
- [ ] Step 1: Read the current
audit-production.js
Already read above. The script currently reads only root package.json at line 26-28. We need it to also read every workspace package.json and collect their dependencies keys.
- [ ] Step 2: Modify
audit-production.jsto gather workspace production deps
Replace the section that reads only root package.json (lines 24-34) with logic that:
- Reads root
package.jsonto get theworkspacesarray - Iterates each workspace, reads its
package.json - Collects ALL production dependency names into a single
prodDepsset - Logs how many deps came from root vs workspaces
// Get root package.json
const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
const rootPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
// Collect production dependencies from root AND all workspaces
const prodDeps = new Set(Object.keys(rootPackageJson.dependencies || {}));
const rootCount = prodDeps.size;
const workspaces = rootPackageJson.workspaces || [];
for (const ws of workspaces) {
const wsPackagePath = path.join(__dirname, '..', '..', ws, 'package.json');
try {
const wsPackageJson = JSON.parse(fs.readFileSync(wsPackagePath, 'utf8'));
for (const dep of Object.keys(wsPackageJson.dependencies || {})) {
prodDeps.add(dep);
}
} catch {
// Workspace package.json may not exist (optional workspace)
}
}
console.log(`๐ฆ Found ${prodDeps.size} production dependencies (${rootCount} root + ${prodDeps.size - rootCount} workspaces)\n`);- [ ] Step 3: Run the enhanced audit to verify it works
Run: node tooling/scripts/audit-production.js --level=moderate Expected: Output shows "Found N production dependencies (X root + Y workspaces)" with a larger count than before (was 6 root deps, should now include express, firebase-admin, zod, pino, etc. from API services).
- [ ] Step 4: Commit
git add tooling/scripts/audit-production.js
git commit -m "feat(audit): scan all workspace production deps, not just root"Task 2: Remove redundant audit from workspace validate scripts โ
Files:
- Modify:
apps/admin/package.json - Modify:
services/bots/discord/package.json - Modify:
services/functions/firebase/package.json
Now that the central audit covers all workspaces, remove the per-workspace npm audit calls.
- [ ] Step 1: Update
apps/admin/package.jsonvalidate script
Change line 11 from:
"validate": "npm run build && npm audit --audit-level=moderate && echo 'Admin validation: OK'"to:
"validate": "npm run build && echo 'Admin validation: OK'"- [ ] Step 2: Update
services/bots/discord/package.jsonvalidate script
Change line 11 from:
"validate": "npm audit --audit-level=moderate && echo 'Discord bot validation: OK'"to:
"validate": "echo 'Discord bot validation: OK'"Keep the standalone "audit" script for manual use โ just remove it from validate.
- [ ] Step 3: Update
services/functions/firebase/package.jsonvalidate script
Change line 18 from:
"validate": "npm audit --audit-level=moderate && echo 'Firebase functions validation: OK'"to:
"validate": "echo 'Firebase functions validation: OK'"Keep the standalone "audit" script for manual use.
- [ ] Step 4: Verify each workspace validate still works
Run:
npm run validate -w apps/admin 2>&1 | tail -1
npm run validate -w services/bots/discord 2>&1 | tail -1
npm run validate -w services/functions/firebase 2>&1 | tail -1Expected: Each prints its "validation: OK" message without running audit.
- [ ] Step 5: Commit
git add apps/admin/package.json services/bots/discord/package.json services/functions/firebase/package.json
git commit -m "refactor(validate): remove per-workspace audit, now centralized"Task 3: Create the validate orchestrator script โ
Files:
- Create:
tooling/scripts/validate.js
This is the main deliverable. A Node.js ESM script that runs checks sequentially with beautiful terminal output.
- [ ] Step 1: Create
tooling/scripts/validate.jswith argument parsing
The script should:
- Parse
--workspace <name>and--scope <comma-separated>fromprocess.argv - Define the full check registry (name, scope, workspace affinity, command)
- Filter checks based on flags
- Run checks sequentially, wrapping each in section banners
- Print a summary table at the end
#!/usr/bin/env node
/**
* Lantern Validate Orchestrator
*
* Runs all validation checks with clear visual separation.
* Supports filtering:
* --workspace apps/web Run only checks for this workspace
* --scope lint,audit Run only these check categories
*
* Usage:
* node tooling/scripts/validate.js # Full suite
* node tooling/scripts/validate.js --workspace apps/web # Web app only
* node tooling/scripts/validate.js --scope audit # Audit only
*/
import { spawn } from 'child_process';
import { performance } from 'perf_hooks';
// โโ ANSI helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const c = {
reset: '\x1b[0m',
bold: '\x1b[1m',
dim: '\x1b[2m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
white: '\x1b[37m',
bgRed: '\x1b[41m',
bgGreen: '\x1b[42m',
bgBlue: '\x1b[44m',
};
const COLS = Math.min(process.stdout.columns || 80, 100);
function banner(label, color = c.cyan) {
const pad = Math.max(0, COLS - label.length - 6);
const line = 'โ'.repeat(pad);
console.log(`\n${color}${c.bold}โโ ${label} ${line}${c.reset}\n`);
}
function divider() {
console.log(`${c.dim}${'โ'.repeat(COLS)}${c.reset}`);
}
function formatDuration(ms) {
if (ms < 1000) return `${Math.round(ms)}ms`;
return `${(ms / 1000).toFixed(1)}s`;
}
// โโ Check registry โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const CHECKS = [
{
name: 'Formatting',
scope: 'format',
workspace: 'apps/web',
cmd: 'npm', args: ['run', 'format'],
},
{
name: 'Header Validation',
scope: 'headers',
workspace: 'apps/web',
cmd: 'npm', args: ['run', 'validate:headers'],
},
{
name: 'Workflow Consistency',
scope: 'workflows',
workspace: null, // global
cmd: 'npm', args: ['run', 'test:workflows:validate'],
},
{
name: 'Web App Tests & Coverage',
scope: 'test',
workspace: 'apps/web',
cmd: 'npm', args: ['run', 'test:coverage', '-w', 'apps/web'],
},
{
name: 'Format Check',
scope: 'format',
workspace: 'apps/web',
cmd: 'npm', args: ['run', 'format:check', '-w', 'apps/web'],
},
{
name: 'Linting',
scope: 'lint',
workspace: 'apps/web',
cmd: 'npm', args: ['run', 'lint', '-w', 'apps/web'],
},
{
name: 'Security Audit (all workspaces)',
scope: 'audit',
workspace: null, // global โ covers all workspaces
cmd: 'node', args: ['tooling/scripts/audit-production.js', '--level=moderate'],
},
{
name: 'Admin Validation',
scope: 'workspace',
workspace: 'apps/admin',
cmd: 'npm', args: ['run', 'validate', '-w', 'apps/admin'],
},
{
name: 'Analytics API Validation',
scope: 'workspace',
workspace: 'services/api/analytics',
cmd: 'npm', args: ['run', 'validate', '-w', 'services/api/analytics'],
},
{
name: 'Venue API Validation',
scope: 'workspace',
workspace: 'services/api/venues',
cmd: 'npm', args: ['run', 'validate', '-w', 'services/api/venues'],
},
{
name: 'Discord Bot Validation',
scope: 'workspace',
workspace: 'services/bots/discord',
cmd: 'npm', args: ['run', 'validate', '-w', 'services/bots/discord'],
},
{
name: 'Firebase Functions Validation',
scope: 'workspace',
workspace: 'services/functions/firebase',
cmd: 'npm', args: ['run', 'validate', '-w', 'services/functions/firebase'],
},
];
// โโ Argument parsing โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function parseArgs(argv) {
const args = { workspace: null, scopes: null };
for (let i = 2; i < argv.length; i++) {
if (argv[i] === '--workspace' && argv[i + 1]) {
args.workspace = argv[++i];
} else if (argv[i] === '--scope' && argv[i + 1]) {
args.scopes = argv[++i].split(',').map(s => s.trim());
}
}
return args;
}
// โโ Filter checks โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function filterChecks(checks, { workspace, scopes }) {
let filtered = checks;
if (workspace) {
filtered = filtered.filter(
ch => ch.workspace === workspace || ch.workspace === null
);
}
if (scopes) {
filtered = filtered.filter(ch => scopes.includes(ch.scope));
}
return filtered;
}
// โโ Run a single check โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function runCheck(check) {
return new Promise((resolve) => {
const start = performance.now();
const proc = spawn(check.cmd, check.args, {
stdio: 'inherit',
shell: process.platform === 'win32',
});
proc.on('close', (code) => {
const duration = performance.now() - start;
resolve({ name: check.name, code, duration });
});
proc.on('error', (err) => {
const duration = performance.now() - start;
console.error(` Error spawning: ${err.message}`);
resolve({ name: check.name, code: 1, duration });
});
});
}
// โโ Summary table โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function printSummary(results) {
const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
const passed = results.filter(r => r.code === 0).length;
const failed = results.filter(r => r.code !== 0).length;
console.log('');
banner('Summary', failed > 0 ? c.red : c.green);
const nameCol = Math.max(...results.map(r => r.name.length), 6) + 2;
// Header
console.log(
` ${'Check'.padEnd(nameCol)} ${'Status'.padEnd(8)} Duration`
);
divider();
for (const r of results) {
const status = r.code === 0
? `${c.green}PASS${c.reset}`
: `${c.red}FAIL${c.reset}`;
const dur = formatDuration(r.duration);
console.log(` ${r.name.padEnd(nameCol)} ${status} ${c.dim}${dur}${c.reset}`);
}
divider();
console.log(
` ${c.bold}Total: ${passed} passed, ${failed} failed${c.reset}` +
` ${c.dim}(${formatDuration(totalDuration)})${c.reset}`
);
console.log('');
if (failed > 0) {
console.log(`${c.bgRed}${c.white}${c.bold} VALIDATION FAILED ${c.reset}\n`);
} else {
console.log(`${c.bgGreen}${c.white}${c.bold} VALIDATION PASSED ${c.reset}\n`);
}
}
// โโ Main โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async function main() {
const args = parseArgs(process.argv);
const checks = filterChecks(CHECKS, args);
// Print header
console.log('');
console.log(`${c.bgBlue}${c.white}${c.bold} LANTERN VALIDATE ${c.reset}`);
console.log('');
if (args.workspace) {
console.log(` ${c.cyan}Workspace:${c.reset} ${args.workspace}`);
}
if (args.scopes) {
console.log(` ${c.cyan}Scopes:${c.reset} ${args.scopes.join(', ')}`);
}
if (!args.workspace && !args.scopes) {
console.log(` ${c.cyan}Mode:${c.reset} Full validation`);
}
console.log(` ${c.cyan}Checks:${c.reset} ${checks.length} of ${CHECKS.length}`);
if (checks.length === 0) {
console.log(`\n${c.yellow}No checks match the given filters.${c.reset}\n`);
process.exit(0);
}
const results = [];
for (const check of checks) {
banner(check.name);
const result = await runCheck(check);
results.push(result);
if (result.code === 0) {
console.log(`\n ${c.green}โ ${check.name} passed${c.reset} ${c.dim}(${formatDuration(result.duration)})${c.reset}`);
} else {
console.log(`\n ${c.red}โ ${check.name} failed${c.reset} ${c.dim}(${formatDuration(result.duration)})${c.reset}`);
}
}
printSummary(results);
process.exit(results.some(r => r.code !== 0) ? 1 : 0);
}
main();- [ ] Step 2: Make the script executable
Run: chmod +x tooling/scripts/validate.js
- [ ] Step 3: Test the orchestrator with a single scope
Run: node tooling/scripts/validate.js --scope audit Expected: Only the "Security Audit (all workspaces)" section runs, with a banner, streaming output, and summary table.
- [ ] Step 4: Test the orchestrator with a workspace filter
Run: node tooling/scripts/validate.js --workspace apps/web Expected: Only checks with workspace: 'apps/web' or workspace: null run (Formatting, Headers, Workflow Consistency, Web App Tests, Format Check, Linting, Security Audit).
- [ ] Step 5: Test the full orchestrator
Run: node tooling/scripts/validate.js Expected: All 12 checks run sequentially with section banners, pass/fail per section, and a final summary table. Exit code is 0 if all pass, 1 if any fail.
- [ ] Step 6: Commit
git add tooling/scripts/validate.js
git commit -m "feat(validate): add orchestrator with section banners, filtering, and summary table"Task 4: Update root package.json to use the orchestrator โ
Files:
Modify:
package.json(root)[ ] Step 1: Replace the validate script
Change line 118 from:
"validate": "npm run format && npm run validate:headers && npm run test:workflows:validate && npm run test:coverage -w apps/web && npm run format:check -w apps/web && npm run lint -w apps/web && npm run audit && npm run validate -w apps/admin && npm run validate -w services/api/analytics && npm run validate -w services/api/venues && npm run validate -w services/bots/discord && npm run validate -w services/functions/firebase",to:
"validate": "node tooling/scripts/validate.js",- [ ] Step 2: Update the
scripts-infoentry for validate
Change the validate entry (line 191) from:
"validate": "Full pre-commit validation: format + headers + tests + lint + audit + all workspaces",to:
"validate": "Full pre-commit validation with section banners โ supports --workspace and --scope filters",- [ ] Step 3: Verify
npm run validate -- --scope auditworks
Run: npm run validate -- --scope audit Expected: Only the audit section runs with orchestrator banners.
- [ ] Step 4: Verify
npm run validate -- --workspace apps/webworks
Run: npm run validate -- --workspace apps/web Expected: Only web-app-relevant checks + global checks run.
- [ ] Step 5: Commit
git add package.json
git commit -m "refactor(validate): use orchestrator script instead of chained npm commands"Task 5: Update CI workflow to remove redundant Discord bot audit โ
Files:
- Modify:
.github/workflows/ci.yml
The CI lint-and-security job has an extra step for Discord bot audit (line 65-66). Since npm run audit now covers all workspace production deps, this is redundant.
- [ ] Step 1: Remove the Discord bot audit step from CI
Remove lines 65-66:
- name: Check for known vulnerabilities in Discord bot
run: cd services/bots/discord && npm audit --audit-level=moderate- [ ] Step 2: Verify CI still runs the centralized audit
Confirm line 63-64 remains:
- name: Run npm audit (production dependencies only)
run: npm run auditThis now covers Discord bot production deps via the enhanced audit-production.js.
- [ ] Step 3: Commit
git add .github/workflows/ci.yml
git commit -m "ci: remove redundant discord bot audit, now covered by centralized audit"Task 6: Update CLAUDE.md with new validate commands โ
Files:
Modify:
CLAUDE.md[ ] Step 1: Update the Common Commands section
In the ## Common Commands section, update the validation block. Replace the existing validate entry:
# Full Validation (run before commits)
npm run validate # Lint + format + tests + coverage + audit + all workspaceswith:
# Full Validation (run before commits)
npm run validate # Full suite with section banners + summary
npm run validate -- --workspace apps/web # Only web app checks + global (audit)
npm run validate -- --workspace services/api/auth # Only auth API checks + global (audit)
npm run validate -- --scope audit # Security audit only (all workspaces)
npm run validate -- --scope lint,format # Lint + format only
npm run validate -- --scope test # Tests only- [ ] Step 2: Add audit standardization note to Architecture section or Key Conventions
Add to the end of the ## Key Conventions section:
- **Validation orchestrator:** `tooling/scripts/validate.js` runs all checks with visual section banners, timing, and a pass/fail summary table. Supports `--workspace` and `--scope` filters for targeted validation during development. Full `npm run validate` remains the pre-commit gate.
- **Security audit:** `tooling/scripts/audit-production.js` audits production dependencies across ALL workspaces (root + apps + services + packages). Only production deps are checked โ devDependency vulnerabilities are ignored. Individual workspace `validate` scripts do NOT run their own audit; it's centralized.- [ ] Step 3: Commit
git add CLAUDE.md
git commit -m "docs: update CLAUDE.md with validate orchestrator commands and audit standardization"Task 7: End-to-end verification โ
- [ ] Step 1: Run the full validate suite
Run: npm run validate Expected: All 12 checks run with clear section banners, streaming output, pass/fail indicators with timing, and a final summary table. Exit code matches whether all checks passed.
- [ ] Step 2: Run a workspace-scoped validate
Run: npm run validate -- --workspace services/api/auth Expected: Only "Workflow Consistency", "Security Audit (all workspaces)", and no other workspace checks run (auth API has no validate script with tests currently โ if it does, it runs too). Check count should be small (2-3).
- [ ] Step 3: Run a scope-filtered validate
Run: npm run validate -- --scope lint,format Expected: Only "Formatting", "Linting", and "Format Check" run.
- [ ] Step 4: Verify CI workflow is valid YAML
Run: node -e "const yaml = require('yaml'); const fs = require('fs'); yaml.parse(fs.readFileSync('.github/workflows/ci.yml', 'utf8')); console.log('Valid YAML');" 2>/dev/null || python3 -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml')); print('Valid YAML')" Expected: "Valid YAML"
- [ ] Step 5: Final commit if any fixes were needed
If any issues were found and fixed during verification, commit them:
git add -A
git commit -m "fix(validate): address issues found during end-to-end verification"