Skip to content

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 โ€‹

ActionFileResponsibility
Createtooling/scripts/validate.jsMain orchestrator โ€” runs checks sequentially, wraps output in section banners, supports --workspace and --scope filters, prints summary table
Modifytooling/scripts/audit-production.jsEnhance to scan ALL workspace package.json files for production deps, not just root
Modifypackage.json (root)Update validate script to call node tooling/scripts/validate.js, add validate:workspace and validate:scope convenience aliases
Modifyapps/admin/package.jsonRemove npm audit from validate script (now centralized)
Modifyservices/bots/discord/package.jsonRemove npm audit from validate script (now centralized)
Modifyservices/functions/firebase/package.jsonRemove npm audit from validate script (now centralized)
Modify.github/workflows/ci.ymlReplace ad-hoc audit commands with npm run validate -- --scope audit or keep using npm run audit (already centralized)
ModifyCLAUDE.mdUpdate 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.js to gather workspace production deps

Replace the section that reads only root package.json (lines 24-34) with logic that:

  1. Reads root package.json to get the workspaces array
  2. Iterates each workspace, reads its package.json
  3. Collects ALL production dependency names into a single prodDeps set
  4. Logs how many deps came from root vs workspaces
javascript
// 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
bash
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.json validate script

Change line 11 from:

json
"validate": "npm run build && npm audit --audit-level=moderate && echo 'Admin validation: OK'"

to:

json
"validate": "npm run build && echo 'Admin validation: OK'"
  • [ ] Step 2: Update services/bots/discord/package.json validate script

Change line 11 from:

json
"validate": "npm audit --audit-level=moderate && echo 'Discord bot validation: OK'"

to:

json
"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.json validate script

Change line 18 from:

json
"validate": "npm audit --audit-level=moderate && echo 'Firebase functions validation: OK'"

to:

json
"validate": "echo 'Firebase functions validation: OK'"

Keep the standalone "audit" script for manual use.

  • [ ] Step 4: Verify each workspace validate still works

Run:

bash
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 -1

Expected: Each prints its "validation: OK" message without running audit.

  • [ ] Step 5: Commit
bash
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.js with argument parsing

The script should:

  1. Parse --workspace <name> and --scope <comma-separated> from process.argv
  2. Define the full check registry (name, scope, workspace affinity, command)
  3. Filter checks based on flags
  4. Run checks sequentially, wrapping each in section banners
  5. Print a summary table at the end
javascript
#!/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
bash
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:

json
"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:

json
"validate": "node tooling/scripts/validate.js",
  • [ ] Step 2: Update the scripts-info entry for validate

Change the validate entry (line 191) from:

json
"validate": "Full pre-commit validation: format + headers + tests + lint + audit + all workspaces",

to:

json
"validate": "Full pre-commit validation with section banners โ€” supports --workspace and --scope filters",
  • [ ] Step 3: Verify npm run validate -- --scope audit works

Run: npm run validate -- --scope audit Expected: Only the audit section runs with orchestrator banners.

  • [ ] Step 4: Verify npm run validate -- --workspace apps/web works

Run: npm run validate -- --workspace apps/web Expected: Only web-app-relevant checks + global checks run.

  • [ ] Step 5: Commit
bash
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:

yaml
      - 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:

yaml
      - name: Run npm audit (production dependencies only)
        run: npm run audit

This now covers Discord bot production deps via the enhanced audit-production.js.

  • [ ] Step 3: Commit
bash
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:

bash
# Full Validation (run before commits)
npm run validate                  # Lint + format + tests + coverage + audit + all workspaces

with:

bash
# 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:

markdown
- **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
bash
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:

bash
git add -A
git commit -m "fix(validate): address issues found during end-to-end verification"

Built with VitePress