ms-ai-architect/hooks/scripts/stop-assessment-reminder.mjs
Kjell Tore Guttormsen a8ae1fd276 feat(ms-ai-architect): RX-REG — én maskinlesbar AI Act-frist-kilde + synk-test (B2-B5, R3-5, M10) [skip-docs]
Rotårsak-mekanisme: scripts/kb-update/data/ai-act-deadlines.json er nå eneste
kilde for AI Act-frister; CLAUDE.md-tabellen, assessor-malen og begge hooks
synk-testes mot den (tests/kb-update/test-ai-act-deadlines-sync.test.mjs, 9
tester, TDD rød→grønn). Suite 815/815.

- B2: «Kommisjonen kan fremskynde»/«ytre grense» fjernet (trigger droppet i
  endelig Omnibus-tekst)
- B3: status oppdatert i alle 3 lag — formelt vedtatt (EP 2026-06-16, Rådet
  2026-06-29), trer i kraft ved OJ-publisering (ikke publisert per 2026-07-15,
  verifisert mot EUR-Lex 32024R1689)
- B4: Art. 50-raden «Gjeldende» → «Fra 2026-08-02»
- B5: stop-hooken feilmerket 2026-08-02 som «GPAI-frist» — begge hooks leser
  nå kilden, 0 hardkodede frist-literals
- R3-5: 2026-12-02-raden (Art. 50(2)) inn i assessor-malen
- M10: Nkom navngitt som koordinerende markedstilsynsmyndighet + nasjonalt
  kontaktpunkt (kilde: nkom.no/ki/regulering/hvem-handhever)
- pending_oj-markører i kilden for RX-REG² (nudifiserings-forbud, Art. 111)
- .gitignore: whitelist for den kuraterte frist-kilden
2026-07-15 06:13:36 +02:00

82 lines
2.5 KiB
JavaScript

#!/usr/bin/env node
// stop-assessment-reminder.mjs
// Reminds about uncommitted assessments and suggests next steps on session end.
// Output: JSON { systemMessage } to stdout. Always exits 0 (advisory, never blocking).
import { readdirSync, statSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { loadAiActDeadlines } from '../../scripts/kb-update/lib/ai-act-deadlines.mjs';
const cwd = process.cwd();
const workDir = join(cwd, '.work');
const TWELVE_HOURS_MS = 12 * 60 * 60 * 1000;
const now = Date.now();
// No .work/ directory — nothing to remind about
if (!existsSync(workDir)) {
console.log('{}');
process.exit(0);
}
// Find recent state files in .work/
const recentSessions = [];
try {
const entries = readdirSync(workDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const sessionDir = join(workDir, entry.name);
try {
const files = readdirSync(sessionDir);
for (const file of files) {
const filePath = join(sessionDir, file);
try {
const stat = statSync(filePath);
if (now - stat.mtimeMs < TWELVE_HOURS_MS) {
recentSessions.push(entry.name);
break;
}
} catch {
// Skip
}
}
} catch {
// Skip
}
}
} catch {
console.log('{}');
process.exit(0);
}
if (recentSessions.length === 0) {
console.log('{}');
process.exit(0);
}
// Build reminder
const suggestions = [
'/architect:adr — generer ADR fra vurderinger',
'/architect:export — eksporter til PDF',
'/architect:summary — lag beslutningsnotat',
];
// Add AI Act suggestion if the nearest deadline (from the shared source) is within 180 days
const DAY_MS = 24 * 60 * 60 * 1000;
const aiActSource = loadAiActDeadlines();
let nearestDeadline = null;
for (const dl of aiActSource ? aiActSource.deadlines : []) {
const daysLeft = Math.ceil((new Date(dl.date).getTime() - now) / DAY_MS);
if (daysLeft > 0 && daysLeft <= 180 && (!nearestDeadline || daysLeft < nearestDeadline.daysLeft)) {
nearestDeadline = { ...dl, daysLeft };
}
}
if (nearestDeadline) {
suggestions.push(`/architect:classify — EU AI Act-klassifisering (${nearestDeadline.daysLeft}d til ${nearestDeadline.label})`);
}
const sessionList = recentSessions.join(', ');
const message = `Architect: ${recentSessions.length} aktiv(e) vurdering(er) i .work/ (${sessionList}). Foreslåtte neste steg: ${suggestions.join(' | ')}`;
console.log(JSON.stringify({ systemMessage: message }));
process.exit(0);