mirror of
https://github.com/msitarzewski/agency-agents.git
synced 2026-09-22 01:15:54 +03:00
fix(convert): track code fences when splitting OpenClaw sections, and pin it with an invariant (#855)
convert_openclaw() split on `## ` without tracking fenced code blocks, tearing six fenced examples across SOUL.md/AGENTS.md in five agents and leaving dangling fences in each. Implementation from #854 (@guozi-lab): fence helpers in lib.sh used by convert.sh and lint-agents.sh, CommonMark indent aware. Regression test from #853 (@dajiaohuang): every source fenced block must land whole in exactly one output file — verified to fail on the unfixed converter. Reported by @AmineOzil. Closes #849. Supersedes #853 and #854. Co-Authored-By: fruit <200041037+guozi-lab@users.noreply.github.com> Co-Authored-By: Wu Shuwen <108231307+dajiaohuang@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
fruit
Wu Shuwen
Claude Opus 5
parent
6d29a9b087
commit
ad9264e309
@@ -288,6 +288,71 @@ for tool in TOOLS:
|
||||
report(tool, bad_parse, bad_trip,
|
||||
"parse and round-trip" if fmt in ("yaml-fm", "toml") else "parse, carry their slug, and have their prose file")
|
||||
|
||||
# --- Layer A (split integrity): a source fenced block must survive whole -------
|
||||
# openclaw is the one tool that splits a single agent body across two files, at
|
||||
# `## ` headings: SOUL.md (persona) / AGENTS.md (operations). A heading inside a
|
||||
# fenced code block is block content, not a boundary. Splitting on one cuts the
|
||||
# block in half and leaves each file holding a dangling fence, which renders as
|
||||
# broken markdown for every user of that integration (#849). So every fenced
|
||||
# block in a source must land intact in exactly one of the two files.
|
||||
SPLIT_FENCE = re.compile(r"^(`{3,}|~{3,})")
|
||||
|
||||
def body_lines(text):
|
||||
"""Mirror lib.sh's get_body, including `$(...)`'s trailing-newline strip."""
|
||||
out, fm = [], 0
|
||||
for line in text.split("\n"):
|
||||
if line == "---":
|
||||
fm += 1
|
||||
continue
|
||||
if fm >= 2:
|
||||
out.append(line)
|
||||
while out and out[-1] == "":
|
||||
out.pop()
|
||||
return out
|
||||
|
||||
def fence_blocks(lines):
|
||||
"""Inclusive (opener, closer) index pairs; closer = last line if unterminated."""
|
||||
res, marker, mlen, start = [], "", 0, None
|
||||
for i, line in enumerate(lines):
|
||||
m = SPLIT_FENCE.match(line)
|
||||
if not m:
|
||||
continue
|
||||
tok = m.group(1)
|
||||
if not marker:
|
||||
marker, mlen, start = tok[0], len(tok), i
|
||||
elif tok[0] == marker and len(tok) >= mlen:
|
||||
res.append((start, i)); marker, mlen, start = "", 0, None
|
||||
if marker and start is not None:
|
||||
res.append((start, len(lines) - 1))
|
||||
return res
|
||||
|
||||
def has_run(hay, needle):
|
||||
n = len(needle)
|
||||
return n > 0 and n <= len(hay) and any(hay[i:i+n] == needle for i in range(len(hay) - n + 1))
|
||||
|
||||
split_bad = 0
|
||||
for slug, (_d, _n, path) in sorted(src.items()):
|
||||
sfile = os.path.join(OUT, "openclaw", slug, "SOUL.md")
|
||||
afile = os.path.join(OUT, "openclaw", slug, "AGENTS.md")
|
||||
if not (os.path.isfile(sfile) and os.path.isfile(afile)):
|
||||
continue
|
||||
body = body_lines(open(os.path.join(R, path), encoding="utf-8").read())
|
||||
bl = fence_blocks(body)
|
||||
if not bl:
|
||||
continue
|
||||
outs = [open(sfile, encoding="utf-8").read().split("\n"),
|
||||
open(afile, encoding="utf-8").read().split("\n")]
|
||||
for a, b in bl:
|
||||
if not any(has_run(o, body[a:b + 1]) for o in outs):
|
||||
split_bad += 1
|
||||
if split_bad <= 3:
|
||||
bad(f"openclaw: {slug} source fenced block at lines {a+1}-{b+1} is torn across "
|
||||
f"SOUL.md/AGENTS.md — a `## ` heading inside the fence was treated as a section boundary")
|
||||
if split_bad:
|
||||
if split_bad > 3: bad(f"openclaw: ...and {split_bad-3} more torn fenced blocks")
|
||||
else:
|
||||
ok(f"openclaw: all {N} agents keep every source fenced block whole in one output file")
|
||||
|
||||
# --- Layer A (app-facing): every SOURCE frontmatter strict-parsed above -------
|
||||
for m in src_bad[:5]: bad(m)
|
||||
if len(src_bad) > 5: bad(f"...and {len(src_bad)-5} more source frontmatter problems")
|
||||
|
||||
Reference in New Issue
Block a user