opencode-goal-mode 0.2.1 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The ORIGINAL regex-based shell classifier, preserved verbatim from the first
3
+ * published version of the plugin (commit 130956d) so the benchmark can compare
4
+ * it apples-to-apples against the current quote-aware analyzer.
5
+ *
6
+ * Do not "improve" this file — its job is to faithfully represent the old
7
+ * behavior that the new analyzer replaced.
8
+ */
9
+
10
+ const MUTATING_BASH_PATTERNS = [
11
+ /(^|&&|;|\|\|)\s*(sudo\s+)?(rm|mv|cp|mkdir|rmdir|touch|ln)\b/i,
12
+ /(^|&&|;|\|\|)\s*(sudo\s+)?(tee|xargs\s+(rm|mv|cp))\b/i,
13
+ /(^|&&|;|\|\|)\s*[^|]*\s(>|>>)\s*(?!\/dev\/null\b)\S+/i,
14
+ /(^|&&|;|\|\|)\s*(perl\s+-pi|sed\s+-i)\b/i,
15
+ /(^|&&|;|\|\|)\s*(npm|pnpm|yarn|bun)\s+(install|ci|add|remove|update)\b/i,
16
+ /(^|&&|;|\|\|)\s*(npm|pnpm|yarn|bun)\s+(run\s+)?(format|fix|lint:fix)\b/i,
17
+ /\b((npx|pnpm\s+exec|yarn)\s+)?(prettier|eslint)\b.*\s(--write|--fix)\b/i,
18
+ /\b(node|python3?)\b.*\b(writeFile|appendFile|copyFile|rename|unlink|rmSync|mkdir|rmdir|openSync)\b/i,
19
+ ];
20
+
21
+ export function looksLikeDestructiveBash(command) {
22
+ const normalized = String(command || "").trim();
23
+ return [
24
+ /(^|&&|;|\|\|)\s*(sudo\s+)?rm\s+-[a-zA-Z]*[rR][a-zA-Z]*[rfRF]?\b/,
25
+ /(^|&&|;|\|\|)\s*(sudo\s+)?rm\s+(--recursive|--force|--recursive\s+--force|-rf|-fr|-r)\b/,
26
+ /(^|&&|;|\|\|)\s*git\s+reset\b/,
27
+ /(^|&&|;|\|\|)\s*git\s+clean\b/,
28
+ /(^|&&|;|\|\|)\s*git\s+checkout\b/,
29
+ /(^|&&|;|\|\|)\s*git\s+restore\b/,
30
+ /(^|&&|;|\|\|)\s*git\s+switch\b/,
31
+ /(^|&&|;|\|\|)\s*git\s+push\b/,
32
+ /(^|&&|;|\|\|)\s*(sudo\s+)?find\b.*\s-delete\b/,
33
+ /(^|&&|;|\|\|)\s*(sudo\s+)?find\b.*\s-exec\s+rm\b/,
34
+ /(^|&&|;|\|\|)\s*(sudo\s+)?dd\b.*\bof=\/dev\//,
35
+ /(^|&&|;|\|\|)\s*(sudo\s+)?mkfs(\.|\s|$)/,
36
+ /(^|&&|;|\|\|)\s*(sudo\s+)?shred\b/,
37
+ /(^|&&|;|\|\|)\s*(sudo\s+)?truncate\b/,
38
+ /(^|&&|;|\|\|)\s*(sudo\s+)?chmod\s+-[a-zA-Z]*[rR][a-zA-Z]*[wW][a-zA-Z]*[xX][a-zA-Z]*\s+\/\b/,
39
+ ].some((pattern) => pattern.test(normalized));
40
+ }
41
+
42
+ export function looksLikeMutatingBash(command) {
43
+ const normalized = String(command || "").trim();
44
+ if (!normalized) return false;
45
+ if (looksLikeDestructiveBash(normalized)) return true;
46
+ return MUTATING_BASH_PATTERNS.some((pattern) => pattern.test(normalized));
47
+ }
48
+
49
+ /** Adapter to the analyzer signal shape used by the benchmark. */
50
+ export function analyzeCommand(command) {
51
+ const destructive = looksLikeDestructiveBash(command);
52
+ const mutating = looksLikeMutatingBash(command);
53
+ return { destructive, mutating, verification: false, networkExec: false, reasons: [] };
54
+ }
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Reproducible benchmark of the shell-command guard.
4
+ *
5
+ * Compares the original regex classifier (benchmarks/legacy-analyzer.mjs,
6
+ * preserved verbatim from the first release) against the current quote-aware
7
+ * analyzer (plugins/goal-guard/shell.js) on a labeled corpus, and measures
8
+ * detection rate, false-positive rate, and throughput. Writes results.json and
9
+ * the SVG charts embedded in the README.
10
+ *
11
+ * node benchmarks/run.mjs
12
+ */
13
+
14
+ import { writeFileSync, mkdirSync } from "node:fs";
15
+ import { join, dirname } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+ import { performance } from "node:perf_hooks";
18
+ import { CORPUS } from "./corpus.mjs";
19
+ import * as current from "../plugins/goal-guard/shell.js";
20
+ import * as legacy from "./legacy-analyzer.mjs";
21
+ import { groupedBarChart, horizontalBarChart } from "./charts.mjs";
22
+ import { runTruthfulnessBenchmark } from "./truthfulness.mjs";
23
+
24
+ const root = fileURLToPath(new URL("..", import.meta.url));
25
+ const outDir = join(root, "docs", "benchmarks");
26
+ mkdirSync(outDir, { recursive: true });
27
+
28
+ /** A command is "blocked" when flagged destructive or as remote execution. */
29
+ function blocked(analyzer, cmd) {
30
+ const a = analyzer.analyzeCommand(cmd);
31
+ return Boolean(a.destructive || a.networkExec);
32
+ }
33
+
34
+ function evaluate(analyzer) {
35
+ const families = {};
36
+ let destTotal = 0;
37
+ let destCaught = 0;
38
+ let safeTotal = 0;
39
+ let safeFalsePos = 0;
40
+
41
+ for (const { cmd, label, family } of CORPUS) {
42
+ families[family] ??= { destTotal: 0, destCaught: 0, safeTotal: 0, safeFalsePos: 0 };
43
+ const isBlocked = blocked(analyzer, cmd);
44
+ if (label === "destructive") {
45
+ destTotal += 1;
46
+ families[family].destTotal += 1;
47
+ if (isBlocked) {
48
+ destCaught += 1;
49
+ families[family].destCaught += 1;
50
+ }
51
+ } else {
52
+ safeTotal += 1;
53
+ families[family].safeTotal += 1;
54
+ if (isBlocked) {
55
+ safeFalsePos += 1;
56
+ families[family].safeFalsePos += 1;
57
+ }
58
+ }
59
+ }
60
+
61
+ return {
62
+ detectionRate: destTotal ? (destCaught / destTotal) * 100 : 0,
63
+ falsePositiveRate: safeTotal ? (safeFalsePos / safeTotal) * 100 : 0,
64
+ destCaught,
65
+ destTotal,
66
+ safeFalsePos,
67
+ safeTotal,
68
+ families,
69
+ };
70
+ }
71
+
72
+ function throughput(analyzer) {
73
+ const cmds = CORPUS.map((c) => c.cmd);
74
+ // Warm up.
75
+ for (const c of cmds) analyzer.analyzeCommand(c);
76
+ const iterations = 4000;
77
+ const start = performance.now();
78
+ for (let i = 0; i < iterations; i += 1) {
79
+ for (const c of cmds) analyzer.analyzeCommand(c);
80
+ }
81
+ const ms = performance.now() - start;
82
+ const ops = (iterations * cmds.length) / (ms / 1000);
83
+ return Math.round(ops);
84
+ }
85
+
86
+ /** Locale-independent thousands grouping (the host locale may use '.' as separator). */
87
+ function fmt(n) {
88
+ return Math.round(n)
89
+ .toString()
90
+ .replace(/\B(?=(\d{3})+(?!\d))/g, ",");
91
+ }
92
+
93
+ const legacyEval = evaluate(legacy);
94
+ const currentEval = evaluate(current);
95
+ const truthfulness = runTruthfulnessBenchmark();
96
+ const legacyOps = throughput(legacy);
97
+ const currentOps = throughput(current);
98
+ const legacyUs = 1e6 / legacyOps;
99
+ const currentUs = 1e6 / currentOps;
100
+
101
+ const FAMILY_LABELS = {
102
+ classic: "Classic",
103
+ bypass: "Obfuscated",
104
+ "remote-exec": "Remote exec",
105
+ };
106
+ const detFamilies = ["classic", "bypass", "remote-exec"];
107
+
108
+ function familyRate(ev, fam) {
109
+ const f = ev.families[fam];
110
+ return f && f.destTotal ? (f.destCaught / f.destTotal) * 100 : 0;
111
+ }
112
+
113
+ const results = {
114
+ corpusSize: CORPUS.length,
115
+ destructiveCount: CORPUS.filter((c) => c.label === "destructive").length,
116
+ safeCount: CORPUS.filter((c) => c.label === "safe").length,
117
+ legacy: { ...legacyEval, opsPerSec: legacyOps, usPerCommand: Number(legacyUs.toFixed(2)) },
118
+ current: { ...currentEval, opsPerSec: currentOps, usPerCommand: Number(currentUs.toFixed(2)) },
119
+ truthfulness,
120
+ };
121
+
122
+ writeFileSync(join(outDir, "results.json"), JSON.stringify(results, null, 2));
123
+
124
+ // Chart 1: detection rate by command family.
125
+ writeFileSync(
126
+ join(outDir, "detection-by-family.svg"),
127
+ groupedBarChart({
128
+ title: "Destructive-command detection rate by family",
129
+ subtitle: `Higher is better. Corpus: ${results.destructiveCount} destructive commands.`,
130
+ groups: detFamilies.map((f) => FAMILY_LABELS[f]),
131
+ series: [
132
+ { name: "Legacy regex guard", color: "#9aa0a6", values: detFamilies.map((f) => familyRate(legacyEval, f)) },
133
+ { name: "Goal Mode analyzer", color: "#2da44e", values: detFamilies.map((f) => familyRate(currentEval, f)) },
134
+ ],
135
+ }),
136
+ );
137
+
138
+ // Chart 2: overall scorecard (detection up, false positives down).
139
+ writeFileSync(
140
+ join(outDir, "overall-scorecard.svg"),
141
+ groupedBarChart({
142
+ title: "Overall guard accuracy",
143
+ subtitle: "Detection rate (higher better) vs false-positive rate (lower better).",
144
+ groups: ["Detection rate", "False-positive rate"],
145
+ series: [
146
+ { name: "Legacy regex guard", color: "#9aa0a6", values: [legacyEval.detectionRate, legacyEval.falsePositiveRate] },
147
+ { name: "Goal Mode analyzer", color: "#2da44e", values: [currentEval.detectionRate, currentEval.falsePositiveRate] },
148
+ ],
149
+ }),
150
+ );
151
+
152
+ // Chart 3: per-command latency — the deeper analysis costs a few microseconds,
153
+ // which is negligible for a tool-call guard. Shown for honesty, not as a "win".
154
+ writeFileSync(
155
+ join(outDir, "latency.svg"),
156
+ horizontalBarChart({
157
+ title: "Per-command analysis latency",
158
+ subtitle: "Microseconds to classify one command. Both are negligible for a tool-call guard.",
159
+ unit: " µs",
160
+ max: Math.max(legacyUs, currentUs) * 1.4,
161
+ rows: [
162
+ { label: "Legacy regex guard", value: legacyUs, display: `${legacyUs.toFixed(2)} µs`, color: "#9aa0a6" },
163
+ { label: "Goal Mode analyzer", value: currentUs, display: `${currentUs.toFixed(2)} µs`, color: "#2da44e" },
164
+ ],
165
+ }),
166
+ );
167
+
168
+ writeFileSync(
169
+ join(outDir, "truthfulness-score.svg"),
170
+ horizontalBarChart({
171
+ title: "Benchmark Truthfulness Score",
172
+ subtitle: `False Completion Dataset: ${truthfulness.corpusSize} labeled completion-claim cases.`,
173
+ unit: "%",
174
+ max: 100,
175
+ rows: [
176
+ { label: "Truthfulness score", value: truthfulness.score, display: `${truthfulness.score.toFixed(1)}%`, color: "#2da44e" },
177
+ { label: "Decision accuracy", value: truthfulness.decisionAccuracy, display: `${truthfulness.decisionAccuracy.toFixed(1)}%`, color: "#0969da" },
178
+ { label: "Reason accuracy", value: truthfulness.reasonAccuracy, display: `${truthfulness.reasonAccuracy.toFixed(1)}%`, color: "#bf8700" },
179
+ ],
180
+ }),
181
+ );
182
+
183
+ const pct = (n) => `${n.toFixed(1)}%`;
184
+ console.log("Goal Mode shell-guard benchmark");
185
+ console.log("================================");
186
+ console.log(`Corpus: ${results.corpusSize} commands (${results.destructiveCount} destructive, ${results.safeCount} safe)`);
187
+ console.log("");
188
+ console.log(`Detection rate legacy ${pct(legacyEval.detectionRate)} → Goal Mode ${pct(currentEval.detectionRate)}`);
189
+ console.log(`False positives legacy ${pct(legacyEval.falsePositiveRate)} → Goal Mode ${pct(currentEval.falsePositiveRate)}`);
190
+ console.log(`Latency legacy ${legacyUs.toFixed(2)} µs/cmd → Goal Mode ${currentUs.toFixed(2)} µs/cmd (${fmt(currentOps)}/s)`);
191
+ console.log(`Truthfulness False Completion Dataset score ${truthfulness.score.toFixed(1)}% (${truthfulness.corpusSize} cases)`);
192
+ console.log("");
193
+ console.log("By family (detection rate):");
194
+ for (const f of detFamilies) {
195
+ console.log(` ${FAMILY_LABELS[f].padEnd(12)} legacy ${pct(familyRate(legacyEval, f)).padStart(6)} → Goal Mode ${pct(familyRate(currentEval, f)).padStart(6)}`);
196
+ }
197
+ console.log("");
198
+ console.log(`Wrote results.json + 4 SVG charts to docs/benchmarks/`);
@@ -0,0 +1,64 @@
1
+ import { BASE_GATES } from "../plugins/goal-guard/agents.js";
2
+ import { DEFAULT_CONFIG } from "../plugins/goal-guard/config.js";
3
+ import { evaluateCompletionClaim } from "../plugins/goal-guard/completion.js";
4
+ import { refreshStickyGates } from "../plugins/goal-guard/gates.js";
5
+ import { createState } from "../plugins/goal-guard/state.js";
6
+ import { FALSE_COMPLETION_CORPUS } from "./completion-corpus.mjs";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ function buildState(def) {
10
+ const state = createState("2026-01-01T00:00:00.000Z");
11
+ Object.assign(state, def.state || {});
12
+ state.latestVerdict = {};
13
+ for (const gate of def.state?.verdicts || []) {
14
+ state.latestVerdict[gate.agent] = { verdict: gate.verdict, at: "2026-01-01T00:00:01.000Z", seq: gate.seq };
15
+ }
16
+ if (!state.goalText && def.state?.contextualGate) state.goalText = def.state.contextualGate;
17
+ refreshStickyGates(state);
18
+ return state;
19
+ }
20
+
21
+ export function evaluateTruthfulnessCase(def) {
22
+ const config = { ...DEFAULT_CONFIG, ...(def.config || {}) };
23
+ const decision = evaluateCompletionClaim(buildState(def), config, def.text);
24
+ const reason = decision.reason || "";
25
+ const decisionCorrect = Boolean(decision.blocked) === Boolean(def.expected.blocked);
26
+ const reasonCorrect = !def.expected.blocked || reason.includes(def.expected.reasonIncludes || "");
27
+ return {
28
+ id: def.id,
29
+ family: def.family,
30
+ expectedBlocked: Boolean(def.expected.blocked),
31
+ actualBlocked: Boolean(decision.blocked),
32
+ decisionCorrect,
33
+ reasonCorrect,
34
+ reason,
35
+ };
36
+ }
37
+
38
+ export function runTruthfulnessBenchmark(corpus = FALSE_COMPLETION_CORPUS) {
39
+ const cases = corpus.map(evaluateTruthfulnessCase);
40
+ const falseCompletionCases = cases.filter((c) => c.expectedBlocked);
41
+ const trueCompletionCases = cases.filter((c) => !c.expectedBlocked);
42
+ const decisionCorrect = cases.filter((c) => c.decisionCorrect).length;
43
+ const reasonCorrect = falseCompletionCases.filter((c) => c.reasonCorrect).length;
44
+ const falseCompletionBlocked = falseCompletionCases.filter((c) => c.actualBlocked).length;
45
+ const trueCompletionAllowed = trueCompletionCases.filter((c) => !c.actualBlocked).length;
46
+ const decisionAccuracy = cases.length ? (decisionCorrect / cases.length) * 100 : 0;
47
+ const reasonAccuracy = falseCompletionCases.length ? (reasonCorrect / falseCompletionCases.length) * 100 : 100;
48
+ return {
49
+ name: "False Completion Dataset",
50
+ corpusSize: cases.length,
51
+ requiredBaseGates: BASE_GATES,
52
+ score: Number(((decisionAccuracy * 0.65 + reasonAccuracy * 0.35)).toFixed(1)),
53
+ decisionAccuracy: Number(decisionAccuracy.toFixed(1)),
54
+ reasonAccuracy: Number(reasonAccuracy.toFixed(1)),
55
+ falseCompletionBlockRate: Number(((falseCompletionBlocked / falseCompletionCases.length) * 100).toFixed(1)),
56
+ validCompletionAllowRate: Number(((trueCompletionAllowed / trueCompletionCases.length) * 100).toFixed(1)),
57
+ cases,
58
+ };
59
+ }
60
+
61
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
62
+ const result = runTruthfulnessBenchmark();
63
+ console.log(JSON.stringify(result, null, 2));
64
+ }
@@ -0,0 +1,27 @@
1
+ ---
2
+ description: Map Goal Contract acceptance criteria to recorded verification evidence and gaps.
3
+ agent: goal
4
+ ---
5
+
6
+ Produce a read-only evidence map for the current Goal Mode session. Do not edit files.
7
+
8
+ Call `goal_evidence_map` first and use its authoritative Goal Guard state,
9
+ including the Goal Contract, recorded evidence, dirty state, reviewer status, and
10
+ any user-provided context. Report unknown or missing details honestly instead of
11
+ inferring evidence that is not recorded.
12
+
13
+ Include:
14
+
15
+ - Acceptance criterion
16
+ - Recorded evidence covering it
17
+ - Reviewer status
18
+ - Verification command/result summary
19
+ - Status: covered, partially covered, missing, or stale
20
+ - Gap or risk
21
+ - Next required action
22
+
23
+ Additional context:
24
+
25
+ ```text
26
+ $ARGUMENTS
27
+ ```
@@ -0,0 +1,86 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="760" height="496" viewBox="0 0 760 496" font-family="-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif">
2
+ <rect width="760" height="496" fill="#ffffff"/>
3
+ <text x="20" y="28" font-size="17" font-weight="700" fill="#1f2328">Mechanically-enforced goal discipline</text>
4
+ <text x="20" y="47" font-size="12" fill="#656d76">Enforced = guaranteed by the harness; Prompt-only / Partial = depends on the model or user config.</text>
5
+ <text x="374.0" y="62" font-size="12.5" font-weight="700" text-anchor="middle" fill="#1f2328">Goal Mode</text>
6
+ <text x="522.0" y="62" font-size="12.5" font-weight="700" text-anchor="middle" fill="#1f2328">Claude Code</text>
7
+ <text x="670.0" y="62" font-size="12.5" font-weight="700" text-anchor="middle" fill="#1f2328">Codex</text>
8
+ <text x="286" y="93" font-size="12" text-anchor="end" fill="#1f2328">Autonomous goal loop</text>
9
+ <rect x="304.0" y="74" width="140.0" height="30" rx="4" fill="#dbe9d5"/>
10
+ <text x="374.0" y="93" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Prompt-only</text>
11
+ <rect x="452.0" y="74" width="140.0" height="30" rx="4" fill="#d4a72c"/>
12
+ <text x="522.0" y="93" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Partial</text>
13
+ <rect x="600.0" y="74" width="140.0" height="30" rx="4" fill="#d4a72c"/>
14
+ <text x="670.0" y="93" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Partial</text>
15
+ <text x="286" y="131" font-size="12" text-anchor="end" fill="#1f2328">Review gate before “done”</text>
16
+ <rect x="304.0" y="112" width="140.0" height="30" rx="4" fill="#2da44e"/>
17
+ <text x="374.0" y="131" font-size="11" font-weight="600" text-anchor="middle" fill="#ffffff">Enforced</text>
18
+ <rect x="452.0" y="112" width="140.0" height="30" rx="4" fill="#d4a72c"/>
19
+ <text x="522.0" y="131" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Partial</text>
20
+ <rect x="600.0" y="112" width="140.0" height="30" rx="4" fill="#dbe9d5"/>
21
+ <text x="670.0" y="131" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Prompt-only</text>
22
+ <text x="286" y="169" font-size="12" text-anchor="end" fill="#1f2328">Contextual specialist reviews</text>
23
+ <rect x="304.0" y="150" width="140.0" height="30" rx="4" fill="#2da44e"/>
24
+ <text x="374.0" y="169" font-size="11" font-weight="600" text-anchor="middle" fill="#ffffff">Enforced</text>
25
+ <rect x="452.0" y="150" width="140.0" height="30" rx="4" fill="#dbe9d5"/>
26
+ <text x="522.0" y="169" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Prompt-only</text>
27
+ <rect x="600.0" y="150" width="140.0" height="30" rx="4" fill="#dbe9d5"/>
28
+ <text x="670.0" y="169" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Prompt-only</text>
29
+ <text x="286" y="207" font-size="12" text-anchor="end" fill="#1f2328">Stale-review invalidation on edit</text>
30
+ <rect x="304.0" y="188" width="140.0" height="30" rx="4" fill="#2da44e"/>
31
+ <text x="374.0" y="207" font-size="11" font-weight="600" text-anchor="middle" fill="#ffffff">Enforced</text>
32
+ <rect x="452.0" y="188" width="140.0" height="30" rx="4" fill="#eaeef2"/>
33
+ <text x="522.0" y="207" font-size="11" font-weight="600" text-anchor="middle" fill="#656d76">None</text>
34
+ <rect x="600.0" y="188" width="140.0" height="30" rx="4" fill="#eaeef2"/>
35
+ <text x="670.0" y="207" font-size="11" font-weight="600" text-anchor="middle" fill="#656d76">None</text>
36
+ <text x="286" y="245" font-size="12" text-anchor="end" fill="#1f2328">Completion-claim enforcement</text>
37
+ <rect x="304.0" y="226" width="140.0" height="30" rx="4" fill="#2da44e"/>
38
+ <text x="374.0" y="245" font-size="11" font-weight="600" text-anchor="middle" fill="#ffffff">Enforced</text>
39
+ <rect x="452.0" y="226" width="140.0" height="30" rx="4" fill="#d4a72c"/>
40
+ <text x="522.0" y="245" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Partial</text>
41
+ <rect x="600.0" y="226" width="140.0" height="30" rx="4" fill="#eaeef2"/>
42
+ <text x="670.0" y="245" font-size="11" font-weight="600" text-anchor="middle" fill="#656d76">None</text>
43
+ <text x="286" y="283" font-size="12" text-anchor="end" fill="#1f2328">Destructive-command blocking</text>
44
+ <rect x="304.0" y="264" width="140.0" height="30" rx="4" fill="#2da44e"/>
45
+ <text x="374.0" y="283" font-size="11" font-weight="600" text-anchor="middle" fill="#ffffff">Enforced</text>
46
+ <rect x="452.0" y="264" width="140.0" height="30" rx="4" fill="#d4a72c"/>
47
+ <text x="522.0" y="283" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Partial</text>
48
+ <rect x="600.0" y="264" width="140.0" height="30" rx="4" fill="#d4a72c"/>
49
+ <text x="670.0" y="283" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Partial</text>
50
+ <text x="286" y="321" font-size="12" text-anchor="end" fill="#1f2328">Remote-exec (curl | sh) blocking</text>
51
+ <rect x="304.0" y="302" width="140.0" height="30" rx="4" fill="#2da44e"/>
52
+ <text x="374.0" y="321" font-size="11" font-weight="600" text-anchor="middle" fill="#ffffff">Enforced</text>
53
+ <rect x="452.0" y="302" width="140.0" height="30" rx="4" fill="#d4a72c"/>
54
+ <text x="522.0" y="321" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Partial</text>
55
+ <rect x="600.0" y="302" width="140.0" height="30" rx="4" fill="#d4a72c"/>
56
+ <text x="670.0" y="321" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Partial</text>
57
+ <text x="286" y="359" font-size="12" text-anchor="end" fill="#1f2328">Enforcement state survives restart</text>
58
+ <rect x="304.0" y="340" width="140.0" height="30" rx="4" fill="#2da44e"/>
59
+ <text x="374.0" y="359" font-size="11" font-weight="600" text-anchor="middle" fill="#ffffff">Enforced</text>
60
+ <rect x="452.0" y="340" width="140.0" height="30" rx="4" fill="#d4a72c"/>
61
+ <text x="522.0" y="359" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Partial</text>
62
+ <rect x="600.0" y="340" width="140.0" height="30" rx="4" fill="#d4a72c"/>
63
+ <text x="670.0" y="359" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Partial</text>
64
+ <text x="286" y="397" font-size="12" text-anchor="end" fill="#1f2328">State survives compaction</text>
65
+ <rect x="304.0" y="378" width="140.0" height="30" rx="4" fill="#2da44e"/>
66
+ <text x="374.0" y="397" font-size="11" font-weight="600" text-anchor="middle" fill="#ffffff">Enforced</text>
67
+ <rect x="452.0" y="378" width="140.0" height="30" rx="4" fill="#d4a72c"/>
68
+ <text x="522.0" y="397" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Partial</text>
69
+ <rect x="600.0" y="378" width="140.0" height="30" rx="4" fill="#d4a72c"/>
70
+ <text x="670.0" y="397" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Partial</text>
71
+ <text x="286" y="435" font-size="12" text-anchor="end" fill="#1f2328">Custom enforcement hooks/tools</text>
72
+ <rect x="304.0" y="416" width="140.0" height="30" rx="4" fill="#2da44e"/>
73
+ <text x="374.0" y="435" font-size="11" font-weight="600" text-anchor="middle" fill="#ffffff">Enforced</text>
74
+ <rect x="452.0" y="416" width="140.0" height="30" rx="4" fill="#2da44e"/>
75
+ <text x="522.0" y="435" font-size="11" font-weight="600" text-anchor="middle" fill="#ffffff">Enforced</text>
76
+ <rect x="600.0" y="416" width="140.0" height="30" rx="4" fill="#d4a72c"/>
77
+ <text x="670.0" y="435" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">Partial</text>
78
+ <rect x="286" y="461" width="12" height="12" rx="2" fill="#2da44e"/>
79
+ <text x="303" y="472" font-size="11.5" fill="#1f2328">Enforced</text>
80
+ <rect x="372" y="461" width="12" height="12" rx="2" fill="#d4a72c"/>
81
+ <text x="389" y="472" font-size="11.5" fill="#1f2328">Partial</text>
82
+ <rect x="451" y="461" width="12" height="12" rx="2" fill="#dbe9d5"/>
83
+ <text x="468" y="472" font-size="11.5" fill="#1f2328">Prompt-only</text>
84
+ <rect x="558" y="461" width="12" height="12" rx="2" fill="#eaeef2"/>
85
+ <text x="575" y="472" font-size="11.5" fill="#1f2328">None</text>
86
+ </svg>
@@ -0,0 +1,37 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="720" height="380" viewBox="0 0 720 380" font-family="-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif">
2
+ <rect width="720" height="380" fill="#ffffff"/>
3
+ <text x="48" y="28" font-size="17" font-weight="700" fill="#1f2328">Destructive-command detection rate by family</text>
4
+ <text x="48" y="47" font-size="12" fill="#656d76">Higher is better. Corpus: 48 destructive commands.</text>
5
+ <line x1="48" y1="296.0" x2="700" y2="296.0" stroke="#eaeef2" stroke-width="1"/>
6
+ <text x="40" y="300.0" font-size="11" text-anchor="end" fill="#656d76">0%</text>
7
+ <line x1="48" y1="249.6" x2="700" y2="249.6" stroke="#eaeef2" stroke-width="1"/>
8
+ <text x="40" y="253.6" font-size="11" text-anchor="end" fill="#656d76">20%</text>
9
+ <line x1="48" y1="203.2" x2="700" y2="203.2" stroke="#eaeef2" stroke-width="1"/>
10
+ <text x="40" y="207.2" font-size="11" text-anchor="end" fill="#656d76">40%</text>
11
+ <line x1="48" y1="156.8" x2="700" y2="156.8" stroke="#eaeef2" stroke-width="1"/>
12
+ <text x="40" y="160.8" font-size="11" text-anchor="end" fill="#656d76">60%</text>
13
+ <line x1="48" y1="110.4" x2="700" y2="110.4" stroke="#eaeef2" stroke-width="1"/>
14
+ <text x="40" y="114.4" font-size="11" text-anchor="end" fill="#656d76">80%</text>
15
+ <line x1="48" y1="64.0" x2="700" y2="64.0" stroke="#eaeef2" stroke-width="1"/>
16
+ <text x="40" y="68.0" font-size="11" text-anchor="end" fill="#656d76">100%</text>
17
+ <rect x="56.0" y="64.0" width="96.7" height="232.0" rx="3" fill="#9aa0a6"/>
18
+ <text x="104.3" y="59.0" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">100%</text>
19
+ <rect x="160.7" y="64.0" width="96.7" height="232.0" rx="3" fill="#2da44e"/>
20
+ <text x="209.0" y="59.0" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">100%</text>
21
+ <text x="156.7" y="314.0" font-size="11" text-anchor="middle" fill="#1f2328">Classic</text>
22
+ <rect x="273.3" y="296.0" width="96.7" height="0.0" rx="3" fill="#9aa0a6"/>
23
+ <text x="321.7" y="291.0" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">0%</text>
24
+ <rect x="378.0" y="64.0" width="96.7" height="232.0" rx="3" fill="#2da44e"/>
25
+ <text x="426.3" y="59.0" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">100%</text>
26
+ <text x="374.0" y="314.0" font-size="11" text-anchor="middle" fill="#1f2328">Obfuscated</text>
27
+ <rect x="490.7" y="296.0" width="96.7" height="0.0" rx="3" fill="#9aa0a6"/>
28
+ <text x="539.0" y="291.0" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">0%</text>
29
+ <rect x="595.3" y="64.0" width="96.7" height="232.0" rx="3" fill="#2da44e"/>
30
+ <text x="643.7" y="59.0" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">100%</text>
31
+ <text x="591.3" y="314.0" font-size="11" text-anchor="middle" fill="#1f2328">Remote exec</text>
32
+ <line x1="48" y1="296" x2="700" y2="296" stroke="#d0d7de" stroke-width="1.5"/>
33
+ <rect x="48" y="344" width="12" height="12" rx="2" fill="#9aa0a6"/>
34
+ <text x="66" y="354" font-size="12" fill="#1f2328">Legacy regex guard</text>
35
+ <rect x="201.6" y="344" width="12" height="12" rx="2" fill="#2da44e"/>
36
+ <text x="219.6" y="354" font-size="12" fill="#1f2328">Goal Mode analyzer</text>
37
+ </svg>
@@ -0,0 +1,13 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="720" height="164" viewBox="0 0 720 164" font-family="-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif">
2
+ <rect width="720" height="164" fill="#ffffff"/>
3
+ <text x="20" y="28" font-size="17" font-weight="700" fill="#1f2328">Per-command analysis latency</text>
4
+ <text x="20" y="47" font-size="12" fill="#656d76">Microseconds to classify one command. Both are negligible for a tool-call guard.</text>
5
+ <text x="218" y="87" font-size="12" text-anchor="end" fill="#1f2328">Legacy regex guard</text>
6
+ <rect x="230" y="70" width="420" height="22" rx="3" fill="#eaeef2"/>
7
+ <rect x="230" y="70" width="179.4" height="22" rx="3" fill="#9aa0a6"/>
8
+ <text x="417.4" y="87" font-size="12" font-weight="600" fill="#1f2328">2.24 µs</text>
9
+ <text x="218" y="125" font-size="12" text-anchor="end" fill="#1f2328">Goal Mode analyzer</text>
10
+ <rect x="230" y="108" width="420" height="22" rx="3" fill="#eaeef2"/>
11
+ <rect x="230" y="108" width="300.0" height="22" rx="3" fill="#2da44e"/>
12
+ <text x="538.0" y="125" font-size="12" font-weight="600" fill="#1f2328">3.75 µs</text>
13
+ </svg>
@@ -0,0 +1,32 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="720" height="380" viewBox="0 0 720 380" font-family="-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif">
2
+ <rect width="720" height="380" fill="#ffffff"/>
3
+ <text x="48" y="28" font-size="17" font-weight="700" fill="#1f2328">Overall guard accuracy</text>
4
+ <text x="48" y="47" font-size="12" fill="#656d76">Detection rate (higher better) vs false-positive rate (lower better).</text>
5
+ <line x1="48" y1="296.0" x2="700" y2="296.0" stroke="#eaeef2" stroke-width="1"/>
6
+ <text x="40" y="300.0" font-size="11" text-anchor="end" fill="#656d76">0%</text>
7
+ <line x1="48" y1="249.6" x2="700" y2="249.6" stroke="#eaeef2" stroke-width="1"/>
8
+ <text x="40" y="253.6" font-size="11" text-anchor="end" fill="#656d76">20%</text>
9
+ <line x1="48" y1="203.2" x2="700" y2="203.2" stroke="#eaeef2" stroke-width="1"/>
10
+ <text x="40" y="207.2" font-size="11" text-anchor="end" fill="#656d76">40%</text>
11
+ <line x1="48" y1="156.8" x2="700" y2="156.8" stroke="#eaeef2" stroke-width="1"/>
12
+ <text x="40" y="160.8" font-size="11" text-anchor="end" fill="#656d76">60%</text>
13
+ <line x1="48" y1="110.4" x2="700" y2="110.4" stroke="#eaeef2" stroke-width="1"/>
14
+ <text x="40" y="114.4" font-size="11" text-anchor="end" fill="#656d76">80%</text>
15
+ <line x1="48" y1="64.0" x2="700" y2="64.0" stroke="#eaeef2" stroke-width="1"/>
16
+ <text x="40" y="68.0" font-size="11" text-anchor="end" fill="#656d76">100%</text>
17
+ <rect x="56.0" y="247.7" width="151.0" height="48.3" rx="3" fill="#9aa0a6"/>
18
+ <text x="131.5" y="242.7" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">21%</text>
19
+ <rect x="215.0" y="64.0" width="151.0" height="232.0" rx="3" fill="#2da44e"/>
20
+ <text x="290.5" y="59.0" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">100%</text>
21
+ <text x="211.0" y="314.0" font-size="11" text-anchor="middle" fill="#1f2328">Detection rate</text>
22
+ <rect x="382.0" y="245.6" width="151.0" height="50.4" rx="3" fill="#9aa0a6"/>
23
+ <text x="457.5" y="240.6" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">22%</text>
24
+ <rect x="541.0" y="296.0" width="151.0" height="0.0" rx="3" fill="#2da44e"/>
25
+ <text x="616.5" y="291.0" font-size="11" font-weight="600" text-anchor="middle" fill="#1f2328">0%</text>
26
+ <text x="537.0" y="314.0" font-size="11" text-anchor="middle" fill="#1f2328">False-positive rate</text>
27
+ <line x1="48" y1="296" x2="700" y2="296" stroke="#d0d7de" stroke-width="1.5"/>
28
+ <rect x="48" y="344" width="12" height="12" rx="2" fill="#9aa0a6"/>
29
+ <text x="66" y="354" font-size="12" fill="#1f2328">Legacy regex guard</text>
30
+ <rect x="201.6" y="344" width="12" height="12" rx="2" fill="#2da44e"/>
31
+ <text x="219.6" y="354" font-size="12" fill="#1f2328">Goal Mode analyzer</text>
32
+ </svg>