Skip to content

Releases: ferdinandobons/brand-docs

v0.10.1: reliability and security hardening

Choose a tag to compare

@ferdinandobons ferdinandobons released this 10 Jul 09:17

BrandDocs v0.10.1 is a reliability and security hardening release. It adds no new product features.

Security

  • Validate OOXML packages at every live extract, generate, deterministic-QA, and visual-render boundary before third-party parsers process them.
  • Enforce archive, inflated part/package, entry-count, compression-ratio, duplicate-member, unsafe-path, and content-types constraints centrally.

Fixed

  • Preserve XLSX x14 sparkline groups through openpyxl saves and fail closed when worksheet extensions are lost.
  • Publish generated files atomically only after schema, resolver, provenance, and QA checks pass; failed runs preserve any existing good output.
  • Bound whole-row and whole-column Excel names to the used rectangle, reject discontiguous names explicitly, and keep sparse-sheet operations sparse.
  • Make fixture-builder failures fail CI instead of silently becoming skips.

Changed

  • Pin CI/development dependencies in requirements-ci.txt.
  • Treat warnings as errors except for the explicitly handled openpyxl sparkline warning.
  • Test and support Python 3.10 through 3.13 explicitly.

Verification

  • 946 tests passed, 6 skipped, and 39 subtests passed locally on Python 3.11 and 3.13.
  • Ruff lint and formatting checks passed.
  • Real LibreOffice/Poppler rendering passed locally and in GitHub Actions.
  • GitHub Actions passed across Python 3.10, 3.11, 3.12, and 3.13.

v0.10.0: hero polish

Choose a tag to compare

@ferdinandobons ferdinandobons released this 10 Jun 10:52

Hero-image polish release.

Changed

  • The README hero's three blocks are now equidistant. The full hero refresh shipped inside v0.9.0 without a changelog entry: a redesigned one-to-many visual (one company template, extracted into a verified hexagonal Brand Profile core, fanning out into unlimited on-brand documents, with numbered extract / verify / generate steps), exported as the transparent assets/hero.webp the README embeds plus a white-flattened assets/hero.jpg, with assets/hero.svg as the editable source. This release balances that composition: the template page moved 30 px right, the core 45 px left, and the flow curves, flying brand particles and step labels were re-fit so the three blocks sit at even ~250 px gaps (pixel-measured on the render).

Full changelog: CHANGELOG.md

v0.9.0 - the brand-intelligence release

Choose a tag to compare

@ferdinandobons ferdinandobons released this 10 Jun 10:41

[0.9.0] - 2026-06-10

The brand-intelligence release: derived indexes that never lie (rich TOC
cache, empty rebuilds), multi-template profiles (extract --blend), the
cross-template drift report (compare-profiles), a guided authoring surface,
a local-corpus fidelity benchmark, and a 31-finding general-review hardening
wave on top.

Fixed

  • General-review wave (multi-agent, 31 confirmed findings). Engine: the
    heading-less generation now collapses a bare multi-paragraph outline TOC's
    FULL span to an empty field (the plain writer used to leave the template's
    demo entries and orphan the end fldChar - a regression of the rich-TOC
    delta); pptx and xlsx extraction now parse the theme a:fontScheme latin
    typefaces instead of hardcoding latin=null (the docx extractor already
    did); schema.validate returns problems instead of raising on a
    hand-edited blend ledger with mixed-type entries; profile JSON/sidecar
    writes are atomic (same-directory temp + os.replace), making the blend's
    byte-identity-on-failure claim true under ENOSPC/kill; OOXML unpack/
    read_part reject decompression bombs (per-part and per-package declared
    inflated-size floors); the corpus runner exits 1 on verify exceptions, not
    just clean failures. Examples: the three builders now consume ONE complete
    12-slot brand clrScheme from _brandlib (docx/pptx/xlsx had drifted apart
    on five supporting slots - caught live by compare-profiles - and the
    shipped pair now reports zero theme drift, pinned by a strengthened CLI
    test that replaced a tautological assertion). Docs: README test count and
    blending/compare teaching, CONVENTIONS gains the engine verb surface and a
    §16 blend/compare vocabulary, the Pages site's broken README anchor and
    stale install command fixed, a real brand word in ROADMAP replaced with a
    placeholder.

  • The refreshed outline TOC cache is now a real Word-shaped TOC (REFLECTIONS
    P3).
    The visible cache of a preserved or authored outline table of contents
    was rewritten as flat plain-text entries: one style for every level, no
    links, no page-number fields. The rewrite now authors deterministic
    bookmarks on the generated headings (_TocBD000001 sequential,
    collision-scanned ids), harvests a per-level paragraph-style map from the
    template's OWN cached entries (trailing-digit TOC style convention,
    indentation-rank fallback, nearest-lower-level chain), and emits each entry
    as hyperlink-to-bookmark + tab + nested dirty PAGEREF, so previews show
    the template's real per-level TOC styling and Word fills page numbers on
    open. The bare-paragraph TOC path converges to the same multi-paragraph
    shape. Fail-closed: a malformed cache falls back to the previous simple
    rewrite atomically; documents without an outline TOC author zero bookmarks
    and stay byte-identical (now pinned by a dedicated TOC-free frozen anchor;
    the historical anchor was deliberately recomputed because the committed
    fixture itself contains an outline TOC, so its cached-TOC bytes necessarily
    changed shape).

  • Template demo entries no longer survive in derived indexes the content did
    not feed.
    A kept caption index (table-of-tables / table-of-figures) whose
    sequence received no captions, and an outline TOC in a heading-less
    generation, used to keep the template's cached demo entries (the "stale
    derived index" defect class, visible in every preview renderer). Both caches
    are now rebuilt EMPTY: the field code survives, dirty, so Word recomputes it
    on open, but fabricated entries never reach the generated document. The
    destructive-action floor is untouched: structure is still never removed
    without corroboration; only the visible cache is honest now.

Added

  • Local-corpus fidelity benchmark (REFLECTIONS P3). New
    scripts/corpus_benchmark.py walks a corpus of REAL templates that lives
    outside the repository (and refuses one inside it), runs
    extract -> verify -> brand-agnostic probe generate -> QA per template in a
    throwaway directory, and writes a dated report.{md,json} next to the
    corpus with the LibreOffice-vs-Word caveat stated in the header. The probe
    input is typed blocks only (no styles, colors, fonts), so the corpus
    measures the engine without ever tuning it; documentation/DEVELOPMENT.md
    documents the layout. CI smokes the runner on the synthetic example
    template.
  • Authoring intelligence for the IntermediateDocument/GridDocument
    (REFLECTIONS P3).
    The least-guarded stage (what the authoring agent puts
    IN the input) now has a guided surface: PROFILE.md gains three
    format-uniform sections rendered by the new shared common/profilemd.py
    (the role table on all three formats - pptx/xlsx previously shipped a
    two-line stub - a "Brand palette roles" table naming the semantic color
    tokens an author may reference, and "Authoring hints" that advertise
    comprehended fragments when present); each skill's SKILL.md gains an
    "Authoring the IntermediateDocument/GridDocument" section with role-first
    composition rules (skeleton order, heading hierarchy the TOC regenerates
    from, captions so derived indexes stay real, native objects on pptx, region
    bounds and formula preservation on xlsx, color discipline, fragments before
    re-derivation). Advisory by construction: nothing here adds engine
    authority, the resolver remains the only author of values.
  • Multi-template profile blending (REFLECTIONS P3, the quality-ceiling
    lift).
    extract --name X --template second.docx --blend folds a SECOND
    same-format template into an existing profile at the VALUE-fact level: it
    fills captured facts the primary left unset, corroborates agreeing facts
    with a bounded deterministic confidence boost, and keeps the primary's value
    on every conflict. Artifact POINTERS (style ids, layouts, anchors,
    numbering) never cross shells: generation still opens the PRIMARY shell and
    the resolver still membership-validates everything against it, so the brand
    guarantee is untouched. Fail-closed all-or-nothing transaction (a rejected
    blend leaves profile.json byte-identical), sha-deduped and idempotent
    (re-blending the same file is a no-op), secondary shells stored
    content-addressed next to the primary with provenance, guarded by the new
    blend_shell_provenance QA check (donor tamper/drift = ERROR). Single-
    template profiles serialize without one new key and both frozen generation
    anchors are unmoved. Same-format only by design; cross-format stays the job
    of compare-profiles.
  • compare-profiles: the cross-template drift report (REFLECTIONS P3).
    New read-only CLI verb that compares the BRAND-level facts of two saved
    profiles: theme colors per slot, theme/captured fonts, semantic palette
    roles, and off-theme usage (a raw hex in one profile that is a theme slot in
    the other - the sharpest "one template is off-theme" signal). Structural
    facts (styles, layouts, anchors) are never compared as drift: they are
    per-shell by design; role coverage is reported as information only. Writes
    nothing; exits 1 on brand-level drift so it can gate brand coherence in CI.
    First live run immediately caught a real inconsistency between the shipped
    docx and pptx example templates (five theme slots disagree) - queued for the
    next example-template refresh.
  • Contributor onramp (REFLECTIONS P1). CONTRIBUTING.md expanded from a
    10-line stub to the full gate (dev setup, the three test lanes, the seven
    non-negotiable rules, PR checklist, sized first contributions);
    scripts/brandkit/README.md created as the engine internals map (module
    layout, the ten verbs' call paths, the two invariants, the recipe for a new
    capture axis); the release checklist written down in
    documentation/DEVELOPMENT.md; three sized "good first issue"s opened.
  • PLUGIN_WORKFLOW drift guard. documentation/PLUGIN_WORKFLOW.md now
    documents the full ten-verb CLI surface and the learning loop
    (generation_report.json -> learn/propose-overrides -> --accept) in the
    end-to-end diagram; tests/test_doc_drift.py fails the build if a CLI verb
    or a commands/ slash command is missing from the document.

v0.8.1: hardening patch

Choose a tag to compare

@ferdinandobons ferdinandobons released this 10 Jun 04:51

Highlights

A post-0.8.0 hardening patch. Generated bytes are unchanged for well-formed templates and profiles (frozen byte-identity anchor verified).

  • Plugin onboarding fixed: every skill/command doc now teaches python scripts/cli.py ... - a launcher that resolves the engine root from the skill folder AND the repo/plugin root (BRAND_DOCS_ROOT overrides). Fresh plugin sessions no longer trip on a wrong path.
  • Example pptx imports in Keynote: python-pptx omits p:notesMasterIdLst when speaker notes are authored; strict importers rejected the package. The builder now repairs it deterministically at build time.
  • Engine hardening (with regression tests): caption-index refresh validates both field bounds (a corrupted inventory could corrupt the body via negative indexing); role_id validates its own composition; border-apply refuses mislabeled sides and narrows its catches; set-only-when-unset guards treat empty attributes as unset. A frozen CHECK_REGISTRY pins the QA check-id vocabulary behind an AST-guarded test.
  • Example docx visual fixes from a page-by-page render audit: clean solid cover band, landscape appendix no longer inherits the two-column layout, readable white table headers, repeating header rows on page-spanning tables, balanced two-column synopsis.
  • Honest speed expectation in the README: first run on a new template up to ~15 minutes end to end with an agent; every later document takes seconds - still a fraction of hand-formatting, with a faithful file instead of an approximate one.

Full detail: CHANGELOG.md

v0.8.0: the learning release

Choose a tag to compare

@ferdinandobons ferdinandobons released this 10 Jun 01:22

Highlights

The profile now learns. Three channels close the loop, every one fail-closed and advisory until an explicit --accept:

  • From its own QA findings: every generate persists a cross-run report; the learn verb distills recurring findings into shell-frozen overrides, and the new propose-overrides verb lets the model correct the ambiguous remainder through the same single validated sink, with an override_applied audit finding for every live correction.
  • From the model's judgement: the L2 visual-audit verdict persists (and soundly short-circuits byte-identical re-renders), ambiguous WARNINGs get triaged, faked headings (a body-style line that visibly acts as a heading) are detected as pseudo_headings and can be promoted onto a real heading role, and off-theme brand accents become addressable run colors via palette aliases (the model names, the engine byte-copies the captured ref).
  • From the user: the refine verb turns end-of-generation feedback (text or a screenshot) into a validated comprehension delta for future generations.

Fidelity below typography (docx): three new dominant-sampled, shell-verified appearance axes - paragraph geometry (spacing/indents/borders/shading), table conditional formats (tblLook, table-style reference, cell margins; band fills stay in the shell's style part) and list numbering (per-level numFmt/lvlText/indent, referencing/cloning the shell's own definitions, never synthesized). Run typography (font/size/color) now captures and applies on all three formats.

Covers on anchor-less templates: when extraction records anchors.cover.kind == NONE, the generator synthesizes a minimal cover from the profile's own resolvable cover.* roles through the resolver chokepoint - validated on a real-world template - with a cover_synthesized audit finding. A uniform appearance_apply_degraded parity ledger measures any captured axis a format cannot realize yet.

Measurably faster, byte-identical: single-launch render probe, per-pass artifact-load memo, lazy imports, shared scans. generate --qa auto ~8.5s -> ~5s per format; the 850-test suite 59s -> 24s; document bytes, profiles and QA reports byte-diffed identical.

Validated end-to-end on the enriched synthetic example templates and on real corporate templates (in-place SDT covers, synthesized covers, faked-heading promotion, alias minting, caption-index regeneration). Schema stays 1.2.0: profiles from 0.7.0 keep working unchanged.

Full detail: CHANGELOG.md

v0.7.0 - brand appearance, model-driven color, cover & caption-index fidelity

Choose a tag to compare

@ferdinandobons ferdinandobons released this 09 Jun 13:16

[0.7.0] - 2026-06-09

Brand-fidelity release: the engine now follows a template's real per-run typography
(font, size, color), applies model-named brand colors, fills content-control covers
faithfully, and regenerates caption indexes from the new content. Schema stays 1.2.0
(additive); profiles from earlier versions keep working unchanged.

Added

  • Brand typography capture (font family + size + color). Extraction now captures
    the template's dominant DIRECT run typography - the real visible font, font size
    and run color a designed template applies per-run (e.g. Roboto 16pt) rather than
    via the named styles or theme - into the reserved role.appearance field and the
    additive theme.fonts.body (font/size) and theme.text.body (color). Generation
    applies all three as direct run formatting through the resolver (paragraphs, list
    items, captions, quotes, callouts, table cells, and hyperlink runs), so a generated
    document renders in the template's real font, size and color instead of falling
    back to the docDefaults (typically Arial 11pt black). _extract_theme also reads
    the real theme1.xml major/minor fonts and docDefaults.
    • Universal and deterministic. Each axis is captured by dominant sampling over
      ALL runs (a run with no explicit value votes "inherit"), so a value is applied
      only when it dominates the whole document - an accent color present on a few runs
      never becomes the body color. Nothing is hardcoded to any template; a document
      with no dominant value (and every pre-capture profile) behaves exactly as before.
    • Brand guarantee, fail-closed. Every applied font/size/color is re-validated
      against what the shell proves it contains (appearance_targets_exist): fonts vs
      the fontTable + theme, sizes vs the template's own w:sz values, colors vs the
      theme palette + the template's own w:color values. The body size/color default
      never flows onto heading roles (their style's intrinsic size/color is preserved).
    • Deferred (see ROADMAP section 1): per-word accents,
      cover-layout reconstruction, and heading typography when the template fakes
      headings in the body style.
  • Model-driven brand color (run color palette tokens). The profile now carries a
    theme.palette of the template's brand colors (theme slots like accent1, plus
    off-theme hex:RRGGBB) with usage provenance; an IntermediateDocument run can carry
    a semantic color token (a palette key, never a literal hex) that the resolver maps
    to the brand color and the generator applies - rendering a theme color as
    w:color@w:val=<hex> + w:themeColor=<slot> so it shows in both LibreOffice and
    Word. The model NAMES each color's purpose via comprehension.palette_annotations;
    it never authors a color (fail-closed: a token into an empty/absent palette errors).
  • Caption-index regeneration (list of tables / figures). A kept caption index
    (TOC \c "<seq>") is now repopulated from the new content: each captioned table or
    figure emits a real Word SEQ field, and the index's visible cache is rebuilt from
    the emitted captions (so a headless render shows the new entries, not the template's
    stale ones). The model maps a caption kind to its index via the new closed
    comprehension.conventions.indexes[*].caption_target (table|figure); brand- and
    language-agnostic (the seq label is the template's own, read from the profile).
  • Cover fidelity for content-control covers. A cover content control bound to a
    core property (e.g. the subject repeated in the page header) now has its cached run
    text refreshed across the body and every header/footer, so a headless render shows
    the filled value, not the template prompt. The cover role-style re-assertion is
    gated to bare placeholder controls, so a real author-formatted slot keeps its own
    formatting (a builtin Subtitle whose color is a near-white tint no longer blanks a
    filled cover subtitle).

Fixed

  • Colored hyperlink runs now write w:color at the schema-correct CT_RPr position
    (before w:u / w:sz / w:vertAlign) instead of appending it last, so an
    underlined colored link is conformant OOXML; a theme-token link with no resolved hex
    emits themeColor only (no stray w:val="000000" that would paint it black).

v0.6.2

Choose a tag to compare

@ferdinandobons ferdinandobons released this 09 Jun 07:41

A robustness fix for real-world templates. No schema, profile or output change for well-formed templates; profiles from earlier versions keep working unchanged.

Fixed

  • DOCX extract and generate no longer crash on templates whose section measures are non-integer twips (e.g. 1440.0000000000002 in w:pgMar / w:pgSz, emitted by some editors). python-docx parses measures with int() and raises the moment any code touches the value, including its own internals (Document.add_table derives the table width from section.left_margin). The shell's section measures are now sanitized in place (sub-twip rounding, page geometry visually identical) and every section-length read tolerates the malformed value via a raw-twips fallback.

Tests

  • MalformedSectionMeasureTest: the section-length helpers tolerate the bad value, sanitize_section_measures repairs it in place, and a generate with a table block (the exact crash path) survives a malformed margin.

v0.6.1

Choose a tag to compare

@ferdinandobons ferdinandobons released this 09 Jun 06:31

A clean finished checkpoint: the remaining LOW items from the v0.6.0 review are
closed and dead parameters removed. No behavior change beyond the fixes; profiles
from earlier versions keep working unchanged.

Fixed

  • The QA gate now verifies a number_format role's mask against the SHELL's
    actually-used formats (the shell-backed peer of the schema's intra-profile
    check), so a fabricated/hand-edited mask is caught at verify time, never applied.
  • A table/KPI/SmartArt block rendered against a profile with no matching role
    no longer falls back to the default style SILENTLY: it surfaces an INFO
    style_fallback so the missing brand style is visible in QA (the content still
    renders).

Changed

  • Removed dead parameters (no behavior change): run_qa's never-read mode, the
    docx _write_block profile arg, and the xlsx _resolve_named_target findings
    arg.

Tests

  • Closed the review's remaining coverage gaps: strict-mode promotion of
    visual.ocr_degraded to an error, _ocr_png timeout/OSError degradation, the
    Quick-Look-absent render degrade, the QA-gate fabricated-mask rejection, and the
    style_fallback path.

v0.6.0

Choose a tag to compare

@ferdinandobons ferdinandobons released this 08 Jun 23:11

Visual QA works by default, a whole-project code + quality review (multi-agent,
adversarially verified) hardens security/correctness/determinism, and the docs are
re-synced to the shipped engine. Brand Profiles from 0.1.x-0.5.0 keep working
unchanged.

Added

  • Visual QA runs by default and installs in one step. The renderer probe is now
    FUNCTIONAL-first: a LibreOffice that actually renders is usable even if its macOS
    code signature was knocked loose by an update/quarantine removal (no re-sign
    needed), so the visual gate is no longer falsely disabled. scripts/setup_visual_qa.sh
    auto-detects the platform package manager and installs LibreOffice + Poppler
    (+ optional Tesseract); the README/INSTALLATION reframe visual QA as a standard,
    on-by-default part of the QA step.
  • An aggregate OCR time budget so a many-page document cannot turn a deep/strict
    gate into an N x timeout hang.

Fixed

  • Security - Excel formula injection: author content starting with = became a
    LIVE formula (=WEBSERVICE/=HYPERLINK/DDE) in the generated workbook, breaking
    the "formulas live only in the shell" invariant. It is now neutralized to a TEXT
    cell (verbatim, never executed) and surfaced; the QA gate also fails closed on any
    output formula the shell did not have.
  • Security - hyperlink scheme allowlist: docx/pptx now refuse file:/
    javascript:/data:/smb: link targets (the text is kept), so untrusted content
    cannot wire a hostile link into an on-brand file.
  • Robustness: component/section expansion has a node-count budget (runaway
    fan-out fails loud, never hangs/OOMs); a pathologically deep list IID raises the
    contracted IIDParseError instead of RecursionError; the OCR step tolerates
    non-UTF-8 tesseract output instead of crashing the whole run.
  • Determinism: xlsx generation is byte-idempotent even when the shell's
    core.xml lacks dcterms:created (openpyxl would otherwise fabricate a
    wall-clock one); a number-format mask is no longer applied to a preserved-formula
    / merged cell; profile.json is written with sorted keys.
  • Number-format classification: elapsed-time masks ([h]:mm) classify as time;
    accounting masks with bracketed currency + padding idioms classify as accounting.
  • Visual QA: the Quick Look fallback accepts only the expected thumbnail name
    (never stages a stale/unrelated PNG as a bogus render).

Changed

  • Removed verified-dead helpers and corrected stale docstrings/docs: the docx TOC
    and the pptx charts/SmartArt/KPI/images are native now (not "deferred"), and
    number_format/named_range are first-class resolver types.

v0.5.0

Choose a tag to compare

@ferdinandobons ferdinandobons released this 08 Jun 21:48

Completes the block-type matrix: Word now renders every block type natively
(the toc block was the last to degrade), and Excel resolves a cell's semantic
number format to the template's own mask. Brand Profiles from 0.1.x-0.4.0 keep
working unchanged; an absent comprehension is still the deterministic path.

Added

  • Word toc blocks render natively. The toc block was the last block type
    that degraded instead of rendering; it now authors a real, updateable outline
    table-of-contents field. If the shell already carries an outline TOC the block
    defers to it (refreshed in place, never a duplicate); otherwise it authors a
    native outline TOC field at the block's position, with its visible cache filled
    from the generated headings and the field marked dirty + updateFields so Word
    rebuilds it on open. It defers only to an OUTLINE TOC, so a shell shipping only a
    table-of-figures still gets its requested table of contents. Byte-idempotent.
    Every Word block type now renders natively.
  • Excel resolves a cell's semantic number format. The number_format resolver
    type is now wired end-to-end (previously reserved/staged). extract classifies
    the template's own number-format masks into brand-agnostic families
    (currency/percent/date/datetime/time/accounting/decimal/integer/
    text/scientific) and emits a number.<family> role bound to the template's
    VERBATIM mask. A GridDocument names the intent via formats: {name: family}
    (keyed by the same named-range/cell vocabulary as cells/regions); generation
    resolves number.<family> and applies the template's own mask to the filled
    cell(s). A format is never fabricated: an intent the template does not carry
    degrades loudly (number_format_degraded) and leaves the existing format, and a
    resolver whose mask is not in the template's surfaced formats is rejected at
    validation.