LLM-fri STEG 1-detektor for kurs-deteksjon (C3.4 av GODKJENT C3-spec). Speiler discover-new-urls' to-stegs-monster: leser Keychain-creds, paginerer modules + learning-paths filtrert til in-domain produkter, differ mot egen diff-state (course-registry.json), skriver kandidatrapport. Skriver KUN report + registry — ALDRI decisions.json (operator-gaten i C3.5 promoterer leads). - scripts/kb-update/detect-courses.mjs (NY): detectCourses(deps) — injiserbar kjerne (readSecret/getToken/paginate/loadTaxonomy/load+saveCourseRegistry/ saveReport/dataDir/now/log) -> hermetisk testbar uten Keychain/nett. Fail-soft (spec 3/6): creds=null -> status:"skipped"; run-feil -> status:"error" (kaster ALDRI); begge exit 0 (advisory, aldri rod pipeline). Tynn CLI med realpath-guard (kjorer kun nar invokert direkte) + --data-dir test-seam. updatedAt.gt-cursor KUN pa incremental (full ISO, gotcha #1); products-filter = comma-join (OR). - lib/detection-schedule.mjs: ny ren selectDetectionSteps(config) gater BADE skillLifecycle + courseDetection (en sannhetskilde). DEFAULT_SCHEDULE_CONFIG +include_course_detection:false (opt-in i opt-in, spec 8d). coerce/serialize utvidet. DETECTION_STEPS +{detect-courses, courseDetection:true}. - run-detection.mjs: bruker selectDetectionSteps (fjerner duplisert filter-linje). Tester (+22): - test-detect-courses-invariant (8): ingen saveDecisions/decisions-io/atomic-write/ backup-import; skriver egen registry+report; binder diffCourses+makeCourseClassifier; LLM-fri (kommentar-strippet kildesjekk). - test-detect-courses (6): fail-soft skipped (alle/delvis creds), error-path (no throw, registry urort), 4.3 report-shape + avledet skill/category, incremental cursor/produkt-param, baseline=ingen leads (spec 8b), hermetisk exit-0-subprosess via PATH-shadow av `security`. - test-detection-schedule (+8): include_course_detection parse/serialize/default; selectDetectionSteps-gating (default av, opt-in pa, uavhengige gates); run-detection bruker selektoren. Gotcha (secrets-hook): clientSecret: '...'-form traff llm-security-regelen (client[_-]?secret|ClientSecret)\s*[=:]\s*['"]…{8,} -> lost med array-av-par (feltnavn star aldri rett for quotet streng). Gate 7 C3.4 mott: invariant + fail-soft(exit 0) + report-shape + DETECTION_STEPS- filter gronn. kb-update 269->291, validate PASSED (239/0), run-detection dry-run dropper detect-courses (opt-in av), eval urort (rorer ikke KB), null regresjon. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
55 lines
2.6 KiB
JavaScript
55 lines
2.6 KiB
JavaScript
#!/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/) ===');
|