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:
Kjell Tore Guttormsen 2026-06-23 15:00:40 +02:00
commit f4dd4d3fb6
5 changed files with 120 additions and 1 deletions

View file

@ -233,3 +233,26 @@ export function summarizeSkillLifecycle(report, opts = {}) {
if (stale > 0) parts.push(`${stale} stale`);
return parts.length ? `Skill-signaler: ${parts.join(', ')}` : null;
}
/**
* Build a compact one-liner summarizing course-detection leads (Spor C / C3.6)
* for the SessionStart hook. Pure; tolerant of partial/missing reports. Mirrors
* summarizeSkillLifecycle. Surfaces only NEW + UPDATED leads in covered
* products; `removed` is an informational rapport-signal (spec §4.2), never a
* surfaced lead. Returns null when there is nothing worth surfacing (0 new + 0
* updated, a skipped/error report, or a malformed shape).
* @param {object|null} report parsed course-detection-report.json
* @returns {string|null}
*/
export function summarizeCourses(report) {
if (!report || typeof report !== 'object') return null;
if (report.status && report.status !== 'ok') return null;
const counts = report.counts ?? {};
const fresh = Number(counts.new) || 0;
const changed = Number(counts.updated) || 0;
const parts = [];
if (fresh > 0) parts.push(`${fresh} nye`);
if (changed > 0) parts.push(`${changed} endrede`);
return parts.length ? `Kurs-signaler: ${parts.join(' / ')} kurs i dekkede produkter` : null;
}