Skip to content

Python API

The library behind every surface. Import from the top level:

from kumihimo import Plan, braid, export, KumihimoError

plan = Plan.load("myplan/")
findings = plan.check()  # list[Finding], errors first
prompt = plan.braid(strategy="grouped", where={"status": "todo"})
mermaid_source = export.mermaid(plan)

Mutations go through the ops layer — the same functions the CLI, editor, and MCP server call:

from kumihimo.core import ops

ops.add_node(plan.root, "cache", "task", needs=("api-endpoints",))
ops.link(plan.root, "cache", to="redis-outage", rel="threatened-by")
ops.rename_node(plan.root, "cache", "response-cache")

Every op loads fresh from disk, writes atomically, and returns the reloaded result; structural mistakes (dangling targets, cycle-closing edges, id collisions) raise KumihimoError with a printable message.

Plan

A loaded plan: manifest, resolved kinds, node records, findings.

@purpose The one aggregate every client holds; a snapshot of disk that knows how to write its own changes back. @tags plan, aggregate

manifest property

The parsed manifest.

@purpose Compile settings and plan meta for clients and the braid.

nodes property

Nodes by id.

@purpose The read-only view almost every consumer wants.

records property

Node records by id, in sorted-file order.

@purpose The mutable layer ops work on; most readers want .nodes instead.

root property

The plan directory.

@purpose Clients need it for messages and relative paths.

__init__(loaded, kinds, kind_findings)

Wire a loaded plan to its resolved kind system.

@purpose Constructor for Plan.load; direct use is for tests.

braid(**kwargs)

Compile this plan (or a slice) into one prompt; see compile.braid.

@purpose The public sugar over the pipeline — accepts strategy, where, from_, until, in_, diagram, dry; returns the woven text.

check()

Everything wrong or suspicious about the plan, errors first.

@purpose Load findings plus every rule in validate.py, in deterministic order — the one validation answer every surface renders.

load(path) classmethod

Load the plan directory at path.

@purpose The library's front door; content problems become findings on the returned Plan, only "not a plan" raises.

node(node_id)

One node by id, or a clean error naming it.

@purpose KeyError with context, as a KumihimoError clients can print.

save()

Write every dirty record; return the rel paths written.

@purpose Fidelity contract surface: an untouched plan saves to an empty list and zero writes.

The braid

Compile a plan (or a slice of it) into one deterministic prompt.

@purpose The whole point of the tool, as one function: same plan and arguments in, byte-identical text out. @tags braid, pipeline

Everything one braid produced.

@purpose Clients that want more than the text (the editor's preview, tests, --dry) get the structure without re-deriving it.

Operations

@file kumihimo/core/ops.py @purpose The one mutation path (invariant 1): add, update, link, unlink, rename, remove — each loads fresh from disk, edits the record's live frontmatter map so comments survive, refuses structural nonsense (dangling targets, cycles, id collisions) with clean errors, saves atomically, and returns the reloaded result. @layer core @tags ops, mutations, invariant-1, referrer-fixup @related kumihimo/core/store.py (the records and saves), kumihimo/core/graph.py (the cycle guard on link), kumihimo/core/plan.py (Plan.load used before and after) @design PLAN.md §7.1 invariant 1, queue item K5

add_node(root, node_id, kind, *, title=None, body='', fields=None, needs=(), in_=())

Create a node file with canonical frontmatter and return it.

@purpose The only way tools bring a node into existence; every edge target must already exist and the id must be free. @tags ops, add

Draw one edge from src: a dependency, a membership, or an annotation.

@purpose Exactly one edge per call; needs-edges are refused (with the path) when they would close a cycle, so no tool can write one. @tags ops, link, cycle-guard

remove_node(root, node_id, *, force=False)

Delete a node; with force, strip every reference to it first.

@purpose A referenced node refuses to die quietly — the error names the referrers, and force removes the edges in the same operation so the plan is never left dangling. @tags ops, remove

rename_node(root, old, new)

Move a node to a new id, fixing every referrer and the view layout.

@purpose Renames are safe or they don't happen: the renamed file's bytes never change (the id is the filename), and no reference is left pointing at the old name. @tags ops, rename, referrer-fixup

Remove one edge from src.

@purpose The inverse of link; removing an absent edge is an error, not a shrug, so tools notice their own stale state. @tags ops, unlink

update_node(root, node_id, *, kind=None, title=None, body=None, priority=None, set_fields=None, unset_fields=())

Change a node's kind, title, body, priority, or kind-defined fields.

@purpose Field values stay permissive (check reports schema breaches); structure stays strict (reserved keys are not fields, kinds must exist). @tags ops, update

Model

@file kumihimo/core/model.py @purpose The pure data model: nodes with their two semantic edge kinds and annotation links, findings, field specs, kind definitions, and the manifest. No IO, no behaviour beyond validation and defaults. @layer core @tags model, node, edges, kinds, manifest, findings @related kumihimo/core/store.py (reads/writes these from disk), kumihimo/core/kinds.py (resolves and validates kind fields) @design PLAN.md §3.1-3.2

CompileSettings

Bases: BaseModel

Plan-level braid defaults from the manifest.

@purpose The user's standing answers to "how should this compile" so the CLI flags are overrides, not requirements.

FieldSpec

Bases: BaseModel

Schema for one kind-defined field.

@purpose Small enough to author by hand in YAML, rich enough to drive validation now and editor forms/JSON Schema later.

Finding

Bases: BaseModel

One validation result, error or warning, tied to where it was found.

@purpose The unit check returns everywhere — CLI table, editor panel, MCP — so every surface reports identically.

render()

One-line human form.

@purpose Shared formatting so CLI and logs agree.

KindDef

Bases: BaseModel

A node kind: its field schemas and (from M2) its render template.

@purpose Where node meaning lives, per the generic/opinionated line — the compiler never reads these fields directly, templates do.

Bases: BaseModel

An annotation edge: free-form relation, zero compiler semantics.

@purpose The pressure valve of the model — any relationship users invent fits here without core changes (PLAN.md §3.1).

Manifest

Bases: BaseModel

Parsed kumihimo.yaml: plan meta, kind pack + overrides, compile defaults.

@purpose Everything plan-wide in one validated object; raw kind overrides stay unparsed here and resolve in kinds.resolve_kinds.

Node

Bases: BaseModel

One thread of the braid: identity, prose, order, membership, annotation.

@purpose The five things core understands about a node; everything else lives in the kind-validated fields bag. @tags node, needs, membership

default_title(node_id)

Humanize an id into a display title: last segment, dashes to spaces.

@purpose Titles are optional in frontmatter; every node still renders with one.