feat(ms-ai-architect): C3.4 — detect-courses.mjs (binder C3.1–C3.3, LLM-fri) + selectDetectionSteps + include_course_detection (TDD) [skip-docs]
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>
This commit is contained in:
parent
b9f51ea7b3
commit
2e69e69326
6 changed files with 640 additions and 6 deletions
|
|
@ -23,6 +23,7 @@ import {
|
|||
serializeScheduleConfig,
|
||||
loadScheduleConfig,
|
||||
shouldRunDetection,
|
||||
selectDetectionSteps,
|
||||
summarizeSkillLifecycle,
|
||||
daysSinceLastPoll,
|
||||
} from '../../scripts/kb-update/lib/detection-schedule.mjs';
|
||||
|
|
@ -42,6 +43,30 @@ test('DEFAULT_SCHEDULE_CONFIG — opt-in: disabled by default', () => {
|
|||
assert.equal(DEFAULT_SCHEDULE_CONFIG.include_skill_lifecycle, true);
|
||||
});
|
||||
|
||||
// --- C3.4: include_course_detection (opt-in inside opt-in; default OFF) ---
|
||||
|
||||
test('DEFAULT_SCHEDULE_CONFIG — include_course_detection defaults to false (§8 d)', () => {
|
||||
assert.equal(DEFAULT_SCHEDULE_CONFIG.include_course_detection, false,
|
||||
'course detection needs Keychain creds almost nobody has => opt-in, default OFF');
|
||||
});
|
||||
|
||||
test('parseScheduleConfig — reads include_course_detection: true', () => {
|
||||
const cfg = parseScheduleConfig('scheduled_detection:\n include_course_detection: true\n');
|
||||
assert.equal(cfg.include_course_detection, true);
|
||||
});
|
||||
|
||||
test('parseScheduleConfig — a non-boolean include_course_detection stays OFF', () => {
|
||||
const cfg = parseScheduleConfig('scheduled_detection:\n include_course_detection: maybe\n');
|
||||
assert.equal(cfg.include_course_detection, false);
|
||||
});
|
||||
|
||||
test('serializeScheduleConfig — emits + round-trips include_course_detection', () => {
|
||||
assert.match(serializeScheduleConfig({}), /\n {2}include_course_detection: false/,
|
||||
'serialized at its OFF default');
|
||||
const cfg = parseScheduleConfig(serializeScheduleConfig({ include_course_detection: true }));
|
||||
assert.equal(cfg.include_course_detection, true);
|
||||
});
|
||||
|
||||
test('parseScheduleConfig — empty/absent text returns the OFF default', () => {
|
||||
assert.deepEqual(parseScheduleConfig(''), DEFAULT_SCHEDULE_CONFIG);
|
||||
assert.deepEqual(parseScheduleConfig('# just some markdown, no frontmatter'), DEFAULT_SCHEDULE_CONFIG);
|
||||
|
|
@ -353,6 +378,7 @@ const KNOWN_DETECTION_SCRIPTS = new Set([
|
|||
'report-changes.mjs',
|
||||
'discover-new-urls.mjs',
|
||||
'detect-skill-lifecycle.mjs',
|
||||
'detect-courses.mjs',
|
||||
'build-registry.mjs',
|
||||
]);
|
||||
|
||||
|
|
@ -365,6 +391,37 @@ test('DETECTION_STEPS — only the known pure-node detection scripts, never clau
|
|||
}
|
||||
// skill-lifecycle detection must be present and flagged so config can exclude it
|
||||
assert.ok(DETECTION_STEPS.some((s) => s.script === 'detect-skill-lifecycle.mjs' && s.skillLifecycle === true));
|
||||
// C3.4: course detection present and flagged so config can exclude it (opt-in)
|
||||
assert.ok(DETECTION_STEPS.some((s) => s.script === 'detect-courses.mjs' && s.courseDetection === true));
|
||||
});
|
||||
|
||||
// --- C3.4: selectDetectionSteps — the pure gate run-detection.mjs applies ---
|
||||
|
||||
test('selectDetectionSteps — drops detect-courses when include_course_detection is OFF (default)', () => {
|
||||
const steps = selectDetectionSteps(DEFAULT_SCHEDULE_CONFIG);
|
||||
assert.ok(!steps.some((s) => s.courseDetection), 'course detection excluded by default');
|
||||
// the always-on steps survive
|
||||
assert.ok(steps.some((s) => s.script === 'poll-sitemaps.mjs'));
|
||||
});
|
||||
|
||||
test('selectDetectionSteps — includes detect-courses when opted in', () => {
|
||||
const steps = selectDetectionSteps({ ...DEFAULT_SCHEDULE_CONFIG, include_course_detection: true });
|
||||
assert.ok(steps.some((s) => s.script === 'detect-courses.mjs' && s.courseDetection === true));
|
||||
});
|
||||
|
||||
test('selectDetectionSteps — the two opt-in gates are independent', () => {
|
||||
const onlyCourses = selectDetectionSteps({
|
||||
...DEFAULT_SCHEDULE_CONFIG,
|
||||
include_skill_lifecycle: false,
|
||||
include_course_detection: true,
|
||||
});
|
||||
assert.ok(!onlyCourses.some((s) => s.skillLifecycle), 'skill-lifecycle gated OFF');
|
||||
assert.ok(onlyCourses.some((s) => s.courseDetection), 'course detection gated ON');
|
||||
});
|
||||
|
||||
test('run-detection.mjs source — gates detection via the shared selector (no duplicated filter)', () => {
|
||||
const src = readFileSync(join(PLUGIN_ROOT, 'scripts', 'kb-update', 'run-detection.mjs'), 'utf8');
|
||||
assert.match(src, /selectDetectionSteps\(/, 'run-detection uses the pure step selector');
|
||||
});
|
||||
|
||||
test('run-detection.mjs source — spawns node, NEVER claude (the ToS boundary made structural)', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue