Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{
"name": "code-walkthrough",
"description": "code-walkthrough: turn a diff, branch, PR, or existing code into a top-down walkthrough page: overview, story map, call graph, annotated diff, and line comments you hand back to the agent",
"version": "1.6.0",
"version": "1.6.1",
"source": "./",
"author": {
"name": "crisperity"
Expand Down
6 changes: 6 additions & 0 deletions skills/code-walkthrough/scripts/cw_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
_CLAUDE_AUTH_MARKERS = ("not logged in", "unauthorized", "authentication required")
_CLAUDE_RATE_MARKERS = ("usage limit", "rate limit", "rate_limit")
_CLAUDE_CONTEXT_MARKERS = ("prompt is too long", "context length")
_CLAUDE_FLAGGED_MARKERS = ("safeguards flagged",)
_CLAUDE_FLAGGED_REMEDY = (
"the content of this batch was flagged by model safeguards; retrying or switching model will not help"
)

_semaphore_lock = threading.Lock()
_semaphore = threading.BoundedSemaphore(4)
Expand Down Expand Up @@ -260,6 +264,8 @@ def _classify_claude_error(parsed, stderr, profile):
return "rate", _CLAUDE_RATE_REMEDY
if any(marker in text for marker in _CLAUDE_CONTEXT_MARKERS):
return "context", f"this batch is too big for {profile.get('model')}: lower batch_max_lines"
if any(marker in text for marker in _CLAUDE_FLAGGED_MARKERS):
return "flagged", _CLAUDE_FLAGGED_REMEDY
if parsed.get("subtype") == "error_max_turns":
return "rounds", None
return "error", None
Expand Down
214 changes: 147 additions & 67 deletions skills/code-walkthrough/scripts/cw_run.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion skills/code-walkthrough/scripts/cw_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ def _tool_walkthrough_get(daemon, args):
result["meta"] = {
"target": meta.get("target"), "base": meta.get("base"), "head": meta.get("head"),
"route": meta.get("route"), "batches": meta.get("batches"),
"batches_done": meta.get("batches_done"), "steps": _steps_public(meta),
"batches_done": meta.get("batches_done"), "withheld": meta.get("withheld"), "steps": _steps_public(meta),
"usage": meta.get("usage", {}), "total": cw_store.usage_total(meta),
"created_at": meta.get("created_at"), "updated_at": meta.get("updated_at"), "dir": str(d),
}
Expand Down
1 change: 1 addition & 0 deletions skills/code-walkthrough/scripts/cw_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def strip_ref_fences(text):
"max_conversation_tokens": 200000,
"small_diff_lines": 1500,
"batch_max_lines": 400,
"batch_max_chars": 120000,
"trust": {"repos": [], "orgs": []},
"repos": {},
"port": 27183,
Expand Down
65 changes: 59 additions & 6 deletions skills/code-walkthrough/scripts/fanout.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Split raw.diff into per-file batches for parallel analysis, then merge the results.

python3 fanout.py split --diff raw.diff --out <dir> [--max-lines 400] [--max-batches 8]
python3 fanout.py split --diff raw.diff --out <dir> [--max-lines 400] [--max-chars 120000] [--max-batches 8]
python3 fanout.py merge --diff raw.diff --prose prose.json \
--fragments <dir>/fragment-1.json ... --out analysis.json

Expand Down Expand Up @@ -66,19 +66,71 @@ def split_chunks(text):
return [(order[i], text[bounds[i] : bounds[i + 1]]) for i in range(len(order))]


def batch(chunks, max_lines, max_batches):
STUB_NAME = re.compile(r"\.golden\.|\.snap$|\.lock$|(^|/)(package-lock\.json|pnpm-lock\.yaml)$")
STUB_LINE_CHARS = 2000
STUB_FILE_CHARS = 60000


def should_stub(path, chunk):
return (
bool(STUB_NAME.search(path))
or "GIT binary patch" in chunk
or re.search(r"^Binary files .* differ$", chunk, re.M) is not None
or len(chunk) > STUB_FILE_CHARS
or any(len(line) > STUB_LINE_CHARS for line in chunk.split("\n"))
)


def stub_chunk(chunk):
"""Keep the file header and every `@@` line, drop the hunk bodies: seed entries and
validation key on the headers."""
out, body, in_body = [], 0, False

def flush():
if body:
out.append(f"<{-(-body // 1000)} KB omitted: golden/generated/binary>\n")

for line in chunk.splitlines(keepends=True):
if line.startswith("@@"):
flush()
out.append(line)
body, in_body = 0, True
elif in_body or line.startswith("GIT binary patch"):
body, in_body = body + len(line), True
else:
out.append(line)
flush()
return "".join(out)


def stub_diff(text, extra=()):
"""The diff as a model should see it, plus the paths whose bodies were withheld."""
parts, withheld = [], set()
for path, chunk in split_chunks(text):
if path in extra or should_stub(path, chunk):
parts.append(stub_chunk(chunk))
withheld.add(path)
else:
parts.append(chunk)
return "".join(parts), withheld


def batch(chunks, max_lines, max_batches, max_chars=None):
"""Greedy contiguous fill. A file never straddles two batches: its notes read better
written against the whole file's diff."""
total = sum(chunk.count("\n") for _, chunk in chunks)
budget = max(max_lines, -(-total // max_batches)) if max_batches > 0 else max_lines
batches, current, current_lines = [], [], 0
batches, current, current_lines, current_chars = [], [], 0, 0
for path, chunk in chunks:
lines = chunk.count("\n")
if current and current_lines + lines > budget:
size = len(stub_chunk(chunk) if should_stub(path, chunk) else chunk)
if current and (current_lines + lines > budget
or (max_chars and current_chars + size > max_chars)):
batches.append(current)
current, current_lines = [], 0
current, current_lines, current_chars = [], 0, 0
current.append((path, chunk))
current_lines += lines
current_chars += size
if current:
batches.append(current)
return batches
Expand Down Expand Up @@ -109,7 +161,7 @@ def do_split(args):

_, diff_files = parse_hunks(text)
manifest = []
for i, group in enumerate(batch(split_chunks(text), args.max_lines, args.max_batches), 1):
for i, group in enumerate(batch(split_chunks(text), args.max_lines, args.max_batches, args.max_chars), 1):
batch_path = out_dir / f"batch-{i}.diff"
seed_path = out_dir / f"fragment-{i}.seed.json"
body = "".join(chunk for _, chunk in group)
Expand Down Expand Up @@ -195,6 +247,7 @@ def main():
split_parser.add_argument("--diff", required=True)
split_parser.add_argument("--out", required=True)
split_parser.add_argument("--max-lines", type=int, default=400)
split_parser.add_argument("--max-chars", type=int, default=120000)
split_parser.add_argument("--max-batches", type=int, default=8)
split_parser.set_defaults(func=do_split)

Expand Down
3 changes: 3 additions & 0 deletions skills/code-walkthrough/scripts/test_cw_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ def test_claude_call_error_kinds():
"rate": [cw_testlib.claude_result(is_error=True, result="rate limit hit", api_error_status=429)],
"ctx": [cw_testlib.claude_result(is_error=True, result="Prompt is too long")],
"nf": [not_found],
"flag": [cw_testlib.claude_result(is_error=True, result="API Error: Sonnet 5.5's safeguards flagged this message")],
"raw": [cw_testlib.claude_raw("not json", exit=1, stderr="boom")],
"long": [cw_testlib.claude_result(is_error=True, result="z" * 500)],
}
Expand All @@ -370,6 +371,8 @@ def failure(model, **extra):
assert error.kind == "context" and "ctx" in error.remedy
error = failure("nf", name="prof-nf")
assert error.kind == "notfound" and "prof-nf" in error.remedy
error = failure("flag")
assert error.kind == "flagged" and "safeguards" in error.remedy
assert failure("raw").kind == "protocol"
error = failure("long")
assert error.kind == "error" and len(str(error)) == 300
Expand Down
167 changes: 166 additions & 1 deletion skills/code-walkthrough/scripts/test_cw_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import cw_store # noqa: E402
import cw_testlib # noqa: E402
import fanout # noqa: E402
from validate_analysis import WITHHELD_NOTE # noqa: E402


def _config(**overrides):
Expand All @@ -41,7 +42,8 @@ def _prepare_batches(tmp, repo, base, head, max_lines=1):
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
fanout.do_split(types.SimpleNamespace(
diff=str(d / "raw.diff"), out=str(batches_dir), max_lines=max_lines, max_batches=8))
diff=str(d / "raw.diff"), out=str(batches_dir), max_lines=max_lines, max_batches=8,
max_chars=1000000000))
manifest = json.loads(buf.getvalue())
(batches_dir / "manifest.json").write_text(json.dumps(manifest, indent=2))
return d, manifest
Expand Down Expand Up @@ -121,6 +123,169 @@ def test_failing_fragment_gate_escalates(kind):
assert all(Path(e["cwd"]).resolve() == (d / "head").resolve() for e in log())


def test_stubbed_long_line_file_reaches_the_model_headers_only_and_passes_with_the_canned_note():
big = "x" * 5000
with tempfile.TemporaryDirectory() as tmp:
repo, base, head = cw_testlib.make_repo(tmp, {"foo.py": "a\nb\nc\n"}, {"foo.py": f"a\n{big}\nc\n"})
d, manifest = _prepare_batches(tmp, repo, base, head, max_lines=50)
(d / "head").mkdir()
entry = manifest[0]
seed = json.loads(Path(entry["seed"]).read_text())
with cw_testlib.StubLLM({"m": [cw_testlib.tool_call("submit_fragment", _passing_fragment(seed))]}) as stub:
config = _config(profiles={"a": stub.profile("m")}, roles={"analysis": "a", "escalate": "a"})
assert cw_run._run_batch(d, 1, entry, config, None) is True
prompt = json.dumps(stub.requests)
assert big not in prompt and "KB omitted" in prompt and seed["files"][0]["hunks"][0]["header"] in prompt
frag = json.loads((d / "batches" / "fragment-1.json").read_text())
assert {h["note"] for h in frag["files"][0]["hunks"]} == {WITHHELD_NOTE}
assert json.loads((d / "meta.json").read_text())["withheld"] == {"1": ["foo.py"]}


def test_flagged_error_skips_escalate_and_names_the_batch_files():
cw_testlib.require_posix()
with tempfile.TemporaryDirectory() as tmp, contextlib.ExitStack() as stack:
repo, base, head = cw_testlib.make_repo(tmp, {"foo.py": "a\nb\nc\n"}, {"foo.py": "a\nX\nc\n"})
d, manifest = _prepare_batches(tmp, repo, base, head)
(d / "head").mkdir()
flagged = cw_testlib.claude_result(is_error=True, result="API Error: safeguards flagged this message")
fc = stack.enter_context(cw_testlib.fake_claude(tmp, {"sonnet": [flagged] * 3, "opus": [flagged] * 3}))
config = _config(profiles={"a": {"kind": "claude-code", "model": "sonnet"},
"e": {"kind": "claude-code", "model": "opus"}},
roles={"analysis": "a", "escalate": "e"})
assert cw_run._run_batch(d, 1, manifest[0], config, None) is False
assert fc.count("opus") == 0
step = json.loads((d / "meta.json").read_text())["steps"]["batch-1"]
assert step["attempts"] == 1
assert "batch 1" in step["remedy"] and "foo.py" in step["remedy"]


def test_flagged_batch_retries_with_the_largest_file_stubbed():
cw_testlib.require_posix()
with tempfile.TemporaryDirectory() as tmp, contextlib.ExitStack() as stack:
repo, base, head = cw_testlib.make_repo(
tmp, {"small.py": "a\nb\n", "large.py": "a\nb\n"},
{"small.py": "a\nX\n", "large.py": "a\n" + "y = 1\n" * 200})
d, manifest = _prepare_batches(tmp, repo, base, head, max_lines=1000)
(d / "head").mkdir()
entry = manifest[0]
assert len(manifest) == 1
seed = json.loads(Path(entry["seed"]).read_text())
flagged = cw_testlib.claude_result(is_error=True, result="API Error: safeguards flagged this message")
good = cw_testlib.claude_result(structured=_passing_fragment(seed))
fc = stack.enter_context(cw_testlib.fake_claude(tmp, {"sonnet": [flagged, good], "opus": [good]}))
config = _config(profiles={"a": {"kind": "claude-code", "model": "sonnet"},
"e": {"kind": "claude-code", "model": "opus"}},
roles={"analysis": "a", "escalate": "e"})
events = []
assert cw_run._run_batch(d, 1, entry, config, lambda k, v: events.append(v)) is True
assert fc.count("sonnet") == 2 and fc.count("opus") == 0
meta = json.loads((d / "meta.json").read_text())
assert meta["withheld"] == {"1": ["large.py"]}
assert meta["steps"]["batch-1"]["withheld"] == ["large.py"]
frag = json.loads((d / "batches" / "fragment-1.json").read_text())
by_path = {f["path"]: f for f in frag["files"]}
assert all(h["note"] == WITHHELD_NOTE for h in by_path["large.py"]["hunks"])
assert all(h["note"] != WITHHELD_NOTE for h in by_path["small.py"]["hunks"])


def test_batch_stays_failed_when_every_file_is_stubbed_and_still_flagged():
cw_testlib.require_posix()
with tempfile.TemporaryDirectory() as tmp, contextlib.ExitStack() as stack:
repo, base, head = cw_testlib.make_repo(tmp, {"foo.py": "a\nb\n"}, {"foo.py": "a\nX\n"})
d, manifest = _prepare_batches(tmp, repo, base, head)
(d / "head").mkdir()
flagged = cw_testlib.claude_result(is_error=True, result="API Error: safeguards flagged this message")
fc = stack.enter_context(cw_testlib.fake_claude(tmp, {"sonnet": [flagged] * 5, "opus": [flagged] * 5}))
config = _config(profiles={"a": {"kind": "claude-code", "model": "sonnet"},
"e": {"kind": "claude-code", "model": "opus"}},
roles={"analysis": "a", "escalate": "e"})
assert cw_run._run_batch(d, 1, manifest[0], config, None) is False
assert fc.count("sonnet") == 2 and fc.count("opus") == 0
assert "foo.py" in json.loads((d / "meta.json").read_text())["steps"]["batch-1"]["remedy"]


def test_flagged_recovery_stops_after_five_stubbed_files():
cw_testlib.require_posix()
names = [f"f{i}.py" for i in range(7)]
with tempfile.TemporaryDirectory() as tmp, contextlib.ExitStack() as stack:
repo, base, head = cw_testlib.make_repo(
tmp, {n: "a\nb\n" for n in names}, {n: "a\nX\n" for n in names})
d, manifest = _prepare_batches(tmp, repo, base, head, max_lines=1000)
(d / "head").mkdir()
flagged = cw_testlib.claude_result(is_error=True, result="API Error: safeguards flagged this message")
fc = stack.enter_context(cw_testlib.fake_claude(tmp, {"sonnet": [flagged] * 20, "opus": [flagged] * 20}))
config = _config(profiles={"a": {"kind": "claude-code", "model": "sonnet"},
"e": {"kind": "claude-code", "model": "opus"}},
roles={"analysis": "a", "escalate": "e"})
assert cw_run._run_batch(d, 1, manifest[0], config, None) is False
assert fc.count("sonnet") == 6 and fc.count("opus") == 0


def test_small_route_stubs_and_retries_on_flagged_without_escalating():
cw_testlib.require_posix()
with tempfile.TemporaryDirectory() as tmp, contextlib.ExitStack() as stack:
repo, base, head = cw_testlib.make_repo(
tmp, {"small.py": "a\nb\n", "large.py": "a\nb\n"},
{"small.py": "a\nX\n", "large.py": "a\n" + "y = 1\n" * 200})
d, manifest = _prepare_batches(tmp, repo, base, head, max_lines=1000)
(d / "head").mkdir()
seed = json.loads(Path(manifest[0]["seed"]).read_text())
analysis = {**_passing_fragment(seed), "overview": "Overview text", "verdict": "v"}
flagged = cw_testlib.claude_result(is_error=True, result="API Error: safeguards flagged this message")
good = cw_testlib.claude_result(structured=analysis)
fc = stack.enter_context(cw_testlib.fake_claude(tmp, {"sonnet": [flagged, good], "opus": [good]}))
config = _config(profiles={"a": {"kind": "claude-code", "model": "sonnet"},
"e": {"kind": "claude-code", "model": "opus"}},
roles={"prose": "a", "escalate": "e"})
assert cw_run._run_small(d, {"target": "t"}, config, None) is True
assert fc.count("sonnet") == 2 and fc.count("opus") == 0
assert json.loads((d / "meta.json").read_text())["withheld"] == {"1": ["large.py"]}
by_path = {f["path"]: f for f in json.loads((d / "analysis.json").read_text())["files"]}
assert all(h["note"] == WITHHELD_NOTE for h in by_path["large.py"]["hunks"])


def test_clean_pass_clears_a_stale_withheld_entry_and_reuse_recomputes_it():
with tempfile.TemporaryDirectory() as tmp:
repo, base, head = cw_testlib.make_repo(tmp, {"foo.py": "a\nb\nc\n"}, {"foo.py": "a\nX\nc\n"})
d, manifest = _prepare_batches(tmp, repo, base, head)
entry = manifest[0]
seed = json.loads(Path(entry["seed"]).read_text())
cw_store.update_meta(d, lambda m: m.update({"withheld": {"1": ["foo.py"]}}))
with cw_testlib.StubLLM({"m": [cw_testlib.tool_call("submit_fragment", _passing_fragment(seed))]}) as stub:
config = _config(profiles={"a": stub.profile("m")}, roles={"analysis": "a", "escalate": "a"})
assert cw_run._run_batch(d, 1, entry, config, None) is True
assert "1" not in json.loads((d / "meta.json").read_text())["withheld"]
frag = _passing_fragment(seed)
frag["files"][0]["hunks"][0]["note"] = WITHHELD_NOTE
(d / "batches" / "fragment-1.json").write_text(json.dumps(frag))
assert cw_run._run_batch(d, 1, entry, _config(), None) is True
assert json.loads((d / "meta.json").read_text())["withheld"] == {"1": ["foo.py"]}


def test_routed_retry_passes_withheld_paths_and_keeps_the_fragment_when_flagged(monkeypatch):
cw_testlib.require_posix()
with tempfile.TemporaryDirectory() as tmp, contextlib.ExitStack() as stack:
repo, base, head = cw_testlib.make_repo(tmp, {"b.py": "x\ny\nz\n"}, {"b.py": "x\nB\nz\n"})
d, manifest = _prepare_batches(tmp, repo, base, head)
(d / "head").mkdir()
seed = json.loads(Path(manifest[0]["seed"]).read_text())
good = _passing_fragment(seed)
(d / "batches" / "fragment-1.json").write_text(json.dumps(good))
(d / "prose.json").write_text(json.dumps({"target": "t", "overview": "", "verdict": ""}))
cw_store.update_meta(d, lambda m: m.update({"withheld": {"1": ["b.py"]}}))
flagged = cw_testlib.claude_result(is_error=True, result="API Error: safeguards flagged this message")
stack.enter_context(cw_testlib.fake_claude(tmp, {"sonnet": [flagged] * 3}))
seen = []
real = cw_run._run_fragment_conversation
monkeypatch.setattr(cw_run, "_run_fragment_conversation",
lambda *a, **k: (seen.append(k.get("stub_paths")), real(*a, **k))[1])
config = _config(profiles={"a": {"kind": "claude-code", "model": "sonnet"}}, roles={"analysis": "a"})
cw_run._route_and_finalize(d, {"target": "t"}, config, None, 1, manifest, ["b.py: role is empty"])
assert seen == [{"b.py"}]
assert json.loads((d / "batches" / "fragment-1.json").read_text()) == good
assert "batch 1 (b.py)" in json.loads((d / "meta.json").read_text())["steps"]["routed"]["remedy"]


def test_llm_error_counts_as_one_failed_conversation_and_moves_on():
with tempfile.TemporaryDirectory() as tmp:
repo, base, head = cw_testlib.make_repo(tmp, {"foo.py": "a\nb\nc\n"}, {"foo.py": "a\nX\nc\n"})
Expand Down
Loading
Loading