#!/usr/bin/env node // run-detection.mjs — Spor C / C1: the Claude-FREE detection entrypoint. // // Runs the pure-node detection pipeline (poll → report → discover → // detect-skill-lifecycle), each a local script that produces a JSON report and // NEVER contacts Anthropic. This is the structural ToS guarantee: this file // only ever spawns `node` on the allow-listed DETECTION_STEPS — it can never // invoke `claude`, and it never touches the apply path (which alone invokes // Claude and stays manual + in-session + gated). // // The SessionStart hook background-spawns this when the opt-in schedule is // enabled + stale (see detection-schedule.mjs). Standalone use is fine too: // node scripts/kb-update/run-detection.mjs # run the pipeline // node scripts/kb-update/run-detection.mjs --dry-run # list steps, run none import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { execFileSync } from 'node:child_process'; import { loadScheduleConfig, selectDetectionSteps } from './lib/detection-schedule.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const PLUGIN_ROOT = join(__dirname, '..', '..'); const dryRun = process.argv.includes('--dry-run'); // Honor the opt-in content choices: each flagged step (skill-lifecycle, // course-detection) runs only when its config flag is on. The gating lives in // the pure selectDetectionSteps so it is unit-tested independently of this // executor. (enabled-gating is the hook's job; invoking this script directly is // itself the intent to run.) const config = loadScheduleConfig(PLUGIN_ROOT); const steps = selectDetectionSteps(config); function run(step) { const fullPath = join(PLUGIN_ROOT, 'scripts', step.dir, step.script); console.log(`\n--- detection: ${step.name} (${step.dir}/${step.script} ${step.args.join(' ')}) ---`); try { // Only ever `node` on a local detection script — never `claude`. execFileSync('node', [fullPath, ...step.args], { stdio: 'inherit', timeout: 10 * 60 * 1000 }); } catch (err) { // A failing step is non-fatal for the others — detection is advisory. console.error(`detection step ${step.name} failed: ${err.message}`); } } if (dryRun) { console.log('DRY RUN — Claude-free detection pipeline would execute:'); steps.forEach((s, i) => console.log(` ${i + 1}. ${s.dir}/${s.script} ${s.args.join(' ')}`)); console.log('\n(All steps are pure node; none invoke Claude. Apply stays manual + in-session.)'); process.exit(0); } console.log('=== Claude-free detection run ==='); for (const step of steps) run(step); console.log('\n=== detection complete (reports refreshed; 0 writes to skills/) ===');