diff --git a/metainfer/tasks/evolve_kernel/server/_state_readers.py b/metainfer/tasks/evolve_kernel/server/_state_readers.py index 27effcb6..17f28164 100644 --- a/metainfer/tasks/evolve_kernel/server/_state_readers.py +++ b/metainfer/tasks/evolve_kernel/server/_state_readers.py @@ -5,6 +5,7 @@ from __future__ import annotations +import difflib import json from pathlib import Path from typing import Any, Dict, List, Optional @@ -213,3 +214,94 @@ def read_reference_kernel(workspace_dir: Path) -> Dict[str, Any]: "path": str(path), "lines": len(code.splitlines()), } + + +# --------------------------------------------------------------------------- # +# Kernel source & diff +# --------------------------------------------------------------------------- # + +# Metadata carried alongside a single kernel's source (the fields the diff +# view needs to explain a change: how fast it was, which iteration added it, +# and which library kernel it was derived from). +_KERNEL_META_KEYS = ( + "exec_time_ms", + "complexity_score", + "combined_score", + "iteration_added", + "parent_id", +) + + +def read_kernel_source(workspace_dir: Path, kernel_id: str) -> Dict[str, Any]: + """Return the full source + metadata for one library kernel, by id. + + ``read_kernel_library`` ships every kernel's code in one payload; this + resolves a single kernel so callers that already know the id (the diff + view) don't have to scan the list, and so the lookup has one definition. + """ + for k in read_kernel_library(workspace_dir)["kernels"]: + if str(k.get("id")) == str(kernel_id): + code = k.get("code") or "" + return { + "exists": True, + "id": k.get("id"), + "code": code, + "lines": len(code.splitlines()), + "meta": {key: k.get(key) for key in _KERNEL_META_KEYS}, + } + return {"exists": False, "id": kernel_id, "code": "", "lines": 0, "meta": {}} + + +def read_kernel_diff(workspace_dir: Path, kernel_id: str, + base: str = "reference") -> Dict[str, Any]: + """Unified diff of a library kernel against a baseline. + + ``base`` is ``"reference"`` (the original kernel the task started from) + or ``"parent"`` (the library kernel this one was derived from). Returns + the diff text plus added/removed line counts; a missing kernel, a root + kernel with no parent, or a parent that is no longer in the library + (the library evicts past ``MAX_LIBRARY_SIZE``) yields ``exists: False`` + with an ``error`` reason rather than raising. + """ + src = read_kernel_source(workspace_dir, kernel_id) + if not src["exists"]: + return _diff_missing(base, f"unknown kernel {kernel_id!r}") + + if base == "parent": + parent_id = src["meta"].get("parent_id") + if not parent_id: + return _diff_missing(base, "kernel has no parent") + base_src = read_kernel_source(workspace_dir, parent_id) + if not base_src["exists"]: + return _diff_missing( + base, f"parent {str(parent_id)[:8]} is no longer in the library") + base_code = base_src["code"] + base_label = f"kernel {str(parent_id)[:8]}" + else: + ref = read_reference_kernel(workspace_dir) + base_code = ref["code"] if ref["exists"] else "" + base_label = "reference" + + diff_lines = list(difflib.unified_diff( + base_code.splitlines(), (src["code"] or "").splitlines(), + fromfile=f"a/{base_label}", + tofile=f"b/kernel {str(kernel_id)[:8]}", + lineterm="", + )) + added = sum(1 for ln in diff_lines + if ln.startswith("+") and not ln.startswith("+++")) + removed = sum(1 for ln in diff_lines + if ln.startswith("-") and not ln.startswith("---")) + return { + "exists": True, + "base": base, + "base_label": base_label, + "diff": "\n".join(diff_lines), + "added": added, + "removed": removed, + } + + +def _diff_missing(base: str, error: str) -> Dict[str, Any]: + return {"exists": False, "base": base, "base_label": base, + "diff": "", "added": 0, "removed": 0, "error": error} diff --git a/metainfer/tasks/evolve_kernel/server/routes.py b/metainfer/tasks/evolve_kernel/server/routes.py index 82b91d58..07cf05a9 100644 --- a/metainfer/tasks/evolve_kernel/server/routes.py +++ b/metainfer/tasks/evolve_kernel/server/routes.py @@ -95,6 +95,16 @@ def ok_reference_kernel(task_id: str) -> Dict[str, Any]: require_task_type(entry, PLUGIN_TYPE) return _state_readers.read_reference_kernel(workspace_dir_for(entry)) + # ---- Kernel diff ---- + + @router.get("/kernels/{kernel_id}/diff") + def ok_kernel_diff(task_id: str, kernel_id: str, + base: str = "reference") -> Dict[str, Any]: + entry = task_or_404(task_id) + require_task_type(entry, PLUGIN_TYPE) + return _state_readers.read_kernel_diff( + workspace_dir_for(entry), kernel_id, base) + # ---- QA ---- register_qa_routes(router, plugin, prefix="/qa") diff --git a/metainfer/tasks/evolve_kernel/static/ok-detail.js b/metainfer/tasks/evolve_kernel/static/ok-detail.js index 9e316db0..0770a0a3 100644 --- a/metainfer/tasks/evolve_kernel/static/ok-detail.js +++ b/metainfer/tasks/evolve_kernel/static/ok-detail.js @@ -15,7 +15,7 @@ import { Timeline } from "app/timeline"; import { getIterations, getCharts, getStateGraph, getKernelLibrary, getCorrectnessHarness, getPerfHarness, - getReferenceKernel, + getReferenceKernel, getKernelDiff, } from "app/ok-runtime-api"; const withTimeout = (p, ms = 8000) => @@ -140,6 +140,101 @@ function HarnessStatus({ harness, label }) { return html`${label}: not generated`; } +// ---- Kernel source / diff inspector ---- + +// Line-numbered source rendering: the raw string in a
 gives no way to
+// cite a line, so emit one 
per line with a gutter number. +function SourceLines({ code }) { + const lines = (code || "").split("\n"); + return html`
+ ${lines.map((ln, i) => html` +
+ ${i + 1} + ${ln} +
+ `)} +
`; +} + +// Unified-diff rendering: classify each line by prefix so additions and +// deletions stand out; hunk headers (@@) get their own muted styling. +function DiffLines({ diff }) { + const lines = (diff || "").split("\n"); + if (!lines.some((l) => l.length > 0)) { + return html`
No differences.
`; + } + return html`
+ ${lines.map((ln, i) => { + let cls = "ok-diff-ctx"; + if (ln.startsWith("+++") || ln.startsWith("---")) cls = "ok-diff-meta"; + else if (ln.startsWith("@@")) cls = "ok-diff-hunk"; + else if (ln.startsWith("+")) cls = "ok-diff-add"; + else if (ln.startsWith("-")) cls = "ok-diff-del"; + return html`
${ln || " "}
`; + })} +
`; +} + +const DIFF_MODES = [ + { key: "source", label: "Source" }, + { key: "reference", label: "Diff vs reference" }, + { key: "parent", label: "Diff vs parent" }, +]; + +function KernelInspector({ taskId, kernel, onClose }) { + const [mode, setMode] = useState("source"); + const [diff, setDiff] = useState(null); + const [diffErr, setDiffErr] = useState(null); + + useEffect(() => { + if (mode === "source" || !taskId || !kernel) return; + let cancelled = false; + setDiff(null); + setDiffErr(null); + withTimeout(getKernelDiff(taskId, kernel.id, mode)) + .then((d) => { if (!cancelled) setDiff(d); }) + .catch((e) => { if (!cancelled) setDiffErr(String(e.message || e)); }); + return () => { cancelled = true; }; + }, [taskId, kernel && kernel.id, mode]); + + const code = kernel.code || kernel.code_preview || ""; + const parentId = kernel.parent_id ? String(kernel.parent_id).slice(0, 8) : null; + + return html`
+

Kernel: ${String(kernel.id).slice(0, 8)}… + +

+
+
+ ${DIFF_MODES.map((m) => html` + + `)} +
+ ${mode !== "source" && diff && diff.exists ? html` + + +${diff.added} + −${diff.removed} + vs ${diff.base_label} + + ` : null} +
+ + ${mode === "source" + ? html`<${SourceLines} code=${code} />` + : diffErr + ? html`
Failed to load diff: ${diffErr}
` + : !diff + ? html`
Loading diff…
` + : !diff.exists + ? html`
${diff.error || "No diff available."}
` + : html`<${DiffLines} diff=${diff.diff} />`} +
`; +} + // ---- Main view ---- export default function OptKernelDetailView({ @@ -185,12 +280,10 @@ export default function OptKernelDetailView({
${selectedKernel ? html` -
-

Kernel: ${selectedKernel.id.slice(0, 8)}… - -

-
${selectedKernel.code || selectedKernel.code_preview || "Code not available"}
-
+ <${KernelInspector} + taskId=${taskId} + kernel=${selectedKernel} + onClose=${() => setSelectedKernel(null)} /> ` : null}
diff --git a/metainfer/tasks/evolve_kernel/static/ok-runtime-api.js b/metainfer/tasks/evolve_kernel/static/ok-runtime-api.js index 8f572de0..f2ef898a 100644 --- a/metainfer/tasks/evolve_kernel/static/ok-runtime-api.js +++ b/metainfer/tasks/evolve_kernel/static/ok-runtime-api.js @@ -51,6 +51,14 @@ export async function getReferenceKernel(taskId) { return res.json(); } +export async function getKernelDiff(taskId, kernelId, base = "reference") { + const res = await fetch( + `${BASE(taskId)}/kernels/${encodeURIComponent(kernelId)}/diff` + + `?base=${encodeURIComponent(base)}`); + if (!res.ok) throw new Error(`kernel diff: ${res.status}`); + return res.json(); +} + export async function getRetrospective(taskId, n) { const res = await fetch(`${BASE(taskId)}/iterations/${n}/retrospective`); if (!res.ok) throw new Error(`retrospective ${n}: ${res.status}`); diff --git a/metainfer/tasks/evolve_kernel/static/ok.css b/metainfer/tasks/evolve_kernel/static/ok.css index 8b4988c2..2ce837b0 100644 --- a/metainfer/tasks/evolve_kernel/static/ok.css +++ b/metainfer/tasks/evolve_kernel/static/ok.css @@ -162,3 +162,107 @@ color: var(--muted, #8b949e); margin-top: 0.25rem; } + +/* ---- Kernel source / diff inspector ---- */ + +.ok-inspector-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + margin-bottom: 0.75rem; + flex-wrap: wrap; +} + +.ok-inspector-modes { + display: flex; + gap: 0.4rem; +} + +.ok-inspector-modes .btn.active { + background: var(--accent-bg, rgba(88, 166, 255, 0.15)); + color: var(--accent, #58a6ff); + border-color: var(--accent, #58a6ff); +} + +.ok-diff-stat { + display: inline-flex; + align-items: center; + gap: 0.4rem; + font-size: 0.8rem; + font-family: monospace; +} + +.ok-diff-stat-add { + color: #3fb950; + font-weight: 600; +} + +.ok-diff-stat-del { + color: #f85149; + font-weight: 600; +} + +.ok-source, +.ok-diff { + max-height: 600px; + overflow: auto; + background: var(--code-bg, #0d1117); + border: 1px solid var(--border-color, #30363d); + border-radius: 6px; + font-family: monospace; + font-size: 0.8rem; + line-height: 1.45; +} + +.ok-diff.empty { + padding: 1rem; +} + +.ok-diff.empty.error { + color: #f85149; +} + +.ok-source-row, +.ok-diff-row { + display: flex; + white-space: pre; +} + +.ok-source-num { + flex: 0 0 auto; + width: 3.5rem; + padding: 0 0.6rem; + text-align: right; + color: var(--muted, #8b949e); + user-select: none; + border-right: 1px solid var(--border-color, #30363d); +} + +.ok-source-line { + flex: 1 1 auto; + padding: 0 0.6rem; +} + +.ok-diff-row { + padding: 0 0.75rem; +} + +.ok-diff-add { + background: rgba(63, 185, 80, 0.15); + color: #3fb950; +} + +.ok-diff-del { + background: rgba(248, 81, 73, 0.15); + color: #f85149; +} + +.ok-diff-hunk { + color: var(--accent, #58a6ff); + background: var(--accent-bg, rgba(88, 166, 255, 0.08)); +} + +.ok-diff-meta { + color: var(--muted, #8b949e); +} diff --git a/metainfer/tasks/evolve_kernel/tests/test_kernel_diff.py b/metainfer/tasks/evolve_kernel/tests/test_kernel_diff.py new file mode 100644 index 00000000..ed235404 --- /dev/null +++ b/metainfer/tasks/evolve_kernel/tests/test_kernel_diff.py @@ -0,0 +1,189 @@ +"""Unit tests for the kernel source/diff readers in server/_state_readers.py. + +Covers ``read_kernel_source`` (resolve one library kernel by id) and +``read_kernel_diff`` (unified diff against the reference or a parent kernel), +including the non-raising failure paths the diff view depends on. +""" + +from __future__ import annotations + +import json +import tempfile +from pathlib import Path + +from metainfer.tasks.evolve_kernel.server._state_readers import ( + read_kernel_diff, + read_kernel_source, +) + +REF_CODE = "\n".join([ + "def kernel(x):", + " return x", +]) + +PARENT_CODE = "\n".join([ + "def kernel(x):", + " # parent tweak", + " return x", +]) + +CHILD_CODE = "\n".join([ + "def kernel(x):", + " # child tweak", + " return x * 2", +]) + + +def _write_library(ws: Path, kernels): + (ws / "kernel_library.json").write_text( + json.dumps(kernels, indent=2), encoding="utf-8") + + +def _write_reference(ws: Path, code: str): + ref_dir = ws / "reference" + ref_dir.mkdir(parents=True, exist_ok=True) + (ref_dir / "original_kernel.py").write_text(code, encoding="utf-8") + + +def _kernel(kid, code, parent_id=None, **meta): + entry = { + "id": kid, + "code": code, + "parent_id": parent_id, + "exec_time_ms": meta.get("exec_time_ms", 1.0), + "complexity_score": meta.get("complexity_score", 0.0), + "combined_score": meta.get("combined_score", 7.0), + "iteration_added": meta.get("iteration_added", 0), + } + return entry + + +# --------------------------------------------------------------------------- # +# read_kernel_source +# --------------------------------------------------------------------------- # + + +class TestReadKernelSource: + def test_finds_kernel_by_id(self): + with tempfile.TemporaryDirectory() as d: + ws = Path(d) + _write_library(ws, [ + _kernel("aaa", REF_CODE), + _kernel("bbb", CHILD_CODE, parent_id="aaa", + exec_time_ms=2.5, iteration_added=7), + ]) + src = read_kernel_source(ws, "bbb") + assert src["exists"] is True + assert src["id"] == "bbb" + assert src["code"] == CHILD_CODE + assert src["lines"] == len(CHILD_CODE.splitlines()) + assert src["meta"]["parent_id"] == "aaa" + assert src["meta"]["exec_time_ms"] == 2.5 + assert src["meta"]["iteration_added"] == 7 + + def test_matches_by_string_coercion(self): + """ids may be written as non-str (e.g. int); lookup must coerce.""" + with tempfile.TemporaryDirectory() as d: + ws = Path(d) + _write_library(ws, [{"id": 42, "code": REF_CODE}]) + src = read_kernel_source(ws, "42") + assert src["exists"] is True + assert src["code"] == REF_CODE + + def test_missing_kernel(self): + with tempfile.TemporaryDirectory() as d: + ws = Path(d) + _write_library(ws, [_kernel("aaa", REF_CODE)]) + src = read_kernel_source(ws, "nope") + assert src["exists"] is False + assert src["code"] == "" + assert src["meta"] == {} + + def test_no_library_file(self): + with tempfile.TemporaryDirectory() as d: + src = read_kernel_source(Path(d), "anything") + assert src["exists"] is False + + +# --------------------------------------------------------------------------- # +# read_kernel_diff +# --------------------------------------------------------------------------- # + + +class TestReadKernelDiff: + def test_diff_vs_reference(self): + with tempfile.TemporaryDirectory() as d: + ws = Path(d) + _write_reference(ws, REF_CODE) + _write_library(ws, [_kernel("child", CHILD_CODE)]) + diff = read_kernel_diff(ws, "child", base="reference") + assert diff["exists"] is True + assert diff["base"] == "reference" + assert diff["base_label"] == "reference" + assert diff["added"] == 2 # "# child tweak" and "return x * 2" + assert diff["removed"] == 1 # "return x" + assert "a/reference" in diff["diff"] + assert "b/kernel child" in diff["diff"] + + def test_diff_vs_parent(self): + with tempfile.TemporaryDirectory() as d: + ws = Path(d) + _write_reference(ws, REF_CODE) + _write_library(ws, [ + _kernel("parent1234", PARENT_CODE), + _kernel("child5678", CHILD_CODE, parent_id="parent1234"), + ]) + diff = read_kernel_diff(ws, "child5678", base="parent") + assert diff["exists"] is True + assert diff["base_label"] == "kernel parent12" + assert "a/kernel parent12" in diff["diff"] + + def test_root_kernel_has_no_parent(self): + with tempfile.TemporaryDirectory() as d: + ws = Path(d) + _write_reference(ws, REF_CODE) + _write_library(ws, [_kernel("root", REF_CODE)]) + diff = read_kernel_diff(ws, "root", base="parent") + assert diff["exists"] is False + assert diff["error"] == "kernel has no parent" + + def test_parent_evicted_from_library(self): + """A parent no longer present (evicted) must not raise.""" + with tempfile.TemporaryDirectory() as d: + ws = Path(d) + _write_reference(ws, REF_CODE) + _write_library(ws, [ + _kernel("child5678", CHILD_CODE, parent_id="gone0000"), + ]) + diff = read_kernel_diff(ws, "child5678", base="parent") + assert diff["exists"] is False + assert "no longer in the library" in diff["error"] + + def test_unknown_kernel(self): + with tempfile.TemporaryDirectory() as d: + ws = Path(d) + _write_reference(ws, REF_CODE) + _write_library(ws, [_kernel("aaa", REF_CODE)]) + diff = read_kernel_diff(ws, "ghost") + assert diff["exists"] is False + assert "unknown kernel" in diff["error"] + + def test_missing_reference_is_full_addition(self): + with tempfile.TemporaryDirectory() as d: + ws = Path(d) + _write_library(ws, [_kernel("child", CHILD_CODE)]) + diff = read_kernel_diff(ws, "child", base="reference") + assert diff["exists"] is True + assert diff["removed"] == 0 + assert diff["added"] == len(CHILD_CODE.splitlines()) + + def test_identical_code_yields_empty_body(self): + with tempfile.TemporaryDirectory() as d: + ws = Path(d) + _write_reference(ws, REF_CODE) + _write_library(ws, [_kernel("same", REF_CODE)]) + diff = read_kernel_diff(ws, "same", base="reference") + assert diff["exists"] is True + assert diff["added"] == 0 + assert diff["removed"] == 0 + assert diff["diff"] == ""