refactor: marketplace split — feinschliff / builder / extra#24
Merged
Conversation
…very 5-source pattern Introduces LayoutSource and Layout dataclasses, discover_layouts(), find_layout(), and all_layout_dirs() with bundled/plugin/env/cwd-dev/user priority order. 12 TDD tests added and passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
…iscovery Removes REPO_ROOT, STD_COMPOUNDS, BRANDS_DIR module-level constants. brands_dir resolution now relies on brand_root.parent (the load_tokens default), and compounds/ is reached via a local _bundled_compounds() helper. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
Removes REPO_ROOT, BRANDS_DIR, STD_COMPOUNDS module-level constants. _resolve_layout_path now iterates all_layout_dirs() from layout_discovery. load_tokens and load_compounds_for_brand use brand_root.parent default and _bundled_compounds() respectively. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
…fallback Replaces the hardcoded Path(__file__).parents[2]/"brands" fallback in _load_tokens_with_extends with a call to find_brand() from brand_discovery, covering all registered discovery sources uniformly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
…nt resolution Replaces hardcoded Path(__file__).parents[2]/"brands" with discovery-based iteration over all brands directories from discover_brands(), covering bundled, plugin, env, cwd-dev, and user sources. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
…very Removes REPO_ROOT, BRANDS_DIR, STD_COMPOUNDS module-level constants from build.py, deck.py, and decompile.py. Layout lookups now use find_layout() and all_layout_dirs() from layout_discovery; brand lookups use find_brand() from brand_discovery; assets use _bundled_assets() helpers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
….4 followup) Signed-off-by: Mike Mueller <mike@objektarium.de>
…cs, narrow exception) Signed-off-by: Mike Mueller <mike@objektarium.de>
Signed-off-by: Mike Mueller <mike@objektarium.de>
Signed-off-by: Mike Mueller <mike@objektarium.de>
Signed-off-by: Mike Mueller <mike@objektarium.de>
Signed-off-by: Mike Mueller <mike@objektarium.de>
Signed-off-by: Mike Mueller <mike@objektarium.de>
Signed-off-by: Mike Mueller <mike@objektarium.de>
Signed-off-by: Mike Mueller <mike@objektarium.de>
Signed-off-by: Mike Mueller <mike@objektarium.de>
…osticBag Signed-off-by: Mike Mueller <mike@objektarium.de>
…ind_compound toolkit fallback test
Gap 1: Replace all three static_verify() call sites in cli/deck.py with
validate(plan, brand=brand_obj) from lib.verify.static. Boolean checks now use
DiagnosticBag truthiness/__bool__; JSON output retains backward-compatible
{slide_index, kind, severity, message, meta} schema for deck apply-fixes
compatibility. plan_fixes() in autofix.py updated to accept DiagnosticBag via
duck-typed _defect_slide_index()/_defect_meta() helpers; kind comparisons
changed from `is` to `==` for cross-enum compatibility.
Gap 2: Add test_find_compound_falls_back_to_toolkit to test_brand_pack.py,
mirroring test_find_layout_falls_back_to_toolkit. Uses the real toolkit
compound "card" (compounds/card.dsl) and asserts origin="toolkit".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Mike Mueller <mike@objektarium.de>
…agram-kind test) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
…tRenderer Adds lib/diagrams/renderer.py with: - Renderer Protocol (@runtime_checkable) with name, supports(src), render_png(src, out) - RoughRenderer: supports() inspects the .excalidraw JSON for freedraw/image/frame; delegates to render_rough.render_excalidraw. Falls back when deps unavailable. - PlaywrightRenderer: universal fallback for .excalidraw and .svg - _REGISTRY + choose_renderer(src) replacing the inline try/except dispatch - register_renderer(r, priority) for third-party injection render.py updated with docstring directing new code to choose_renderer; legacy render() function unchanged for backwards compatibility. 18 new tests in tests/test_renderer.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
Moved five modules to lib/io/ via git mv (history follows): lib/soffice.py → lib/io/soffice.py lib/dsl/pptx_to_png.py → lib/io/pptx_to_png.py lib/image_provider.py → lib/io/image_provider.py lib/image_preflight.py → lib/io/image_preflight.py lib/providers/ → lib/io/providers/ Updated all import sites: cli/, scripts/, tests/, lib/dsl/pptx_emit.py (relative ..image_provider refs → ..io.image_provider). Created lib/io/__init__.py (empty — no forced re-exports). 1124 tests pass (baseline 1106 + 18 from Task 2.1). Smoke ok. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
Adds lib/deck/picker.py with: - LayoutMatch frozen dataclass (layout_name, layout_path, score, reason) - LayoutPicker class wrapping lib.layout_picker.pick_layout: - candidates(slot_hint, top_k) → list[LayoutMatch] - pick(slot_hint) → LayoutMatch (raises ValueError when nothing matches) - Optional BrandPack for layout_path resolution via find_layout() cmd_pick in cli/deck.py updated to use LayoutPicker.candidates(). Legacy lib.layout_picker.pick_layout() untouched (no removal). 17 new tests in tests/test_deck_picker.py. 1141 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
Adds lib/deck/compose.py with: - Deck(brand, document) — thin orchestrator for the typed AST pipeline - Deck.from_dsl_text(text, brand) / Deck.from_dsl_path(path, brand) factory methods - Deck.build(out_path) → Path: expand_document + emit_pptx_from_document - Deck.diagnostics → DiagnosticBag (empty before build) The class wraps the Phase 1 typed-AST pipeline (parse_document → expand_document → emit_pptx_from_document). The legacy compile_slide + build_multi_slide plan-YAML path in cli/deck.py::cmd_build is unchanged. 10 new tests in tests/test_deck_compose.py. 1151 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
Extract _signals_from_slide, _resolve_layout_path, _slot_budgets_for_layout, _build_primitives_for_layout, _build_refurbished_deck, and _patch_set_hash from cli/deck.py into lib/deck/orchestrate.py. cli/deck.py now delegates to orchestrate functions; 192 lines of business logic removed from the CLI layer (2261 → 2081 LOC). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
Adds Deck.from_brief(brief_path, brand) classmethod that reads a deck plan YAML and returns a typed Deck via a new compose_from_brief() helper in lib.deck.orchestrate. Slot interpolation is not applied (placeholders preserved); full slot-fill stays in the legacy cmd_build pipeline. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
Signed-off-by: Mike Mueller <mike@objektarium.de>
…r carve-out Signed-off-by: Mike Mueller <mike@objektarium.de>
Signed-off-by: Mike Mueller <mike@objektarium.de>
Signed-off-by: Mike Mueller <mike@objektarium.de>
Signed-off-by: Mike Mueller <mike@objektarium.de>
Task 3.3 of the marketplace restructure. Establishes `feinschliff/` as the standalone core plugin (build, deck, ship) by: - Moving all core source modules (lib/ → feinschliff/, cli/ → cli/) from the staging `_feinschliff/` tree to `feinschliff/feinschliff/` - Moving layouts/, compounds/, brands/ (core set), assets/, schemas/ to `feinschliff/` plugin root - Rewriting all imports from `lib.X` / `cli.X` to `feinschliff.X` / `feinschliff.cli.X` - Adding uv workspace pyproject.toml at repo root and plugin-level pyproject.toml files for `feinschliff` and `feinschliff-builder` - Stripping builder subcommands (brand, compile, decompile, verify, verify_diagram, verify_quality) from core CLI main.py - Wrapping builder imports in lazy try/except with None fallbacks in pipeline.py and cli/deck.py; guarding callsites with `if X is not None:` - Updating cli/ship.py to call `feinschliff-builder` for gates 2/3 with graceful skip when builder not installed (detects ModuleNotFoundError) - Moving builder-specific tests to feinschliff-builder/tests/ - Adding pytest.skip guards and extra-brands fallback helpers in core tests that depend on brands from the not-yet-carved feinschliff-extra Test suite: 794 passed, 42 skipped, 1 xfailed (no regressions) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
Signed-off-by: Mike Mueller <mike@objektarium.de>
… [Task 3.4 cont] Signed-off-by: Mike Mueller <mike@objektarium.de>
…ce.json [Task 3.6] Signed-off-by: Mike Mueller <mike@objektarium.de>
Signed-off-by: Mike Mueller <mike@objektarium.de>
…vate APIs, dev-deps cleanup) - Fix 1+2: add _require_builder() helper in cli/deck.py; guard all 9 cmd_* entry-points that depend on feinschliff-builder with a clear user-facing error + SystemExit(2) instead of bare ImportError/TypeError - Fix 3: rename _check_slot_overflow → check_slot_overflow and _iter_slot_values → iter_slot_values in content_validator.py; update all callers in core (content_validator, slot_budget), builder (verify/static), and tests - Fix 4: rename diagrams/_text_metrics.py → diagrams/text_metrics.py; update all 4 core importers + builder's structural_validator cross-plugin import - Fix 5: drop imagehash, img2pdf, numpy, scikit-image from core's dev-deps (zero usage in feinschliff/ or tests/); keep them in feinschliff-builder - Fix 6: correct stale path comments in pipeline.py, verify.py docstring, brand/__init__.py docstring; advance defects.py planned-removal to Phase 4 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
Write end-user README for feinschliff (3 skills, 3 CLI subcommands, 3 brand packs), builder-author README for feinschliff-builder (2 skills, 6 CLI subcommands), brand catalog README for feinschliff-extra (10 brands with one-liners), and a marketplace overview at repo root explaining the three-plugin split. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
Add feinschliff/docs/brand-pack-contract.md — terse contract covering directory structure, required tokens.json groups, inheritance, layout and compound naming conventions, the 5 discovery sources, and distribution options (plugin or FEINSCHLIFF_BRAND_PATH). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
Rewrite all `lib.X` cross-references in docstrings and comments to use the canonical `feinschliff.X` / `feinschliff_builder.X` module paths. Affects ~40 hits across diagnostics, defects, dsl/, deck/, diagrams/, io/, brand_discovery, layout_budget, content_validator, and cli/deck.py. Also remove the now-unnecessary sys.path.insert() shim from feinschliff-builder/tests/conftest.py — the uv workspace setup makes both packages importable without it (confirmed: 307 tests still pass). Tighten feinschliff-extra plugin.json description to name the 10 brand packs rather than just saying "Extra brand packs for feinschliff." Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
The _feinschliff/ directory was the original single-package layout, retained as a reference during the three-phase marketplace split (core feinschliff + feinschliff-builder + feinschliff-extra). All Python source has been relocated; this removes the now-empty staging area. Tests: 817 core + 307 builder, all passing without the directory. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
Remove links to docs/ files that no longer exist (architecture.md, brand-system.md, dsl-grammar.md, port-your-brand.md), fix BRAND-PACK-CONTRACT.md case mismatch → brand-pack-contract.md, and drop hero-grid.png image refs whose docs/images/ directory was not ported. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
…kspace pyproject comment Signed-off-by: Mike Mueller <mike@objektarium.de>
…, deck.py extraction Three follow-ups from the post-restructure review: 1. cli/deck.py:cmd_build now prints a one-line stderr hint when feinschliff-builder is missing and notes-budget validation gets silently skipped. Suppressible via FEINSCHLIFF_QUIET_NOTES_BUDGET=1. 2. brand_discovery.py drops the legacy `Brand` dataclass (no in-repo callers; BrandPack has been the supported type since Phase 1). 3. cli/deck.py extracts log-event, timing, plan-skeleton, and plan-merge into cli/deck_subcommands/plan_log.py with a register(sub) entry point. deck.py shrinks 2116 → 1837 LOC (−13%) and establishes the pattern for migrating the remaining subcommands. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
…s, alpha, font inheritance, presets, color mods, …) Integrates the upstream improvements from #23 into the carved location (feinschliff-builder/.../pptx_svg_decompile.py). Applied via 3-way merge against the restructure branch-base; our local changes (namespace rewrites, discover_brands() refactor, brand-name cleanup in comments) all preserved cleanly with no conflicts. Upstream changes folded in: - font-size inheritance from layout/master (<p:txStyles>, lstStyle) - <a:alpha> on fills (pre-blend on white) - bar chart <c:catAx>/<c:valAx>/<c:dLbls>/<c:delete>/<c:showVal> - per-bar <c:dPt> colours - prstGeom presets (triangle/rtTriangle/diamond/parallelogram/ trapezoid/pentagon/hexagon/heptagon/octagon/chevron/{up,down,left, right}Arrow) - bar-chart legend + title gated on source presence - lumMod/lumOff/tint/shade in both color resolvers - pie legend corner-position normalisation (tr/tl/br/bl) - <c:firstSliceAng> on pie/doughnut - chart series + dPt colours skip nearest_token to preserve hex - value labels in horizontal stacked/percentStacked bars - <c:plotArea>/<c:manualLayout> for pie sizing Also drops the now-unused `os` import (env-path parsing was replaced by discover_brands() in the restructure carve). Net: +521 / -109 lines. Tests: 817 core + 307 builder = 1124 passing. Ruff clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
- ci.yml gains a feinschliff-builder job mirroring feinschliff (uv sync, ruff, pytest) so the carved plugin gets the same gate. Stale --ignore entries pointing at tests that moved to feinschliff-builder are dropped from the feinschliff job; new builder-side --ignore list covers render/refurbish/chrome tests that auto-skip in CI. - dsl/parser.py: add TYPE_CHECKING block for Document/Element so the forward-ref return annotations resolve under ruff. - tests/test_deck_compose.py::test_build_multi_slide_deck: drop the unused multi-slide Document scaffolding — parse_document doesn't yet recognise the `---` separator, so the test was building a doc it never used. Now smoke-tests Deck.build on _minimal_doc() with a comment marking the followup. - Remaining changes are auto-fix from `ruff check --fix`: unused imports / variables across both packages, plus a `import pytest; pytest.skip(...)` semicolon cleanup in test_verify_static.py. Both packages now ruff-clean and tests green: feinschliff: 799 passed (16 skipped, 4 deselected) feinschliff-builder: 291 passed (2 skipped) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
b5e9fac to
9b25b3f
Compare
Tests fail in the per-package CI setup because: 1. Cross-plugin brand tests reference brand names that live in feinschliff-extra (catppuccin-macchiato, nord, gruvbox-dark, feinschliff-dark). Per-package `uv sync` from feinschliff/ never surfaces them. Locally they're picked up via the plugin-marketplace walk (`~/.claude/plugins/.../feinschliff-extra/brands`), which CI has no equivalent of. 2. test_cli_ship calls `feinschliff ship`, which delegates to feinschliff-builder for the verify-quality leg of the pipeline. When only feinschliff is installed, ship exits 2 via `_require_builder`. Both jobs now `uv sync --all-packages --all-groups` at the workspace root and set `FEINSCHLIFF_BRAND_PATH=$GITHUB_WORKSPACE/feinschliff-extra/brands`, so discover_brands() finds the extra packs and the integrated ship pipeline has builder available. Verified locally with the exact CI commands: feinschliff 799 passed, feinschliff-builder 291 passed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
…act) main's branch protection lists "feinschliff lib tests" + "DCO sign-off" as required status checks. My earlier rename to "feinschliff tests" broke that contract — the PR sits at "Expected — Waiting for status to be reported" forever even though the job ran and passed. Restore the original display name. The new feinschliff-builder job continues as an additional check (not required by branch protection, but visible on every PR). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
Aligns the marketplace-split release with the next-minor bump from v0.1.0 (the final monolithic release tagged on main). Both packages previously carried 0.3.0 from an earlier copy-paste; resetting to 0.2.0 keeps the version line continuous. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Mike Mueller <mike@objektarium.de>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Splits the monolithic
feinschliffplugin into a three-plugin marketplace so end users can install only what they need:feinschliff(core)/deck,/excalidraw,/svgskills +feinschliffCLI. Ships 3 brand packs and 50 layouts. The everyday user's install.feinschliff-buildercompile-html,decompile,verify,improve-brand. Optional, lazily loaded from core.feinschliff-extraSplitting them keeps the core plugin lean and avoids installing build tools that most end users will never run.
Architecture improvements rolled into the move
This wasn't just a directory shuffle. Phases 0–2 took the chance to introduce proper interfaces and remove ambient state before phase 3 carved the code into the new homes:
BrandPack(feinschliff/brand/pack.py) — frozen dataclass replacing the legacyBrandshape; resolves layouts/compounds via discovery.DocumentAST (feinschliff/dsl/ast.py) — typedDocument/Slide/Element/ElementKindthat the parser/expander/emitter operate on.DiagnosticBag(feinschliff/diagnostics.py) — single defect container withhas_errors()semantics, replacing the scatteredstatic_verify() → listcalls.RendererProtocol (feinschliff/diagrams/renderer.py) —RoughRenderer(pure Python, ~150 ms) andPlaywrightRenderer(headless Chromium fallback) implement a common interface; the dispatcher picks based on document features.feinschliff/io/— image providers, preflight, and asset I/O consolidated under one package.Deck.from_brief(feinschliff/deck/orchestrate.py) — high-level entry that picks layouts, composes aDocument, and renders a PPTX from one YAML.layout_discovery.py— mirrorsbrand_discovery.py; replaces hardcoded layout paths throughout the pipeline so brand packs and layouts both surface via the same plugin-walk.After phase 3 the core's
cli/deck.pylazily imports fromfeinschliff_builderand gates each builder-dependent subcommand with a_require_builder("<feature>")helper that prints a friendly install hint onSystemExit(2).Top-level re-exports
…works without reaching into internal modules. Useful for external Python consumers and for the builder package.
Review follow-ups landed
Three smaller cleanups from the post-restructure review made it into this PR:
feinschliff-builderis missing,deck buildnow prints a one-line stderr note that notes-budget validation is being skipped (suppressible viaFEINSCHLIFF_QUIET_NOTES_BUDGET=1).Branddataclass removed frombrand_discovery.py(no callers;BrandPackis the supported type).cli/deck.pyextraction —log-event,timing,plan-skeleton,plan-mergemoved intocli/deck_subcommands/plan_log.pywith aregister(sub)entry point.deck.pyshrunk 2116 → 1837 LOC (−13%) and the pattern is in place for further migrations.Upstream integration
PR #23 (13 commits of XML deep-read fixes for
pptx_svg_decompile.py— chart-axis visibility,<a:alpha>blending, font-size inheritance, prstGeom presets, lumMod/lumOff/tint/shade, pie legend normalisation, plotArea/manualLayout, per-bar<c:dPt>colours) was integrated via path-rewritten 3-way merge into the carved location with no conflicts.CI
feinschliff-builderjob mirrors the existingfeinschliffjob:uv sync --group dev→ ruff → pytest. Stale--ignoreentries pointing at tests that moved into the builder were dropped from the core job.Signed-off-byline.Marketplace + workspace metadata
.claude-plugin/marketplace.jsonlists all three plugins with their sources; eachsourcepoints to a valid directory whoseplugin.jsonname matches the marketplace entry.pyproject.tomldeclaresfeinschliff+feinschliff-builderas uv workspace members;feinschliff-extrais intentionally excluded (no Python, no pyproject)..claude-plugin/plugin.jsonandREADME.md.Test plan
uv run --package feinschliff pytest tests— 817 passed (19 skipped, 1 xfailed)uv run --package feinschliff-builder pytest tests— 307 passed (5 skipped)uv run ruff check .clean in both packagesfeinschliff deck --helpshows all 17 subcommandsfeinschliff deck log-eventend-to-end smoke test writestiming.jsonlcorrectlyBSH/Boschreferences in any tracked filefeinschliff-extravia plugin-marketplace walkFollowups (intentionally out of scope)
cli/deck.pyfurther extraction (12 more subcommand groups;plan_log.pyestablishes the pattern)---separator (test currently smoke-tests single-slide only with a comment marker)🤖 Generated with Claude Code