Problem
The asset scripts in .agents/skills/manage-gitbook-assets/scripts/ find image references with regex. Markdown link syntax is not a regular language, so this has already produced one real bug and remains fragile.
What already went wrong
All three scripts originally shared:
That stops at the first closing parenthesis. Many assets here are named like image-000209 (1).png, and markdown wraps paths containing spaces in angle brackets:
.png>)
^ the regex ended the match here
so the parsed basename was image-000209 (1 — a name that matches no file. A reference parsed that way looks unreferenced, which meant cleanup_orphans.py could quarantine an image that was actually in use. Eleven references in the repo parsed incorrectly under that regex.
The current parser in assetrefs.py is better but still regex:
RE_MD_ANGLE = re.compile(r'!\[[^\]]*\]\(\s*<([^>]+)>\s*\)')
RE_MD_PLAIN = re.compile(r'!\[[^\]]*\]\(\s*((?:[^()<>\s]|\([^()]*\))+)\s*\)')
RE_HTML = re.compile(r'<img[^>]+src=["\']([^"\']+)["\']')
RE_MD_PLAIN handles exactly one level of balanced parentheses. That covers today's filenames and nothing beyond them.
Current exposure
12 assets have parentheses in their names:
0C2DnfPTcmE_00-03-27_Nicolas11x12_..._What_To_Look_Out_For (1).png
Diag Wobble Huion Kamvas 16 GEN3 (GS1563).png
DigiDraw-Turing_Basic_Wobble_(T610)_2026_05_18.png
fedora-drawtab-ui (1).png .. (5).png
simple-nib-remover-huion-(Large).jpg
unsupported-video-format (Large).jpeg
Wacom-ACK20002-store-photo (1).webp
wacom-macos-high-memory-1 (Large).jpeg
All 12 are currently referenced as HTML <img src="...">, where the path sits inside a quoted attribute and the parenthesis problem does not arise. So this is latent, not live — every reference resolves today.
It becomes live the moment any of them is referenced with plain markdown  syntax, which GitBook may produce on any future edit of those pages. Nobody would notice: the failure is silent, and its symptom is an in-use image being quarantined.
Other cases regex will not cover
- Nesting deeper than one paren level
- Reference-style links:
![alt][ref] with the target defined elsewhere
- Escaped brackets or parens in alt text
- Image references inside fenced code blocks, which should be ignored but currently match
<img> with unquoted attributes, or src after other attributes containing >
Direction
Avoid regex. Parse the markdown properly and walk the resulting tree for image nodes, so the parser handles escaping, nesting, angle-bracket paths and code blocks by construction.
Candidates for a stdlib-plus-one-dependency approach:
markdown-it-py — CommonMark compliant, exposes a token stream, actively maintained
mistune — fast, AST output
marko — CommonMark compliant, clean AST
Whichever is chosen, the HTML <img> case still needs handling, since GitBook writes a lot of raw HTML. html.parser from the stdlib is enough for that and is more correct than the current attribute regex.
Complementary mitigation
Independently of the parser, renaming the 12 assets to drop parentheses and (Large) export suffixes would remove the ambiguity at the source. That is a smaller change and could land first, but it does not fix the parser for names added later.
Acceptance
assetrefs.py extracts references via a parser, not regex
- Round-trips the existing 933 references with no change in results
- Correctly ignores image syntax inside fenced code blocks
- Handles
 and spaces>) and reference-style links
Problem
The asset scripts in
.agents/skills/manage-gitbook-assets/scripts/find image references with regex. Markdown link syntax is not a regular language, so this has already produced one real bug and remains fragile.What already went wrong
All three scripts originally shared:
r'!\[.*?\]\((.*?)\)'That stops at the first closing parenthesis. Many assets here are named like
image-000209 (1).png, and markdown wraps paths containing spaces in angle brackets:so the parsed basename was
image-000209 (1— a name that matches no file. A reference parsed that way looks unreferenced, which meantcleanup_orphans.pycould quarantine an image that was actually in use. Eleven references in the repo parsed incorrectly under that regex.The current parser in
assetrefs.pyis better but still regex:RE_MD_PLAINhandles exactly one level of balanced parentheses. That covers today's filenames and nothing beyond them.Current exposure
12 assets have parentheses in their names:
All 12 are currently referenced as HTML
<img src="...">, where the path sits inside a quoted attribute and the parenthesis problem does not arise. So this is latent, not live — every reference resolves today.It becomes live the moment any of them is referenced with plain markdown
syntax, which GitBook may produce on any future edit of those pages. Nobody would notice: the failure is silent, and its symptom is an in-use image being quarantined.Other cases regex will not cover
![alt][ref]with the target defined elsewhere<img>with unquoted attributes, orsrcafter other attributes containing>Direction
Avoid regex. Parse the markdown properly and walk the resulting tree for image nodes, so the parser handles escaping, nesting, angle-bracket paths and code blocks by construction.
Candidates for a stdlib-plus-one-dependency approach:
markdown-it-py— CommonMark compliant, exposes a token stream, actively maintainedmistune— fast, AST outputmarko— CommonMark compliant, clean ASTWhichever is chosen, the HTML
<img>case still needs handling, since GitBook writes a lot of raw HTML.html.parserfrom the stdlib is enough for that and is more correct than the current attribute regex.Complementary mitigation
Independently of the parser, renaming the 12 assets to drop parentheses and
(Large)export suffixes would remove the ambiguity at the source. That is a smaller change and could land first, but it does not fix the parser for names added later.Acceptance
assetrefs.pyextracts references via a parser, not regex and spaces>)and reference-style links