Skip to content

Commit e33ab30

Browse files
raifdmuellerclaude
andcommitted
feat: add LLM Runtime Integration modifier (Phase 1+2)
Introduces a cross-cutting modifier (L0-L4) that describes how heavily the software integrates LLMs at runtime, separate from the 5 code dimensions. L3+ pushes the effective tier floor: - L3 (Tool Use) → min Tier 3 - L4 (Agentic) → min Tier 4 At L3+, a callout points to specialized frameworks (OWASP LLM Top 10, Palo Alto SHIELD, Aikido VCAL, Google SAIF) since the built-in mitigation catalog covers build-time risks only. Phase 1 (data model + tier logic): - getTierIndex(values, llmRuntimeLevel) with hard floor multiplier - llmRuntimeLevel state in RiskRadar, passed to RadarChart - RadarChart's rAF updater reads level via ref (closure-safe) Phase 2 (minimal UI): - Pill-button row between chart and tier badge - Callout box at L3+ with framework links - DE/EN i18n for labels, level descriptions, callout text Phases 3-5 (presets, extended docs, skills integration) follow. Refs #20 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent bc76722 commit e33ab30

5 files changed

Lines changed: 196 additions & 5 deletions

File tree

src/components/RadarChart.jsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export default function RadarChart({
118118
size = 460,
119119
determiningKey = null,
120120
registerUpdater = null,
121+
llmRuntimeLevel = 0,
121122
}) {
122123
// Safe fallbacks to prevent math from crashing if data is missing
123124
const safeDims = dimensions || [];
@@ -130,7 +131,12 @@ export default function RadarChart({
130131
const n = safeDims.length || 1; // prevents division by zero
131132
const angStep = 360 / n;
132133

133-
const ti = getTierIndex(safeVals);
134+
// Ref mirrors the prop so the rAF-driven registerUpdater closure
135+
// can read the current level without re-binding the callback.
136+
const llmRuntimeLevelRef = useRef(llmRuntimeLevel);
137+
llmRuntimeLevelRef.current = llmRuntimeLevel;
138+
139+
const ti = getTierIndex(safeVals, llmRuntimeLevel);
134140
const tc = TIER_BG[ti] || "#6b7280";
135141

136142
const [tooltip, setTooltip] = useState(null);
@@ -150,7 +156,7 @@ export default function RadarChart({
150156
registerUpdater((newValues) => {
151157
if (!mountedRef.current) return;
152158
const dims = dimensionsRef.current;
153-
const newTi = getTierIndex(newValues);
159+
const newTi = getTierIndex(newValues, llmRuntimeLevelRef.current);
154160
const newTc = TIER_BG[newTi] || "#6b7280";
155161
const newRiskPts = dims.map((d, i) =>
156162
polarToCartesian(cx, cy, (maxR / levels) * (newValues[d.key] + 1), i * angStep),

src/components/RiskRadar.jsx

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ export default function RiskRadar() {
2020
// React state holds only the final integer values (for tier badge, level labels etc.)
2121
const [values, setValues] = useState({ codeType: 0, language: 1, deployment: 0, data: 0, blastRadius: 0 });
2222

23+
// LLM Runtime Integration Level (0=none, 1=classify, 2=generate, 3=tools, 4=agentic)
24+
// Cross-cutting modifier — at L3+ it pushes the effective tier floor up.
25+
const [llmRuntimeLevel, setLlmRuntimeLevelState] = useState(0);
26+
const llmRuntimeLevelRef = useRef(0);
27+
const setLlmRuntimeLevel = useCallback((level) => {
28+
llmRuntimeLevelRef.current = level;
29+
setLlmRuntimeLevelState(level);
30+
}, []);
31+
2332
// Animated float values live in a ref — updated every rAF frame
2433
// Both sliders and RadarChart read from this ref via their own refs
2534
const floatValuesRef = useRef({ ...values });
@@ -88,7 +97,7 @@ export default function RiskRadar() {
8897
roundedValues[k] = Math.round(values[k]);
8998
});
9099

91-
const ti = getTierIndex(roundedValues);
100+
const ti = getTierIndex(roundedValues, llmRuntimeLevel);
92101
const tier = t.tiers[ti];
93102
const tc = TIER_BG[ti];
94103

@@ -162,12 +171,53 @@ export default function RiskRadar() {
162171
<RadarChart
163172
values={values}
164173
dimensions={t.dimensions}
174+
llmRuntimeLevel={llmRuntimeLevel}
165175
registerUpdater={(fn) => {
166176
chartValuesRef.current = fn;
167177
}}
168178
/>
169179
</div>
170180

181+
{/* LLM Runtime Integration Modifier */}
182+
<div className={styles.llmRuntime}>
183+
<div className={styles.llmRuntimeLabel}>{t.llmRuntime.label}</div>
184+
<div className={styles.llmRuntimeLevels}>
185+
{[0, 1, 2, 3, 4].map((lvl) => {
186+
const isActive = llmRuntimeLevel === lvl;
187+
const levelInfo = t.llmRuntime.levels[lvl];
188+
return (
189+
<button
190+
key={lvl}
191+
onClick={() => setLlmRuntimeLevel(lvl)}
192+
className={styles.llmRuntimeBtn}
193+
title={levelInfo.desc}
194+
style={{
195+
border: isActive ? `2px solid ${tc}` : "1px solid var(--border)",
196+
background: isActive ? `${tc}22` : "var(--bg-card)",
197+
color: isActive ? "var(--text-heading)" : "var(--text-muted)",
198+
fontWeight: isActive ? 700 : 500,
199+
}}
200+
>
201+
L{lvl}
202+
<span className={styles.llmRuntimeBtnLabel}>{levelInfo.short}</span>
203+
</button>
204+
);
205+
})}
206+
</div>
207+
{llmRuntimeLevel >= 3 && (
208+
<div className={styles.llmRuntimeCallout} style={{ borderLeft: `3px solid ${tc}` }}>
209+
<strong>{t.llmRuntime.calloutTitle}</strong> {t.llmRuntime.calloutBody}
210+
<div className={styles.llmRuntimeLinks}>
211+
{t.llmRuntime.frameworks.map((fw) => (
212+
<a key={fw.name} href={fw.url} target="_blank" rel="noopener">
213+
{fw.name}
214+
</a>
215+
))}
216+
</div>
217+
</div>
218+
)}
219+
</div>
220+
171221
{/* Tier badge */}
172222
<div
173223
className={styles.tierBadge}

src/components/RiskRadar.module.css

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,94 @@
8383
max-width: 500px; /* ← change only this to resize the chart */
8484
}
8585

86+
/* ── LLM Runtime Integration Modifier ───────────────────────────────────── */
87+
88+
.llmRuntime {
89+
width: 100%;
90+
max-width: 500px;
91+
display: flex;
92+
flex-direction: column;
93+
gap: 6px;
94+
}
95+
96+
.llmRuntimeLabel {
97+
font-size: 13px;
98+
font-weight: 600;
99+
color: var(--text-secondary);
100+
text-align: center;
101+
text-transform: uppercase;
102+
letter-spacing: 0.05em;
103+
}
104+
105+
.llmRuntimeLevels {
106+
display: flex;
107+
gap: 6px;
108+
justify-content: center;
109+
flex-wrap: wrap;
110+
}
111+
112+
.llmRuntimeBtn {
113+
display: flex;
114+
flex-direction: column;
115+
align-items: center;
116+
gap: 2px;
117+
padding: 6px 10px;
118+
min-width: 74px;
119+
border-radius: 8px;
120+
cursor: pointer;
121+
font-size: 13px;
122+
transition: all 0.2s ease;
123+
}
124+
125+
.llmRuntimeBtn:hover {
126+
transform: translateY(-1px);
127+
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
128+
}
129+
130+
.llmRuntimeBtnLabel {
131+
font-size: 10px;
132+
font-weight: 500;
133+
text-transform: uppercase;
134+
letter-spacing: 0.03em;
135+
opacity: 0.85;
136+
}
137+
138+
.llmRuntimeCallout {
139+
background: var(--bg-card);
140+
border-radius: 8px;
141+
padding: 10px 14px;
142+
font-size: 13px;
143+
color: var(--text-secondary);
144+
line-height: 1.5;
145+
margin-top: 4px;
146+
}
147+
148+
.llmRuntimeCallout strong {
149+
color: var(--text-primary);
150+
display: block;
151+
margin-bottom: 4px;
152+
}
153+
154+
.llmRuntimeLinks {
155+
display: flex;
156+
gap: 12px;
157+
flex-wrap: wrap;
158+
margin-top: 8px;
159+
}
160+
161+
.llmRuntimeLinks a {
162+
color: var(--link);
163+
text-decoration: none;
164+
border-bottom: 1px solid var(--link-underline);
165+
font-weight: 600;
166+
font-size: 12px;
167+
transition: border-color 0.2s ease;
168+
}
169+
170+
.llmRuntimeLinks a:hover {
171+
border-bottom-color: var(--link);
172+
}
173+
86174
.tierBadge {
87175
display: inline-flex;
88176
align-items: center;

src/i18n.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,28 @@ const T = {
1919
docsButton: "Dokumentation",
2020
closeButton: "Schließen",
2121
langSwitch: "EN",
22+
llmRuntime: {
23+
label: "LLM Runtime Integration",
24+
levels: [
25+
{ short: "Kein LLM", desc: "Klassische Software ohne LLM zur Laufzeit" },
26+
{ short: "Klassifikation", desc: "Passive Nutzung: Sentiment, Intent, Embeddings" },
27+
{ short: "Generativ", desc: "Generative Ausgabe: Chat, Zusammenfassungen" },
28+
{ short: "Tool Use", desc: "Function Calling: LLM triggert Aktionen" },
29+
{ short: "Agentic", desc: "Autonome Loops, Code-Execution, Selbstmodifikation" },
30+
],
31+
calloutTitle: "Runtime-Risiken außerhalb unseres Katalogs.",
32+
calloutBody:
33+
"Die Mitigationen hier decken Build-Time-Risiken ab. Für Prompt Injection, Tool Sandboxing und Agentic Guardrails siehe spezialisierte Frameworks:",
34+
frameworks: [
35+
{
36+
name: "OWASP LLM Top 10",
37+
url: "https://owasp.org/www-project-top-10-for-large-language-model-applications/",
38+
},
39+
{ name: "Palo Alto SHIELD", url: "https://unit42.paloaltonetworks.com/securing-vibe-coding-tools/" },
40+
{ name: "Aikido VCAL", url: "https://www.aikido.dev/blog/vibe-coding-security" },
41+
{ name: "Google SAIF", url: "https://saif.google/secure-ai-framework" },
42+
],
43+
},
2244
typeBadges: {
2345
deterministic: "Deterministisch",
2446
probabilistic: "Probabilistisch",
@@ -383,6 +405,28 @@ Quellcode der Skills: https://github.com/LLM-Coding/vibe-coding-risk-radar/tree/
383405
docsButton: "Documentation",
384406
closeButton: "Close",
385407
langSwitch: "DE",
408+
llmRuntime: {
409+
label: "LLM Runtime Integration",
410+
levels: [
411+
{ short: "No LLM", desc: "Classical software, no LLM at runtime" },
412+
{ short: "Classify", desc: "Passive use: sentiment, intent, embeddings" },
413+
{ short: "Generate", desc: "Generative output: chat, summaries" },
414+
{ short: "Tool Use", desc: "Function calling: LLM triggers actions" },
415+
{ short: "Agentic", desc: "Autonomous loops, code execution, self-modification" },
416+
],
417+
calloutTitle: "Runtime risks are outside our catalog.",
418+
calloutBody:
419+
"The mitigations here cover build-time risks. For prompt injection, tool sandboxing, and agentic guardrails, see specialized frameworks:",
420+
frameworks: [
421+
{
422+
name: "OWASP LLM Top 10",
423+
url: "https://owasp.org/www-project-top-10-for-large-language-model-applications/",
424+
},
425+
{ name: "Palo Alto SHIELD", url: "https://unit42.paloaltonetworks.com/securing-vibe-coding-tools/" },
426+
{ name: "Aikido VCAL", url: "https://www.aikido.dev/blog/vibe-coding-security" },
427+
{ name: "Google SAIF", url: "https://saif.google/secure-ai-framework" },
428+
],
429+
},
386430
typeBadges: {
387431
deterministic: "Deterministic",
388432
probabilistic: "Probabilistic",

src/utils.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
export function getTierIndex(values) {
1+
export function getTierIndex(values, llmRuntimeLevel = 0) {
22
const mx = Math.max(...Object.values(values));
3-
return mx <= 1 ? 0 : mx <= 2 ? 1 : mx <= 3 ? 2 : 3;
3+
const base = mx <= 1 ? 0 : mx <= 2 ? 1 : mx <= 3 ? 2 : 3;
4+
// LLM Runtime Modifier: L3 → min Tier 3 (index 2), L4 → min Tier 4 (index 3)
5+
const floor = llmRuntimeLevel >= 4 ? 3 : llmRuntimeLevel >= 3 ? 2 : 0;
6+
return Math.max(base, floor);
47
}
58

69
export function polarToCartesian(cx, cy, r, angleDeg) {

0 commit comments

Comments
 (0)