feat(ms-ai-architect): C3.6 — SessionStart-surfacing av kurs-leads (summarizeCourses) → C3 kurs-spor komplett (TDD)
Siste C3-fase. `summarizeCourses(report)` ren one-liner i detection-schedule.mjs («Kurs-signaler: N nye / M endrede kurs i dekkede produkter»; null ved 0/skipped/error/removed-only/malformed — removed er informasjons-signal, aldri surfacet lead, spec §4.2). Speiler summarizeSkillLifecycle. Wiring i session-start-context.mjs: read-only, leser scripts/kb-update/data/course-detection-report.json, foreslår /architect:kb-update, speiler skill-signaler-blokka. Hooken spawner ingenting for kurs-sporet. docs/development.md: ny C3-workflow-seksjon (to-stegs: Claude-fri deteksjon → SessionStart-surfacing → operatør-gate §3c). CLAUDE.md hook-tabell oppdatert. Tester (+6): summarizeCourses — new+updated one-liner, only-new/only-updated, zero→null, removed-only→null, skipped/error→null, missing/partial→null. kb-update 307→313, validate 239/0, kb-eval 100/0, hooks 11/11, discovery 13/13. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e0d2d05dbb
commit
f4dd4d3fb6
5 changed files with 120 additions and 1 deletions
|
|
@ -25,6 +25,7 @@ import {
|
|||
shouldRunDetection,
|
||||
selectDetectionSteps,
|
||||
summarizeSkillLifecycle,
|
||||
summarizeCourses,
|
||||
daysSinceLastPoll,
|
||||
} from '../../scripts/kb-update/lib/detection-schedule.mjs';
|
||||
import { writeScheduleConfig } from '../../scripts/kb-update/write-schedule-config.mjs';
|
||||
|
|
@ -369,6 +370,65 @@ test('summarizeSkillLifecycle — tolerates a missing/partial report shape', ()
|
|||
assert.equal(summarizeSkillLifecycle(null, { threshold: 7.0 }), null);
|
||||
});
|
||||
|
||||
// ===========================================================================
|
||||
// (C2) summarizeCourses — one-liner surfaced at session start (Spor C / C3.6)
|
||||
// ===========================================================================
|
||||
// Mirrors summarizeSkillLifecycle: a pure one-liner over a parsed
|
||||
// course-detection-report.json, surfacing NEW + UPDATED course leads in
|
||||
// covered products. `removed` is an informational rapport-signal (spec §4.2),
|
||||
// NOT a surfaced lead, so it must never make this return non-null on its own.
|
||||
|
||||
const COURSE_REPORT_OK = {
|
||||
generated_at: '2026-06-23T12:00:00Z',
|
||||
status: 'ok',
|
||||
skipped_reason: null,
|
||||
new: [{ uid: 'm1' }, { uid: 'm2' }],
|
||||
updated: [{ uid: 'm3' }],
|
||||
removed: [],
|
||||
counts: { new: 2, updated: 1, removed: 0 },
|
||||
};
|
||||
|
||||
test('summarizeCourses — surfaces new + updated leads in the canonical one-liner', () => {
|
||||
const s = summarizeCourses(COURSE_REPORT_OK);
|
||||
assert.match(s, /Kurs-signaler:/);
|
||||
assert.match(s, /2 nye/);
|
||||
assert.match(s, /1 endrede/);
|
||||
assert.match(s, /kurs i dekkede produkter/);
|
||||
});
|
||||
|
||||
test('summarizeCourses — only-new and only-updated each render alone', () => {
|
||||
const onlyNew = summarizeCourses({ status: 'ok', counts: { new: 3, updated: 0, removed: 0 } });
|
||||
assert.match(onlyNew, /3 nye kurs i dekkede produkter/);
|
||||
assert.doesNotMatch(onlyNew, /endrede/);
|
||||
const onlyUpdated = summarizeCourses({ status: 'ok', counts: { new: 0, updated: 4, removed: 0 } });
|
||||
assert.match(onlyUpdated, /4 endrede kurs i dekkede produkter/);
|
||||
assert.doesNotMatch(onlyUpdated, /nye/);
|
||||
});
|
||||
|
||||
test('summarizeCourses — zero new + zero updated returns null (nothing to surface)', () => {
|
||||
assert.equal(summarizeCourses({ status: 'ok', counts: { new: 0, updated: 0, removed: 0 } }), null);
|
||||
});
|
||||
|
||||
test('summarizeCourses — removed-only is informational, not a surfaced lead (spec §4.2)', () => {
|
||||
assert.equal(
|
||||
summarizeCourses({ status: 'ok', counts: { new: 0, updated: 0, removed: 5 } }),
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
test('summarizeCourses — skipped/error reports surface nothing', () => {
|
||||
const skipped = { status: 'skipped', skipped_reason: 'creds', counts: { new: 0, updated: 0, removed: 0 } };
|
||||
const errored = { status: 'error', skipped_reason: 'network', counts: { new: 0, updated: 0, removed: 0 } };
|
||||
assert.equal(summarizeCourses(skipped), null);
|
||||
assert.equal(summarizeCourses(errored), null);
|
||||
});
|
||||
|
||||
test('summarizeCourses — tolerates a missing/partial report shape', () => {
|
||||
assert.equal(summarizeCourses({}, {}), null);
|
||||
assert.equal(summarizeCourses(null), null);
|
||||
assert.equal(summarizeCourses({ status: 'ok' }), null); // no counts
|
||||
});
|
||||
|
||||
// ===========================================================================
|
||||
// (D) STRUCTURAL ToS GUARANTEE — detection is Claude-free
|
||||
// ===========================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue