ktg-plugin-marketplace/plugins/ms-ai-architect/hooks/scripts/session-start-context.mjs
Kjell Tore Guttormsen a7a334c8d1 feat(ms-ai-architect): v1.12.0 manuell KB-refresh — fjern launchd/cron-arkitektur
ToS-vurdering konkluderte med at autonom cron-kjøring er unødvendig kompleks
for en solo-fork-and-own-plugin. Apply-fasen krever LLM-resonnering uansett,
så manuell trigger fra en aktiv Claude Code-sesjon er enklere og holder
pluginen klart innenfor Anthropic Consumer Terms paragraf 3 (automated access
only via API key or where explicitly permitted — Claude Code CLI er
eksemptert som offisielt verktøy).

Lagt til:
- commands/kb-update.md — ny /architect:kb-update slash-kommando som driver
  poll, endringsrapport, microsoft_docs_fetch-update og commit fra sesjonen.
  Argumenter: --skip-discover, --priorities, --dry-run, --single-commit
- Catalog-entry i playground HTML for kb-update (categori: tool, 4 input-felt)

Slettet (Wave 3-5 reversert, ~1500 linjer + 7 testmoduler):
- scripts/install-kb-cron.mjs (cross-OS scheduler-installer)
- scripts/kb-update/weekly-kb-cron.mjs (cron-orkestrator med pre-flight, lock,
  backup, claude -p subprocess, post-run verify, rollback)
- scripts/kb-update/templates/ (4 scheduler-templates: launchd plist, systemd
  service+timer, Windows ps1 + README)
- scripts/kb-update/lib/auth-mode.mjs (cron-spesifikk auth validation)
- scripts/kb-update/lib/lock-file.mjs (PID+mtime stale-detection)
- scripts/kb-update/lib/cost-estimat.mjs (pre-flight budget-cap)
- 7 testmoduler under tests/kb-update/ for slettet kode
- tests/test-kb-update.sh (Bash-3.2-shim, erstattet av direkte node --test)

Beholdt (utility-laget fortsatt brukbart):
- run-weekly-update.mjs, report-changes.mjs, build-registry.mjs,
  discover-new-urls.mjs (KB change-detection-pipelinen)
- lib/atomic-write, lib/backup, lib/cross-platform-paths, lib/log-rotate
- 4 testmoduler (42/42 tester PASS)

Endret:
- hooks/scripts/session-start-context.mjs: fjern kb-update-status.json-overvaaking
- tests/run-e2e.sh --kb-update kaller node --test direkte i stedet for shim
- README.md, CLAUDE.md: KB-vedlikehold-seksjon rewriter for manuell modell
- plugin.json: 1.11.0 -> 1.12.0
- Rot README + CLAUDE.md: ms-ai-architect-versjon bumpet

Schedulering er bevisst utenfor scope og overlatt til brukeren — eventuelle
forks som vil ha periodisk varsling kan sette opp egen cron / launchd /
GitHub Actions som kjører rapport-fasen og varsler om aa kjore
/architect:kb-update i CC-sesjon.

Verifisering:
- bash tests/validate-plugin.sh: 219 PASS, 0 FAIL
- bash tests/run-e2e.sh --kb-update: 42/42 inner + suite PASS
- bash tests/run-e2e.sh --playground: 271/271 PASS (statisk + parsers)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 12:03:45 +02:00

173 lines
5.1 KiB
JavaScript

#!/usr/bin/env node
// session-start-context.mjs
// Shows active utredning sessions and KB staleness on session start.
// Output: plain text to stdout (advisory, never blocking).
import { readdirSync, readFileSync, existsSync } from 'node:fs';
import { join, relative } from 'node:path';
import { spawn } from 'node:child_process';
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || join(process.cwd());
const cwd = process.cwd();
const lines = [];
// --- 1. Check for active utredning sessions (.work/ directories) ---
const workDir = join(cwd, '.work');
let activeUtredninger = 0;
if (existsSync(workDir)) {
try {
const entries = readdirSync(workDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
activeUtredninger++;
}
}
} catch {
// Ignore read errors
}
}
// Also check docs/**/utredning.md
const docsDir = join(cwd, 'docs');
let utredningFiles = 0;
if (existsSync(docsDir)) {
try {
utredningFiles = countFiles(docsDir, 'utredning.md');
} catch {
// Ignore
}
}
// --- 2. Check KB staleness (from sitemap-based change report) ---
const now = Date.now();
const DAY_MS = 24 * 60 * 60 * 1000;
const staleLevels = { critical: 0, high: 0, medium: 0 };
let lastPollDaysAgo = Infinity;
const changeReportPath = join(pluginRoot, 'scripts', 'kb-update', 'data', 'change-report.json');
if (existsSync(changeReportPath)) {
try {
const report = JSON.parse(readFileSync(changeReportPath, 'utf8'));
staleLevels.critical = report.by_priority?.critical || 0;
staleLevels.high = report.by_priority?.high || 0;
staleLevels.medium = report.by_priority?.medium || 0;
if (report.last_poll) {
lastPollDaysAgo = (now - new Date(report.last_poll).getTime()) / DAY_MS;
}
} catch {
// Ignore — fall back to showing no data
}
}
// Trigger background poll if >7 days since last check
if (lastPollDaysAgo > 7) {
const updateScript = join(pluginRoot, 'scripts', 'kb-update', 'run-weekly-update.mjs');
if (existsSync(updateScript)) {
try {
spawn('node', [updateScript], { detached: true, stdio: 'ignore' }).unref();
} catch {
// Non-critical — silent fail
}
}
}
// --- 3. Check EU AI Act deadlines ---
const AI_ACT_DEADLINES = [
{ date: new Date('2025-02-02'), label: 'Forbudte AI-praksiser (Art. 5)' },
{ date: new Date('2025-08-02'), label: 'Governance + sanksjoner (Art. 99)' },
{ date: new Date('2026-08-02'), label: 'GPAI-krav + høyrisiko i Annex III' },
{ date: new Date('2027-08-02'), label: 'Alle høyrisiko-krav (full compliance)' },
];
let nearestDeadline = null;
for (const dl of AI_ACT_DEADLINES) {
const daysLeft = Math.ceil((dl.date.getTime() - now) / DAY_MS);
if (daysLeft > 0 && daysLeft <= 180) {
if (!nearestDeadline || daysLeft < nearestDeadline.daysLeft) {
nearestDeadline = { ...dl, daysLeft };
}
}
}
// --- 4. Check onboarding status ---
const orgDir = join(pluginRoot, 'org');
const ORG_FILES = [
'organization-profile.md',
'technology-stack.md',
'security-compliance.md',
'architecture-decisions.md',
'business-references.md',
];
let orgComplete = 0;
const orgExists = existsSync(orgDir);
if (orgExists) {
for (const f of ORG_FILES) {
if (existsSync(join(orgDir, f))) orgComplete++;
}
}
// --- 4. Build output ---
const parts = [];
if (activeUtredninger > 0) {
parts.push(`${activeUtredninger} aktiv(e) utredning(er) i .work/`);
}
if (utredningFiles > 0) {
parts.push(`${utredningFiles} utredningsdokument(er) i docs/`);
}
if (!orgExists || orgComplete === 0) {
parts.push('Ingen virksomhetstilpasning. Kjør /architect:onboard (~5 min)');
} else if (orgComplete < ORG_FILES.length) {
parts.push(`Onboarding ${orgComplete}/${ORG_FILES.length}. Kjør /architect:onboard for å fullføre`);
}
const staleEntries = [];
if (staleLevels.critical > 0) staleEntries.push(`${staleLevels.critical} critical`);
if (staleLevels.high > 0) staleEntries.push(`${staleLevels.high} high`);
if (staleLevels.medium > 0) staleEntries.push(`${staleLevels.medium} medium`);
if (staleEntries.length > 0) {
const pollAge = lastPollDaysAgo < Infinity ? ` (pollet ${Math.floor(lastPollDaysAgo)}d siden)` : '';
parts.push(`KB: ${staleEntries.join(', ')} needs update${pollAge}`);
} else if (lastPollDaysAgo > 7) {
parts.push('KB: poll overdue');
}
if (nearestDeadline) {
parts.push(`EU AI Act: ${nearestDeadline.daysLeft} dager til ${nearestDeadline.label}. Kjør /architect:classify`);
}
if (parts.length > 0) {
lines.push(`Architect: ${parts.join('. ')}. /architect:help`);
} else {
lines.push('Architect: Ingen aktive sesjoner. KB oppdatert. /architect:help');
}
if (lines.length > 0) {
process.stdout.write(lines.join('\n') + '\n');
}
// --- Helpers ---
function countFiles(dir, filename) {
let count = 0;
try {
const entries = readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
count += countFiles(fullPath, filename);
} else if (entry.name === filename) {
count++;
}
}
} catch {
// Ignore permission errors
}
return count;
}