Skip to content

kumiki.librarian

Flat import path

kumiki/__init__.py re-exports everything on this page via from kumiki import *, so every name below is also available directly as kumiki.Param -- you do not need to import from the submodule path shown in the heading above.

kumiki.librarian

Librarian: discovery and loading of kumiki frame examples and pattern books.

This module owns the full librarian stack: AST-driven discovery, scanning, pattern index files, and layered search roots. The JS bridge never reads any of this directly — it always goes through the librarian CLI (:mod:kumiki.librarian_cli).

Frame and pattern-list detection rules (see analyze_source)

Detection never imports the file — it's pure ast inspection. A module-level statement is recognized as a frame entry if either of these holds:

  • Type-based (the primary path): the statement's type resolves to kumiki's canonical Frame (through aliases like from kumiki import Frame as F, dotted forms like kumiki.timber.Frame, or string annotations):

  • name: Frame = ... — annotation is Frame

  • name = Frame(...) / name = Frame.from_joints(...) — RHS call constructs a Frame (checked even when the annotation, if any, isn't Frame)
  • def foo(...) -> Frame: — return annotation is Frame

  • Name-based fallback (unconditional, ignores types entirely): a target or function literally named example or build_frame is always treated as a frame entry, whether or not it's annotated -> Frame. This is why docs/agent_usage_instructions.md can say "just call it example" as the simple convention, while the type-based rule is what actually backs it.

A module-level statement is recognized as a pattern list entry only by name: patterns = [...] or patterns: ... = [...], list literal or call, no content type-checking at this stage (that happens after import, in _resolve_pattern_list).

When a file has multiple frame entries, ModuleStaticInfo.chosen_frame is the last one in source order — that's what downstream consumers render.

Two-phase scan (see _scan_single_file)

  1. Static AST analysis always runs first and is cheap (no import).
  2. A file is only actually imported (exec_module) if its static info has a patterns = [...] candidate (needs_import = bool(static_info.pattern_lists)). Frame-only files are never imported during a scan — only their static info (names/kinds/line numbers) is captured. Building the real Frame object (calling example()/build_frame()) is deferred entirely to the runner, on demand, when a specific file is opened/rendered.

Pattern index files: the caching path

build_pattern_index / refresh_pattern_index / read_pattern_index / write_pattern_index maintain a JSON cache (keyed by per-file sha256) of the same scan results above, so unchanged files don't need to be re-scanned or re-imported:

  • build_pattern_index(root, prior_index=...) — for each file, reuses the prior entry verbatim if its sha256 still matches; only changed/new files get a real scan. Returns both a per-file entries dict (each entry has its own frames/chosen_frame_name/patternbook) and a flattened, path-sorted frame_examples summary list, mirroring what build_scan_index returns for a live scan.
  • refresh_pattern_index(root, index_path) — reads the index already on disk at index_path as prior_index, rebuilds against root, writes the result back to index_path. This is how to keep an index file up to date: call it (or python -m kumiki.librarian_cli refresh-index <root> <index_path>) whenever the source tree may have changed, instead of hand-editing or blowing away the cache.
  • load_or_build_pattern_index_for_root (used by scan_all_roots) is the actual "check for an index file first" behavior: for the kumiki / dependency search roots it trusts a bundled _pattern_index.json as-is with no sha-diffing at all (installed packages are assumed immutable — that file is what tools/build_pattern_index.py generates at release time and ships in the wheel). Workspace roots always get a fresh build_pattern_index call instead.

Caveat: as of this writing the live kigumi extension bridge (kigumi/frame-scanner.js) only ever calls the CLI's scan-workspace action (→ scan_workspace_indexscan_library_folder), which does a full live AST (+ import-if-needed) scan every time and does not consult any pattern index file. The index/cache machinery above is real and tested, but currently only exercised for the bundled kumiki/dependency roots via scan_all_roots — not (yet) for the workspace's own live-edited files.

Layered search roots

discover_search_roots returns, in order: the workspace, the installed kumiki package directory, and any explicitly declared kumiki-aware dependencies. A dependency is only included if it's both declared in .kigumi/config.json (kumiki_dependencies) and its installed metadata's Requires-Dist actually lists kumiki.

Param dataclass

Param(default: Any, description: str = '', kind: Optional[Literal['number', 'boolean', 'string', 'enum', 'v3']] = None, options: Optional[Tuple[str, ...]] = None, minimum: Optional[Any] = None, maximum: Optional[Any] = None, optional: Optional[bool] = None)

Author-facing parameter declaration helper.

Use as a callable default value, e.g.:

def build_frame(width=Param(scalar(2), description="Timber width")):
    ...

default instance-attribute

default: Any

description class-attribute instance-attribute

description: str = ''

kind class-attribute instance-attribute

kind: Optional[Literal['number', 'boolean', 'string', 'enum', 'v3']] = None

options class-attribute instance-attribute

options: Optional[Tuple[str, ...]] = None

minimum class-attribute instance-attribute

minimum: Optional[Any] = None

maximum class-attribute instance-attribute

maximum: Optional[Any] = None

optional class-attribute instance-attribute

optional: Optional[bool] = None