Citation Assembly Hardening + Avalanche Goes Firm-Private
Three reproducible citation-assembly bugs squashed.
fix(citation-assembly): cascading-replacement, inline-with-colon, and
hyphen-in-citation-id bugs
fix(css): TOC heading no longer stranded by page-break-inside: avoid
on a too-tall nested <ol>
move(brand): Avalanche brand bundle → io/avalanche/ firm-private,
symlinked back from templates/brand-configs/
new(skill): context-v/skills/manage-memo-citations/SKILL.md
new(skill): context-v/skills/adhoc-edits-workflow/SKILL.md
new(script): scripts/fix_citation_positioning.py
bump(submodule): io/avalanche → ChromaDB v0.0.11
Three reproducible citation-assembly bugs found during the ChromaDB
v0.0.11 build. Each had a single-line root cause and a single-line
fix; the trick was teasing them apart.
* src/agents/citation_assembly.py — cascading-replacement bug. The
prior renumber_inline_citations used a sequential str.replace loop:
[^notes1] → [^1] could then be caught by the next rule [^1] → [^3],
collapsing many distinct citations to a single number. Replaced
with a single-pass callable-based re.sub so each marker is visited
exactly once.
* src/agents/citation_assembly.py — inline-citation-with-colon bug.
The prior `(?!:)` negative lookahead excluded end-of-sentence
inline citations like "claims [^X]:" as if they were definitions,
leaving them as orphan markers. The replacer now distinguishes
definitions structurally — must be `[^X]:` at the start of a line
followed by a space — from inline citations.
* src/agents/citation_assembly.py — hyphen-in-citation-id bug. The
citation ID regex was `[a-zA-Z0-9_]+`, which silently dropped
semantic IDs like `[^github-2018]`, `[^cursor-2026]`,
`[^mongodb-fy26]`. Widened to `[a-zA-Z0-9_-]+` across all five
places the pattern appears.
* templates/base-style.css — WeasyPrint was stranding the "Table of
Contents" heading on a page by itself with the list pushed to the
next page. Root cause: the markdown TOC is a nested <ol>, so the
outermost <li> (containing every subsection underneath) is taller
than a full page, and `page-break-inside: avoid` on `li` prevented
it from breaking. Added a more-specific override
(`#table-of-contents + ol ... { page-break-inside: auto }`) so the
TOC list flows immediately after the heading.
* templates/brand-configs/brand-avalanche-config.yaml is now a
symlink to ../../io/avalanche/configs/brand-avalanche-config.yaml.
Matches the dark-matter / hypernova convention. Canonical trademark
assets live in io/avalanche/configs/trademarks/ and the brand
config references them by local path; the duplicates in
templates/trademarks/trademark__Avalanche--*.{png,webp} are removed.
* context-v/skills/manage-memo-citations/SKILL.md — codifies the
"foundational citations live in 1-research/, not in section files"
rule, with a theme→file mapping table, the semantic-ID-over-numeric
convention, and a recovery playbook for when orphans appear.
* context-v/skills/adhoc-edits-workflow/SKILL.md — codifies the
four-step ad-hoc edit pipeline (research → sections → assemble →
export), with six anti-patterns captured from this session
(editing the final draft directly, adding prose before the citation
exists, hand-typed numeric IDs, skipping assembly before export).
* scripts/fix_citation_positioning.py — applies two house-style
rules: inline citations belong AFTER terminal punctuation, not
before; and same-citation-id repeats in one paragraph collapse to
a single marker at the end of the paragraph. Obsidian-compliant
single-space-before-bracket throughout.
Also included:
* io/avalanche submodule pointer advanced to the ChromaDB v0.0.11 commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Changelog — 2026-05-26_01
What broke and what got fixed
The ChromaDB v0.0.11 build surfaced three bugs in citation_assembly.py
that had been silently masking each other. The cascading-replacement
loop turned distinct citations into duplicates; the citation-with-colon
exclusion misread legitimate inline citations as definitions; the
hyphen-rejecting regex silently dropped any semantic ID that wasn't
purely alphanumeric. Run the assembler on a memo with semantic IDs
like [^chroma-site] and you'd see fewer citations in the output than
in the input — and not in a way that traced back cleanly to one cause.
Each fix is small in isolation. The lesson is in the diagnosis order: once the regex was widened to accept hyphens, the cascading-replacement behavior became visible because there were now more distinct IDs to collapse, and the inline-with-colon issue became visible because the definitions vs. inline distinction had to be rebuilt. Fix one, surface the next.
The WeasyPrint TOC issue is unrelated but landed in the same commit
because it surfaced in the same export run. A nested <ol> whose
outermost <li> is taller than a page can't honor
page-break-inside: avoid, and WeasyPrint deals with that by stranding
the heading rather than overriding the rule. The fix is one selector
specific enough to win over the global rule.
Avalanche goes firm-private
The brand config and trademark assets for Avalanche have been moved
into io/avalanche/configs/ and io/avalanche/configs/trademarks/,
following the same firm-private pattern as dark-matter, hypernova,
emerge, and alpha-partners. The orchestrator-side
templates/brand-configs/brand-avalanche-config.yaml is now a symlink
pointing back into the submodule; the asset paths inside the YAML
reference the canonical copies under io/avalanche/configs/trademarks/.
This is the standard form for firms whose visual identity counts as their IP. The orchestrator can read the symlinked YAML at export time without holding the assets itself.
Two new skills
The citation rules and the ad-hoc edit pipeline aren't novel — they've
been operative on every memo run for weeks — but they hadn't been
written down. Two context-v/skills/ entries fix that. The next
session that opens a section file to "just add a citation real quick"
can read manage-memo-citations/SKILL.md first and skip the
foundational-vs-section-citation footgun.
What didn't change
No outline YAML, no template structure, no agent prompt.
The assembler's interface is unchanged; only the renumbering pass and the regex character class moved.
No client-private content described here. The
io/avalanchesubmodule pointer bump is a one-liner, not a paragraph.
What's next
Run the assembler over a memo that historically had the cascading-replacement signature (avalanche v0.0.10 → v0.0.11) and diff citation counts to confirm.
The new skills will get exercised on the next memo run; expect at least one anti-pattern to need a follow-up addition.
References
src/agents/citation_assembly.py— the changed filetemplates/base-style.css— the TOC overridecontext-v/skills/manage-memo-citations/SKILL.mdcontext-v/skills/adhoc-edits-workflow/SKILL.mdscripts/fix_citation_positioning.pyapps/memopop-orchestrator/CLAUDE.md§"Changelog scope" — why the avalanche pointer bump is a one-liner, not a paragraph
