#!/usr/bin/env node // inject-okr-context.mjs // Event: UserPromptSubmit // Purpose: Inject OKR organization context from .claude/okr.local.md and .claude/okr/ tree. // Zero npm dependencies. Target execution: <50ms. import { readFileSync, existsSync, readdirSync } from 'node:fs'; import { join } from 'node:path'; const cwd = process.cwd(); const configPath = join(cwd, '.claude', 'okr.local.md'); if (!existsSync(configPath)) { process.exit(0); } try { const content = readFileSync(configPath, 'utf8'); const match = content.match(/^---\n([\s\S]*?)\n---/); if (!match) process.exit(0); const fm = match[1]; const get = (key) => { const m = fm.match(new RegExp(`${key}:\\s*["']?([^"'\\n]+)["']?`)); return m ? m[1].trim() : null; }; // Core fields (backwards-compatible with old 4-field format) const org = get('navn') || get('name'); const syklus = get('gjeldende') || get('id') || get('current_cycle'); const sektor = get('sektor') || get('sector') || get('domene'); const linear = fm.includes('aktivert: true') || fm.includes('enabled: true'); // New v1.1 fields (silently skipped if absent) const modenhet = get('modenhetsnivaa'); const fase = get('fase'); const frikoblet = get('okr_frikoblet_fra_loenn'); const trygghet = get('psykologisk_trygghet'); const kortform = get('kortform'); if (!org) process.exit(0); // Build message parts const parts = [`Organisasjon: ${org}${kortform ? ` (${kortform})` : ''}`]; if (syklus) parts.push(`Syklus: ${syklus}${fase ? ` [${fase}]` : ''}`); if (sektor) parts.push(`Sektor: ${sektor}`); if (modenhet) parts.push(`Modenhet: ${modenhet}`); if (frikoblet !== null) parts.push(`OKR frikoblet fra lonn: ${frikoblet}`); if (trygghet) parts.push(`Psykologisk trygghet: ${trygghet}`); if (linear) parts.push('Linear: aktivert'); // Scan .claude/okr/ directory tree (cap at 50 files) const okrDir = join(cwd, '.claude', 'okr'); const dirParts = []; let totalFiles = 0; if (existsSync(okrDir)) { try { const topEntries = readdirSync(okrDir, { withFileTypes: true }); for (const entry of topEntries) { if (!entry.isDirectory()) continue; if (totalFiles >= 50) break; try { const subEntries = readdirSync(join(okrDir, entry.name), { withFileTypes: true }); const mdFiles = []; const subDirs = []; for (const sub of subEntries) { if (totalFiles >= 50) break; if (sub.isFile() && sub.name.endsWith('.md')) { mdFiles.push(sub.name); totalFiles++; } else if (sub.isDirectory()) { subDirs.push(sub.name); } } // Enumerate nested subdirectories (e.g. syklus/T1-2026/) for (const sd of subDirs) { if (totalFiles >= 50) break; try { const nested = readdirSync(join(okrDir, entry.name, sd), { withFileTypes: true }); const nestedMd = []; for (const n of nested) { if (totalFiles >= 50) break; if (n.isFile() && n.name.endsWith('.md')) { nestedMd.push(n.name); totalFiles++; } } if (nestedMd.length > 0) { dirParts.push(`${entry.name}/${sd}/ (${nestedMd.length} fil${nestedMd.length > 1 ? 'er' : ''}: ${nestedMd.join(', ')})`); } } catch { /* skip unreadable nested dirs */ } } if (mdFiles.length > 0) { dirParts.push(`${entry.name}/ (${mdFiles.length} fil${mdFiles.length > 1 ? 'er' : ''}: ${mdFiles.join(', ')})`); } } catch { /* skip unreadable dirs */ } } } catch { /* .claude/okr/ scan failed — continue without */ } } // Scan historikk/ for archived cycle count const histDir = join(okrDir, 'historikk'); const archivedCycles = []; if (existsSync(histDir)) { try { const histEntries = readdirSync(histDir, { withFileTypes: true }); for (const entry of histEntries) { if (entry.isDirectory()) { archivedCycles.push(entry.name); } } } catch { /* skip */ } } // List active cycle files const cycleId = syklus; const cycleParts = []; if (cycleId) { const cyclePath = join(okrDir, 'syklus', cycleId); if (existsSync(cyclePath)) { try { const cycleEntries = readdirSync(cyclePath, { withFileTypes: true }); for (const e of cycleEntries) { if (e.isFile() && e.name.endsWith('.md')) { cycleParts.push(e.name); } } } catch { /* skip */ } } } // Build rich systemMessage let msg = `OKR-kontekst (fra .claude/okr.local.md): ${parts.join(', ')}.`; if (dirParts.length > 0) { msg += `\nTilgjengelige kontekstfiler: ${dirParts.join('; ')}.`; } if (cycleParts.length > 0) { msg += `\nAktive OKR-filer i syklus ${cycleId}: ${cycleParts.join(', ')}.`; } if (archivedCycles.length > 0) { msg += `\nArkiverte sykluser (${archivedCycles.length}): ${archivedCycles.sort().join(', ')}.`; msg += ' Bruk /okr:analyse for trendanalyse.'; } if (dirParts.length > 0 || cycleParts.length > 0) { msg += '\nBruk disse filene automatisk nar relevant — ikke be brukeren om a lime inn innhold som allerede finnes.'; } process.stdout.write(JSON.stringify({ systemMessage: msg })); } catch { // Graceful exit on any error — never block the user process.exit(0); }