diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index a442d28..53e2f25 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -21,9 +21,9 @@ "description": "Multi-agent workflow for analyzing, reporting, and optimizing Claude Code configuration across your entire machine" }, { - "name": "ultraplan-local", - "source": "./plugins/ultraplan-local", - "description": "Four-command context-engineering pipeline (brief → research → plan → execute) with specialized agent swarms, external research triangulation, adversarial review, session decomposition, and headless execution" + "name": "voyage", + "source": "./plugins/voyage", + "description": "Voyage — brief, research, plan, execute, review, continue. Contract-driven Claude Code pipeline with specialized agent swarms, external research triangulation, adversarial review, post-hoc independent review with Handover 6 feedback loop, multi-session resumption, session decomposition, and headless execution. /trekbrief, /trekplan, and /trekreview each end by building a self-contained operator-annotation HTML (scripts/annotate.mjs, modelled on claude-code-100x): pencil-toggle annotation mode, select text or click any element, pick intent (Fiks/Endre/Spørsmål), comment, Copy Prompt, paste back, Claude revises the .md." }, { "name": "linkedin-thought-leadership", @@ -49,6 +49,16 @@ "name": "okr", "source": "./plugins/okr", "description": "Expert OKR guidance for Norwegian public sector. Write, review, cascade, track and govern OKR based on Google/Doerr methodology adapted for 4-month tertial cycles." + }, + { + "name": "human-friendly-style", + "source": "./plugins/human-friendly-style", + "description": "Shared Claude Code output style for the ktg-plugin-marketplace. Plain-language tone — explains what and why, hides paths/JSON/stack traces by default, matches the user's language." + }, + { + "name": "claude-design", + "source": "./plugins/claude-design", + "description": "End-to-end facilitator for prompting Claude Design (claude.ai/design) — idea to copy-paste-ready prompt with iteration coaching, citing Anthropic primary sources." } ] } diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..b6a2a51 --- /dev/null +++ b/.mailmap @@ -0,0 +1,4 @@ +# Konsoliderer Git-identiteter for statistikk og shortlog. +# Se: https://git-scm.com/docs/gitmailmap + +Kjell Tore Guttormsen diff --git a/CLAUDE.md b/CLAUDE.md index bccb956..af11fc2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,13 +10,13 @@ plugins/ config-audit/ v3.1.0 — Configuration intelligence (health, opportunities, auto-fix, whats-active) graceful-handoff/ v2.1.0 — Auto-trigger handoff via Stop hook (skill + JSON pipeline + 4-step model-aware context resolution) linkedin-thought-leadership/ v1.2.0 — LinkedIn content pipeline + analytics - llm-security/ v6.0.0 — Security scanning, auditing, threat modeling - ms-ai-architect/ v1.8.0 — Microsoft AI architecture (Cosmo Skyberg persona) + llm-security/ v7.7.2 — Security scanning, auditing, threat modeling. HTML report output for all 18 skill commands (render-report CLI + canonical ESM module mirrored bit-identical into the playground). v7.7.2 translated the remaining Norwegian surface text in the playground UI, the canonical renderer, the agent prompts, and the README/CLAUDE.md state sections to English. v7.7.1 stripped the playground to the catalog as the only routable surface. + ms-ai-architect/ v1.15.0 — Microsoft AI architecture (Cosmo Skyberg persona) + manual KB-refresh slash command + v3 project-view (sidebar med 17 artifacts + main + import-modal overlay, v2-surface fjernet i v1.15.0) okr/ v1.0.0 — OKR guidance for Norwegian public sector - ultraplan-local/ v3.4.0 — Brief, research, plan, execute, review, continue (six-command universal pipeline + multi-session resumption + --gates autonomy chain) + voyage/ v5.0.3 — Brief, research, plan, execute, review, continue. Contract-driven Claude Code pipeline (six-command universal pipeline + multi-session resumption + --gates autonomy chain). /trekbrief, /trekplan, and /trekreview each end by running scripts/annotate.mjs against the just-written .md and printing the file:// link to a self-contained operator-annotation HTML modelled on claude-code-100x/build-site.js: pencil-toggle annotation mode, select text or click any element, choose intent (Fiks/Endre/Spørsmål), comment, sidebar groups by section with delete + Copy Prompt, localStorage persistence per artifact path. v5.0.0 removed the v4.2/v4.3 bespoke playground + /trekrevise + Handover 8; v5.0.1 pointed at /playground document-critique (wrong direction); v5.0.2 was operator-led but too thin; v5.0.3 matches the reference the operator pointed at from day one. shared/ - playground-design-system/ v0.1 — Aksel/Digdir-aligned CSS design system + JSON schemas + self-hosted Inter/JetBrains Mono/Source Serif 4 fonts (Tier 1+2+3 wave 1+wave 2 = 20 Tier 3 components total). Consumed by ms-ai-architect, okr, llm-security, ultraplan-local, config-audit + playground-design-system/ v0.6.0 — Aksel/Digdir-aligned CSS design system + JSON schemas + self-hosted Inter/JetBrains Mono/Source Serif 4 fonts. Tier 1 base + Tier 2 + Tier 3 wave 1+2 (20 components) + Tier 4 project-view-arketype (v0.6.0 — sidebar + main + import-modal overlay). Consumed by ms-ai-architect, okr, llm-security, voyage, config-audit. playground-examples/ — Reference scenarios (ROS-Lier, OKR-Bærum, security-Direktorat) + showcase landing + 12 isolated Tier 3 wave 2 component demos under components/ ``` @@ -35,6 +35,7 @@ Hvert plugin er selvstendig med egen CLAUDE.md, README, hooks, agents og command 1. Plugin `README.md` — detaljert dokumentasjon av endringen 2. Plugin `CLAUDE.md` — arkitektur/oversikt 3. Rot-`README.md` — marketplace-landingssiden (`git.fromaitochitta.com/open/ktg-plugin-marketplace`) +- **Playground-oppdatering:** Ved endring av plugin playground HTML eller delt design-system, følg prosedyren i `shared/PLAYGROUND-MAINTENANCE.md` (4 spor: HTML-endring, DS-endring, screenshots, release). ## Sesjonsfiler (lokale, gitignored) @@ -52,3 +53,20 @@ Disse trackes IKKE i git. Oppdater ved sesjonsslutt. 3. Les REMEMBER.md og TODO.md for sesjonsstatus 4. Jobb innenfor scope 5. Oppdater REMEMBER.md ved avslutning + +## Communication patterns + +### Linking to local files + +When pointing to local files in responses, always use markdown link syntax with a descriptive name: + +- Use `[Human-friendly name](file:///absolute/path)` — never bare `file:///...` URLs or autolinks ``. +- Always use absolute paths. Never `~/` or relative paths. +- For multiple files, render as a bullet list of named markdown links. + +Why: bare `file://` URLs only render the first as clickable across multiple lines. Named markdown links make each entry independently clickable and look cleaner. + +Example: + +- [Brief](file:///Users/ktg/.../brief.html) +- [Research summary](file:///Users/ktg/.../research/summary.md) diff --git a/README.md b/README.md index e490866..0f4df4e 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Then open Claude Code and type `/plugin` to browse and install plugins from the ## Plugins -### [LLM Security](plugins/llm-security/) `v7.3.1` +### [LLM Security](plugins/llm-security/) `v7.7.2` Security scanning, auditing, and threat modeling for agentic AI projects. @@ -36,6 +36,13 @@ Built on OWASP LLM Top 10 (2025), OWASP Agentic AI Top 10, and the AI Agent Trap - **Deterministic scanning** — 23 Node.js scanners (10 orchestrated + 13 standalone) for byte-level analysis: Shannon entropy, Unicode codepoints, typosquatting detection, taint flow, DNS resolution, git forensics, AI-BOM, attack simulation, IDE extension prescan (VS Code + JetBrains — URL fetch from Marketplace / OpenVSX / direct VSIX / JetBrains Marketplace, hardened ZIP extractor for zip-slip / symlinks / bombs, plus OS sandbox via `sandbox-exec` / `bwrap` so the kernel enforces FS confinement), MCP cumulative-drift baseline reset (E14 — sticky baseline catches slow-burn rug-pulls). Bash-normalize T1-T6 for obfuscation-resistant denylists - **Advisory analysis** — 20 commands that scan, audit, and model threats with structured reports, letter grades, and actionable remediation - **Enterprise governance** — Compliance mapping (EU AI Act, NIST AI RMF, ISO 42001), SARIF 2.1.0 output, structured audit trail, policy-as-code, standalone CLI +- **v7.7.2 language consistency pass (2026-05-19)** — Norwegian had crept into surface text across v7.5-v7.7. Per the `~/.claude/CLAUDE.md` convention (English for code and documentation, Norwegian for dialog only), this release translates the HTML Report-step in all 18 skill commands, the canonical CLI renderer `scripts/lib/report-renderers.mjs`, the playground UI strings, the skill-scanner and mcp-scanner agent prompts, the marketplace + plugin README/CLAUDE.md state sections, and six table cells in `docs/scanner-reference.md`. Demo-state fixture content for the `dft-komplett-demo` project (intentional Norwegian persona) and regex alternations that match Norwegian-language report markdown (`/^high\|^høy/`, `/resolution\|løsning/`) were preserved. No scanner, hook, or behavior changes — purely surface text +- **v7.7.1 playground UX strip (2026-05-18)** — Operator feedback immediately after v7.7.0: the catalog became the only routable surface in the playground (the onboarding/home/project render functions remain in source but are not routable). Topbar simplified to a `Catalog` button + state/theme actions. Breadcrumb org-name replaced with a neutral `llm-security`. The onboarding concept (per-command context injection) is documented as a v7.8.0 candidate in ROADMAP. No scanner or hook behavior changes +- **v7.7.0 HTML report for all 18 skill commands (2026-05-18)** — Every `/security ` that produces a report now prints a clickable `file://` link to a self-contained HTML version. Delivered across five sessions: (1) playground catalog list-view + builder-pane with a copy button; (2) playground project-surface cleanup (stub-screen + topbar split); (3) the 18 inline parsers + renderers moved to a canonical ESM module `scripts/lib/report-renderers.mjs` (the playground keeps a bit-identical inline copy since ESM `import` does not work from `file://`); (4) new zero-dep CLI `scripts/render-report.mjs` — stdin/file/stdout mode, kebab→camel commandId routing, inlines 6 DS stylesheets, ~140 KB self-contained HTML with system-font fallback, absolute `file://` paths for Ghostty cmd-click; (5) all 18 skills wired (4 in session 4 + 14 in session 5). No scanner or hook behavior changes — purely additive +- **v7.6.1 playground visual patch (2026-05-06)** — Six bugs caught by the maintainer during manual browser verification after the v7.6.0 release. All were mismatches between DS classes and how playground renderers used them (or missing DS implementations the renderers assumed existed): `renderFindingsBlock` used the `.findings` outer class (the DS 2-column list+detail grid) → replaced with `
` + the correct `findings__list` pattern; `.report-table` was missing entirely from the DS but used in 7+ renderers → local CSS implementation; `renderPreDeploy` traffic-lights used the fixed 28×28 px `.sm-card__grade` for "PASS"/"PASS-WITH-NOTES"/"FAIL" → width-adapting status pill; threat-model matrix bubbles were not clickable → `' + '' @@ -1880,12 +1928,11 @@ { id: 'reporting', name: 'AI-assistert rapportering' } ]; + // v1.15.0: currentProjectTab beholdes som zombie for ACTIONS['project-tab']- + // back-compat (test-playground-v3.sh asserter at handleren er definert). + // Selve action-en er unreachable fra v3 DOM. currentProjectScreen er fjernet + // sammen med ACTIONS['project-screen']. let currentProjectTab = 'regulatory'; - // Screen-tabs på project-surface (A4 Tier 3): Oversikt / Rapporter / - // Kontekst / Eksport. Default 'rapporter' = primær arbeidsflate (eksisterende - // category-tabs + panels). Andre skjermer er stub i Sesjon 2 og fylles ut - // i senere sesjoner. - let currentProjectScreen = 'rapporter'; function findProject(id) { const list = store.state.projects || []; @@ -1911,7 +1958,6 @@ store.state.projects.push(project); store.state.activeProjectId = id; currentProjectTab = 'regulatory'; - currentProjectScreen = 'rapporter'; return project; } @@ -2020,254 +2066,15 @@ ); } - // ---- Sub-card rendering ---- - - function renderCommandSubCard(cmd, projectId) { - // Sev-modifier: hvis rapporten er parsed, mappe verdict til DS card--severity-{level}. - // Plugin-domain-verdicts (go/block/approved/...) → severity-band (positive/critical/medium). - const project = findProject(projectId); - const report = project && project.reports && project.reports[cmd.id]; - const verdict = report && report.parsed && report.parsed.verdict - ? String(report.parsed.verdict).toLowerCase() : ''; - const sevMap = { - 'go': 'positive', 'approved': 'positive', 'allow': 'positive', - 'go-with-conditions': 'medium', 'warning': 'medium', - 'block': 'critical', 'failed': 'critical' - }; - const sevModifier = sevMap[verdict] || ''; - const sevClass = sevModifier ? ' card--severity-' + sevModifier : ''; - - const titleHtml = ( - '
' + - '
' + - '

' + escapeHtml(cmd.label) + '

' + - '

' + escapeHtml(cmd.description) + '

' + - '
' + - '/architect:' + escapeHtml(cmd.id) + '' + - '
' - ); - - const formZone = ( - '
' + - '

Skjema

' + - '
' + - renderCommandForm(cmd.id, { context: 'project', projectId: projectId, scope: 'p' }) + - '
' + - '
' - ); - - if (!cmd.produces_report) { - // Verktøy: skjema-zone + .guide-panel--info notis - const toolNotice = ( - '
' + - '
' + - '' + - '
' + - '

Verktøy

' + - '

Dette er et verktøy. Ingen rapport-import — bruk skjemaet til å bygge en pipeline-streng som kjøres i terminalen.

' + - '
' + - '
' + - '
' - ); - return ( - '
' + - titleHtml + - formZone + - toolNotice + - '
' - ); - } - - // Rapport-produserende: skjema-zone + paste-import-zone + report-zone - const pasteZone = ( - '
' + - '

Lim inn rapport-output

' + - '
' + - '' + - '
' + - '' + - 'Routes via PARSERS[' + escapeHtml(cmd.report_archetype || '?') + '] → ' + escapeHtml(cmd.renderer || '?') + ' (Step 11/12).' + - '
' + - '
' + - '
' - ); - - const reportZone = ( - '
' + - '

Visualisering

' + - '
' + - '
' - ); - - return ( - '
' + - titleHtml + - formZone + - pasteZone + - reportZone + - '
' - ); - } - - function renderProjectSurface() { - const root = getSurfaceEl('project'); - if (!root) return; - - const project = findProject(store.state.activeProjectId); - if (!project) { - // Mistet aktivt prosjekt — fall tilbake til hjem. - navigate('home'); - return; - } - - const reportTotal = CATALOG.commands.filter(function (c) { return c.produces_report; }).length; - const reportFilled = projectReportCount(project); - - // Action-bar (Tilbake / Slett) flyttet under page-shell-headeren — - // page__header har dedikert verdict-slot som ikke tar arbitrary HTML. - const actionBar = ( - '
' + - '' + - '' + - '
' - ); - - // Tab-list (DS): Oversikt / Rapporter / Kontekst / Eksport. - // Sesjon 2: Rapporter er primær; andre er stub-skjermer som fylles ut - // i Sesjon 3-6. - const SCREENS = [ - { id: 'oversikt', label: 'Oversikt' }, - { id: 'rapporter', label: 'Rapporter' }, - { id: 'kontekst', label: 'Kontekst' }, - { id: 'eksport', label: 'Eksport' } - ]; - const screenTabsHtml = ''; - - // Tabs per CATALOG.categories — kun synlig under "Rapporter"-skjermen. - const tabsHtml = '
' + CATALOG.categories.map(function (cat) { - const isActive = currentProjectTab === cat.id; - return ( - '' - ); - }).join('') + '
'; - - // Render ALLE kategori-paneler i DOM (med [hidden] på inaktive). Dette - // sikrer at querySelectorAll('[data-paste-import]') matcher alle 17 - // rapport-produserende commands uavhengig av aktiv tab. - const panelsHtml = CATALOG.categories.map(function (cat) { - const isActive = currentProjectTab === cat.id; - const cards = CATALOG.commands - .filter(function (c) { return c.category === cat.id; }) - .map(function (c) { return renderCommandSubCard(c, project.id); }).join(''); - return ( - '
' + - cards + - '
' - ); - }).join(''); - - const scenarioChipsList = (project.scenarios || []).map(function (sid) { - const s = SCENARIOS.find(function (x) { return x.id === sid; }); - return '
  • ' + escapeHtml(s ? s.name : sid) + '
  • '; - }).join(''); - - const oversiktHtml = ( - '
    ' + - '
    ' + - '' + - '
    ' + - '

    Oversikt

    ' + - '

    Opprettet ' + escapeHtml((project.createdAt || '').slice(0, 10)) + '. ' + reportFilled + ' av ' + reportTotal + ' rapporter generert.

    ' + - (scenarioChipsList ? '

    Scenarioer:

      ' + scenarioChipsList + '
    ' : '') + - '

    Sesjon 3+: aggregerte verdict-pillen, recommended-next-actions og top-risks vises her.

    ' + - '
    ' + - '
    ' + - '
    ' - ); - - const rapporterHtml = ( - '
    ' + - tabsHtml + - panelsHtml + - '
    ' - ); - - const kontekstHtml = ( - '
    ' + - '
    ' + - '' + - '
    ' + - '

    Kontekst

    ' + - '

    Fellesfeltene fra onboarding gjenbrukes automatisk i alle command-skjemaer. Bruk for å oppdatere.

    ' + - '

    Sesjon 3+: snapshot av de 20 fellesfeltene og hva som er prefilled per command vises her.

    ' + - '
    ' + - '
    ' + - '
    ' - ); - - const eksportHtml = ( - '
    ' + - '
    ' + - '' + - '
    ' + - '

    Eksport

    ' + - '

    Bruk Eksporter i toppmenyen for hele state. Per-prosjekt eksport (PDF/Markdown) kommer i Sesjon 6.

    ' + - '
    ' + - '
    ' + - '
    ' - ); - - const projectShell = renderPageShell({ - eyebrow: 'PROSJEKT', - title: project.name, - lede: project.description || '', - verdict: inferProjectVerdict(project), - keyStats: [ - { label: 'RAPPORTER', value: reportFilled + '/' + reportTotal }, - { label: 'SIST OPPDATERT', value: inferProjectLastUpdated(project) } - ] - }, - '
    ' + - actionBar + - screenTabsHtml + - oversiktHtml + - rapporterHtml + - kontekstHtml + - eksportHtml + - '
    ' - ); - - root.innerHTML = ( - renderTopbar('Prosjekt: ' + escapeHtml(project.name)) + - '
    ' + - projectShell + - '
    ' - ); - - // v1.10.0+: rehydrer paste-imports etter at DOM er bygget. queueMicrotask - // sikrer at innerHTML har commit-et før vi spørr etter [data-paste-import]. - queueMicrotask(rehydratePasteImports); - } + // v1.15.0: renderCommandSubCard + renderProjectSurface fjernet. + // v3 project-view (renderProjectView/renderProjectSurfaceV3) er nå eneste + // project-overflate. Paste-import er erstattet av modal-flow (import-open). // ============================================================ // CATALOG SURFACE (Step 9) // ============================================================ // - // 24 commands gruppert i 5 .expansion-grupper (CATALOG.categories) med + // 25 commands gruppert i 5 .expansion-grupper (CATALOG.categories) med // søke-input som filtrerer på id+label+description+argument_hint. // Hver kategori-expansion rendrer en .catalog-cards-grid med kort. // "Åpne skjema" på et kort åpner renderCommandForm() i modal. @@ -3253,6 +3060,11 @@ } function renderFindingsBlock(findings, label) { + // v1.14.0 sesjon 3: refaktorert fra .report-meta-band-aid til standalone + // findings-section. DS' .findings er grid 360px+1fr (list+detail-panel) — + // siden vi ikke har detail-panel, bruker vi en standalone container med + // .findings__items--standalone-modifier som styles lokalt. + if (!findings || !findings.length) return ''; const items = findings.map(function (f) { return '
  • ' + '' + @@ -3261,14 +3073,10 @@ 'Lokasjon: ' + escapeHtml(f.location || '—') + ' · Severity: ' + escapeHtml(f.severity || '—') + '' + '
  • '; }).join(''); - return '
    ' + - '
    ' + - '
    ' + - '
    ' + escapeHtml(label) + '' + findings.length + '
    ' + - '
      ' + items + '
    ' + - '
    ' + - '
    ' + - '
    '; + return '
    ' + + '

    ' + escapeHtml(label) + '

    ' + + '
      ' + items + '
    ' + + '
    '; } function renderMatrixHtml(data, cons_max) { @@ -3288,12 +3096,14 @@ for (let prob = 1; prob <= probSize; prob++) { const score = prob * cons; const items = byPC[prob + '_' + cons] || []; + // v1.13.0 fix (B3): bobler er nå '; }).join('') + - (items.length > 3 ? '+' + (items.length - 3) + '' : '') + + (items.length > 3 ? '' : '') + '' : ''; html += '
    ' + @@ -3313,8 +3123,13 @@ function renderRadarSvg(axes) { if (!axes || !axes.length) return ''; + // v1.13.0 fix (B4): bump SVG fra 300×300 til 380×380, R fra 100 til 125, + // label-offset fra R+25 til R+28, og dynamisk text-anchor basert på + // horisontal-posisjon. Forhindrer at bottom-labels overlapper ved 6+ + // akser (typisk for ROS med 7 risiko-dimensjoner). Speilet fra + // llm-security v7.6.1 commit f9b555a. const N = axes.length; - const cx = 150, cy = 150, R = 100; + const SIZE = 380, cx = SIZE / 2, cy = SIZE / 2, R = 125; const points = axes.map(function (a, i) { const angle = (i / N) * 2 * Math.PI - Math.PI / 2; const r = R * (Math.max(0, Math.min(5, a.score)) / 5); @@ -3322,9 +3137,11 @@ }).join(' '); const labels = axes.map(function (a, i) { const angle = (i / N) * 2 * Math.PI - Math.PI / 2; - const x = cx + (R + 25) * Math.cos(angle); - const y = cy + (R + 25) * Math.sin(angle); - return '' + escapeHtml(a.name) + ''; + const x = cx + (R + 28) * Math.cos(angle); + const y = cy + (R + 28) * Math.sin(angle); + const dx = Math.cos(angle); + const anchor = Math.abs(dx) < 0.2 ? 'middle' : (dx > 0 ? 'start' : 'end'); + return '' + escapeHtml(a.name) + ''; }).join(''); const spokes = axes.map(function (a, i) { const angle = (i / N) * 2 * Math.PI - Math.PI / 2; @@ -3333,7 +3150,7 @@ return ''; }).join(''); return '
    ' + - '' + + '' + '' + '' + spokes + labels + @@ -3376,15 +3193,23 @@ ''; }).join('') + '
    '; + // v1.14.0 sesjon 4:
    '; }).join('') : ''; - const body = cardsHtml + (expansionsHtml ? '
    ' + expansionsHtml + '
    ' : ''); + // v1.14.0 sesjon 4: stack-sm rundt expansion-list (etter B-DS-2 fix gir + // expansion__title-main/sub display: block, så vertikal stacking er ren). + const body = cardsHtml + (expansionsHtml ? '
    ' + expansionsHtml + '
    ' : ''); slot.innerHTML = renderPageShell({ eyebrow: 'KRAV', title: data.title || 'AI Act-krav per risiko og rolle', @@ -3560,8 +3387,10 @@ '
    ' + ''; }).join(''); + // v1.14.0 sesjon 4: timeline-section standalone (uten .report-meta-wrapper). timelineHtml = - '

    Frister

    ' + + '
    ' + + '

    Frister

    ' + '
    ' + '
    ' + '
    ' + @@ -3582,7 +3411,12 @@ } function renderDpia(data, slot) { - const matrixHtml = renderMatrixHtml(data, 5); + // v1.14.0 sesjon 3: matrix wrappet i .card med h2 for visuell separasjon + // fra residual-pair og threats-tabell (per Anthropic-ref ros-lier-pattern). + const matrixHtml = '
    ' + + '

    5×5 Risikomatrise

    ' + + renderMatrixHtml(data, 5) + + '
    '; const threatsHtml = renderThreatsTable(data.threats); const rp = data.residualPair; let residualHtml = ''; @@ -3646,34 +3480,51 @@ if (n >= 4) return 'medium'; return 'low'; }; - const matrixHtml = renderMatrixHtml(data, 6); - const radarHtml = renderRadarSvg(data.dimensions || []); - // C7 small-multiples per OWASP-kategori (driver: categoryGrades). + // v1.14.0 sesjon 3: matrix + radar i .ros-layout (1fr 320px) per Anthropic-ref + // ros-lier-scenario. Matrix står i .card, radar i
    '; } const findingsHtml = renderFindingsBlock(data.findings || [], 'Sikkerhetsfunn'); - const body = matrixHtml + radarHtml + smallMultiplesHtml + topRisksHtml + residualHtml + findingsHtml; + const body = layoutHtml + smallMultiplesHtml + topRisksHtml + residualHtml + findingsHtml; // Utvid matrix-risk-6x5-keyStats med RESTRISIKO når residualPair finnes. const baseStats = inferKeyStats(data, 'matrix-risk-6x5'); const stats = (data.keyStats || (rp && rp.after @@ -3729,21 +3580,34 @@ if (n >= 4) return 'medium'; return 'low'; }; - const matrixHtml = renderMatrixHtml(data, 5); - const radarHtml = renderRadarSvg(data.radar_axes || []); - // C4 top-risks-list (max 5). + // v1.14.0 sesjon 3: speil renderSecurity-pattern. Matrix + radar i .ros-layout + // (1fr 320px), top-risks + recommendation i .summary-grid (1.4fr 1fr). + const layoutHtml = '
    ' + + '
    ' + + '

    5×5 Risikomatrise

    ' + + renderMatrixHtml(data, 5) + + '
    ' + + '' + + '
    '; + // C4 top-risks-list (max 5) — som
      inne i .card. const topRisks = (data.topRisks || []).slice(0, 5); - const topRisksHtml = topRisks.length ? '
      ' + - '

      Top-risikoer

      ' + - topRisks.map(function (r, i) { - const sev = r.severity || sevForScore(r.score); - const scoreLabel = r.score ? String(r.score) : (r.severity || '—').toUpperCase(); - return '
      ' + - '' + (i + 1) + '' + - '' + escapeHtml(r.description || '') + '' + - '' + escapeHtml(scoreLabel) + '' + - '
      '; - }).join('') + '
      ' : ''; + const topRisksCardHtml = topRisks.length ? '
      ' + + '

      Topp-risikoer

      ' + + '
        ' + + topRisks.map(function (r, i) { + const sev = r.severity || sevForScore(r.score); + const scoreLabel = r.score ? String(r.score) : (r.severity || '—').toUpperCase(); + return '
      1. ' + + '' + (i + 1) + '' + + '' + escapeHtml(r.description || '') + '' + + '' + escapeHtml(scoreLabel) + '' + + '
      2. '; + }).join('') + + '
      ' + + '
      ' : ''; // B6 residual-pair (gjenbruker mønster fra Dpia / Security). const rp = data.residualPair; let residualHtml = ''; @@ -3772,12 +3636,18 @@ } // D5 recommendation-card. const rec = data.recommendation || ''; - const recommendationHtml = rec ? '
    '; } - const detailsHtml = phases.map(function (p) { + // v1.13.1 fix (B14): brukeren etterspurte tabell-visning. Legg til en + // phases-summary-tabell over phase-detail-seksjonene som gir oversikt + // (Fase, Varighet, Milepæler-count, Suksess-count, Status). + const phasesSummaryRows = phases.map(function (p, i) { + const state = stepStateFor(p, i); + const stateLabel = state === 'completed' ? 'Ferdig' : state === 'current' ? 'Pågår' : 'Planlagt'; + return '' + + '' + escapeHtml(p.name) + '' + + '' + escapeHtml(String(p.duration_weeks || '—')) + ' uker' + + '' + ((p.milestones || []).length) + '' + + '' + ((p.success_criteria || []).length) + '' + + '' + escapeHtml(stateLabel) + '' + + ''; + }).join(''); + const phasesSummaryHtml = phasesSummaryRows + ? '' + phasesSummaryRows + '
    FaseVarighetMilepælerSuksesskriterierStatus
    ' + : ''; + // v1.14.0 sesjon 5: phase-detail (lokal CSS-mønster) erstattet med + //
    -list (DS-supplement). Default-collapsed, klikkbar + // header = fase-navn + varighet, body = milepæler + suksesskriterier. + // phase-expand-handler er registrert i ACTIONS som alias for samme + // toggle-mønster som requirement-expand. + const detailsHtml = phases.length ? '
    ' + phases.map(function (p, idx) { const ms = (p.milestones || []).map(function (m) { return '
  • ' + escapeHtml(m) + '
  • '; }).join(''); const sc = (p.success_criteria || []).map(function (s) { return '
  • ' + escapeHtml(s) + '
  • '; }).join(''); - return '
    ' + - '

    ' + escapeHtml(p.name) + ' (' + (p.duration_weeks || '?') + ' uker)

    ' + - (ms ? '

    Milepæler

      ' + ms + '
    ' : '') + - (sc ? '

    Suksesskriterier

      ' + sc + '
    ' : '') + - '
    '; - }).join(''); + const innerBody = (ms ? '

    Milepæler

      ' + ms + '
    ' : '') + + (sc ? '

    Suksesskriterier

      ' + sc + '
    ' : ''); + return ''; + }).join('') + '
    ' : ''; const risksRows = (data.risks || []).map(function (r) { return '' + escapeHtml(r.risk || '') + '' + escapeHtml(r.probability || '') + '' + escapeHtml(r.consequence || '') + '' + escapeHtml(r.mitigation || '') + ''; }).join(''); const risksHtml = risksRows ? '' + risksRows + '
    RisikoSannsynlighetKonsekvensTiltak
    ' : ''; - const body = ribbonHtml + ladderHtml + detailsHtml + risksHtml; + const body = ribbonHtml + ladderHtml + phasesSummaryHtml + detailsHtml + risksHtml; slot.innerHTML = renderPageShell({ eyebrow: 'MIGRASJON', title: data.title || 'Migrasjonsplan', @@ -4224,9 +4125,28 @@ '
    '; }).join(''); const ladderHtml = '
    ' + stepsHtml + '
    '; - // B5 traffic-light per success-kriterie. R15: uten eksplisitt status, - // bruk fasens state — done=go, active=warning, future=neutral. - const detailsHtml = phases.map(function (p, i) { + // v1.13.1 fix (B15): phases-summary-tabell over phase-detail-seksjonene + // gir struktur og forhindrer at faseinfo flyter horisontalt mot risiko- + // tabellen i renderPoc. Samme mønster som renderMigrate. + const phasesSummaryRows = phases.map(function (p, i) { + const state = stepStateFor(p, i); + const stateLabel = state === 'completed' ? 'Ferdig' : state === 'current' ? 'Pågår' : 'Planlagt'; + return '' + + '' + escapeHtml(p.name) + '' + + '' + escapeHtml(String(p.duration_weeks || '—')) + ' uker' + + '' + ((p.milestones || []).length) + '' + + '' + ((p.success_criteria || []).length) + '' + + '' + escapeHtml(stateLabel) + '' + + ''; + }).join(''); + const phasesSummaryHtml = phasesSummaryRows + ? '' + phasesSummaryRows + '
    FaseVarighetMilepælerSuksesskriterierStatus
    ' + : ''; + // v1.14.0 sesjon 5: phase-detail erstattet med expansion-list (samme + // mønster som renderMigrate). Traffic-light per success-kriterie beholdes + // inne i expansion-body. R15: uten eksplisitt status, bruk fasens state — + // done=green, active=yellow, future=gray. + const detailsHtml = phases.length ? '
    ' + phases.map(function (p, i) { const state = stepStateFor(p, i); const tlStatus = state === 'completed' ? 'green' : state === 'current' ? 'yellow' : 'gray'; const ms = (p.milestones || []).map(function (m) { return '
  • ' + escapeHtml(m) + '
  • '; }).join(''); @@ -4238,19 +4158,28 @@ '' + ''; }).join(''); - return '
    ' + - '

    ' + escapeHtml(p.name) + ' (' + (p.duration_weeks || '?') + ' uker)

    ' + - (ms ? '

    Milepæler

      ' + ms + '
    ' : '') + - (sc ? '

    Suksesskriterier

      ' + sc + '
    ' : '') + - '
    '; - }).join(''); + const innerBody = (ms ? '

    Milepæler

      ' + ms + '
    ' : '') + + (sc ? '

    Suksesskriterier

      ' + sc + '
    ' : ''); + return ''; + }).join('') + '
    ' : ''; const risksRows = (data.risks || []).map(function (r) { return '' + escapeHtml(r.risk || '') + '' + escapeHtml(r.probability || '') + '' + escapeHtml(r.consequence || '') + '' + escapeHtml(r.mitigation || '') + ''; }).join(''); const risksHtml = risksRows ? '' + risksRows + '
    RisikoSannsynlighetKonsekvensTiltak
    ' : ''; - const body = ladderHtml + detailsHtml + risksHtml; + const body = ladderHtml + phasesSummaryHtml + detailsHtml + risksHtml; // B1 verdict-pille: data.pocVerdict styrer (go/go-with-conditions/block). // R15: hvis ikke satt, fall tilbake til risk-baserte heuristikk. let verdict = data.verdict || data.pocVerdict; @@ -4302,8 +4231,14 @@ } return '
    ' + escapeHtml(txt).replace(/\n/g, '
    ') + '
    '; }; - const tabsNavHtml = tabs.length ? '