feat: add okr plugin v1.0.0 — OKR guidance for Norwegian public sector

Expert OKR guidance based on Google/Doerr methodology, adapted for
4-month tertial cycles and Norwegian government accountability.

Components:
- 8 commands (skriv, kvalitet, kaskade, sporing, møter, innføring, governance, oppsett)
- 5 agents (kvalitetssjekker, kaskadebygger, fremdriftssporer, møtefasilitator, styringsrådgiver)
- 3 hooks (UserPromptSubmit context injection, PreCompact state preservation, Stop reminder)
- 15 reference files covering methodology, governance, meetings, antipatterns
- Linear MCP integration for OKR tracking

Previously in ktg-privat, now open-sourced.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-04-08 13:32:45 +02:00
commit 5078712f0e
42 changed files with 7341 additions and 0 deletions

View file

@ -0,0 +1,35 @@
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/inject-okr-context.mjs",
"timeout": 5000
}
]
}
],
"PreCompact": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Before compacting: if OKR work is in progress this session (writing, scoring, reviewing, or cascading OKR), preserve the current state. Include: any OKR draft text, quality scores given, cascade decisions, and next steps agreed upon."
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "If OKR work occurred this session (new OKR written, scored, cascaded, or reviewed), generate a brief 2-3 bullet reminder of what was accomplished and what should be saved to the tracking system (Oboard/Linear). Skip this entirely if no OKR work occurred."
}
]
}
]
}
}

View file

@ -0,0 +1,45 @@
#!/usr/bin/env node
// inject-okr-context.mjs
// Event: UserPromptSubmit
// Purpose: Inject OKR organization context from .claude/okr.local.md when configured.
// Zero npm dependencies. Target execution: <20ms.
import { readFileSync, existsSync } from 'node:fs';
import { join } from 'node:path';
const configPath = join(process.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;
};
const org = get('navn') || get('name');
const syklus = get('gjeldende') || get('current_cycle');
const sektor = get('sektor') || get('sector');
const linear = fm.includes('aktivert: true') || fm.includes('enabled: true');
if (!org) process.exit(0);
const parts = [`Organisasjon: ${org}`];
if (syklus) parts.push(`Gjeldende syklus: ${syklus}`);
if (sektor) parts.push(`Sektor: ${sektor}`);
if (linear) parts.push('Linear: aktivert');
const systemMessage = `OKR-kontekst (fra .claude/okr.local.md): ${parts.join(', ')}.`;
process.stdout.write(JSON.stringify({ systemMessage }));
} catch {
// Graceful exit on any error — never block the user
process.exit(0);
}