← WorldkeepCONTENT HISTORY

Update to Worldkeep

Snapshot Sep 30, 2026 · 23:14 UTC · version 0.3.3

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "description": "Turn a worldbuilding canon folder (one containing world.yaml) into a rendered picture — a graph you can open in a browser. Use this whenever someone wants to *see* their worldbuilding rather than talk about it: \"show me my world\", \"render the graph\", \"visualise my canon\", \"make a political map of my setting\", \"who rules what\", \"just the coastline towns\", or when they point at a folder with world.yaml and ask what's in it. Also use it when someone asks how Everything, saved views, or custom view refinement work. Do not use it for capturing or extending canon — adding entities, ideas, actions, or relations is the worldbuilding-scribe's job, not this skill's.",
  "included_files": [
    {
      "relative_path": "assets/views-library/canon-only.yaml",
      "size_in_bytes": 103
    },
    {
      "relative_path": "assets/views-library/groups.yaml",
      "size_in_bytes": 711
    },
    {
      "relative_path": "scripts/_vendor/PyYAML-LICENSE.txt",
      "size_in_bytes": 1101
    },
    {
      "relative_path": "scripts/_vendor/yaml/__init__.py",
      "size_in_bytes": 12311
    },
    {
      "relative_path": "scripts/_vendor/yaml/composer.py",
      "size_in_bytes": 4883
    },
    {
      "relative_path": "scripts/_vendor/yaml/constructor.py",
      "size_in_bytes": 28639
    },
    {
      "relative_path": "scripts/_vendor/yaml/cyaml.py",
      "size_in_bytes": 3851
    },
    {
      "relative_path": "scripts/_vendor/yaml/dumper.py",
      "size_in_bytes": 2837
    },
    {
      "relative_path": "scripts/_vendor/yaml/emitter.py",
      "size_in_bytes": 43006
    },
    {
      "relative_path": "scripts/_vendor/yaml/error.py",
      "size_in_bytes": 2533
    },
    {
      "relative_path": "scripts/_vendor/yaml/events.py",
      "size_in_bytes": 2445
    },
    {
      "relative_path": "scripts/_vendor/yaml/loader.py",
      "size_in_bytes": 2061
    },
    {
      "relative_path": "scripts/_vendor/yaml/nodes.py",
      "size_in_bytes": 1440
    },
    {
      "relative_path": "scripts/_vendor/yaml/parser.py",
      "size_in_bytes": 25495
    },
    {
      "relative_path": "scripts/_vendor/yaml/reader.py",
      "size_in_bytes": 6794
    },
    {
      "relative_path": "scripts/_vendor/yaml/representer.py",
      "size_in_bytes": 14190
    },
    {
      "relative_path": "scripts/_vendor/yaml/resolver.py",
      "size_in_bytes": 9004
    },
    {
      "relative_path": "scripts/_vendor/yaml/scanner.py",
      "size_in_bytes": 51279
    },
    {
      "relative_path": "scripts/_vendor/yaml/serializer.py",
      "size_in_bytes": 4165
    },
    {
      "relative_path": "scripts/_vendor/yaml/tokens.py",
      "size_in_bytes": 2573
    },
    {
      "relative_path": "scripts/run-python.ps1",
      "size_in_bytes": 3423
    },
    {
      "relative_path": "scripts/run-python.sh",
      "size_in_bytes": 838
    },
    {
      "relative_path": "scripts/vendor/README.md",
      "size_in_bytes": 825
    },
    {
      "relative_path": "scripts/vendor/cose-base.js",
      "size_in_bytes": 118906
    },
    {
      "relative_path": "scripts/vendor/cytoscape-dagre.min.js",
      "size_in_bytes": 45620
    },
    {
      "relative_path": "scripts/vendor/cytoscape-fcose.js",
      "size_in_bytes": 57239
    },
    {
      "relative_path": "scripts/vendor/cytoscape.min.js",
      "size_in_bytes": 435328
    },
    {
      "relative_path": "scripts/vendor/layout-base.js",
      "size_in_bytes": 147898
    },
    {
      "relative_path": "scripts/vendor/licenses/cose-base-LICENSE.txt",
      "size_in_bytes": 1081
    },
    {
      "relative_path": "scripts/vendor/licenses/cytoscape-LICENSE.txt",
      "size_in_bytes": 1082
    },
    {
      "relative_path": "scripts/vendor/licenses/cytoscape-dagre-LICENSE.txt",
      "size_in_bytes": 1093
    },
    {
      "relative_path": "scripts/vendor/licenses/cytoscape-fcose-LICENSE.txt",
      "size_in_bytes": 1079
    },
    {
      "relative_path": "scripts/vendor/licenses/layout-base-LICENSE.txt",
      "size_in_bytes": 1069
    },
    {
      "relative_path": "scripts/view.py",
      "size_in_bytes": 7222
    },
    {
      "relative_path": "scripts/viewer/__init__.py",
      "size_in_bytes": 575
    },
    {
      "relative_path": "scripts/viewer/compile.py",
      "size_in_bytes": 39977
    },
    {
      "relative_path": "scripts/viewer/explain.py",
      "size_in_bytes": 10000
    },
    {
      "relative_path": "scripts/viewer/load.py",
      "size_in_bytes": 12830
    },
    {
      "relative_path": "scripts/viewer/modules.py",
      "size_in_bytes": 18231
    },
    {
      "relative_path": "scripts/viewer/project.py",
      "size_in_bytes": 44970
    },
    {
      "relative_path": "scripts/viewer/render_graph.py",
      "size_in_bytes": 58883
    },
    {
      "relative_path": "scripts/viewer/validate_view.py",
      "size_in_bytes": 14963
    },
    {
      "relative_path": "scripts/wb.py",
      "size_in_bytes": 19704
    },
    {
      "relative_path": "scripts/wblib/__init__.py",
      "size_in_bytes": 58
    },
    {
      "relative_path": "scripts/wblib/capture_report.py",
      "size_in_bytes": 7536
    },
    {
      "relative_path": "scripts/wblib/context.py",
      "size_in_bytes": 15301
    },
    {
      "relative_path": "scripts/wblib/delegate.py",
      "size_in_bytes": 8238
    },
    {
      "relative_path": "scripts/wblib/discovery.py",
      "size_in_bytes": 4637
    },
    {
      "relative_path": "scripts/wblib/mergeable.py",
      "size_in_bytes": 7464
    },
    {
      "relative_path": "scripts/wblib/paths.py",
      "size_in_bytes": 7140
    },
    {
      "relative_path": "scripts/wblib/scribe_config.py",
      "size_in_bytes": 3951
    },
    {
      "relative_path": "scripts/wblib/session.py",
      "size_in_bytes": 12920
    }
  ],
  "name": "canon-viewer",
  "skill_md_contents": "---\nname: canon-viewer\ndescription: >-\n  Turn a worldbuilding canon folder (one containing world.yaml) into a\n  rendered picture — a graph you can open in a browser. Use this whenever\n  someone wants to *see* their worldbuilding rather than talk about it:\n  \"show me my world\", \"render the graph\", \"visualise my canon\", \"make a\n  political map of my setting\", \"who rules what\", \"just the coastline\n  towns\", or when they point at a folder with world.yaml and ask what's in\n  it. Also use it when someone asks how Everything, saved views, or custom\n  view refinement work. Do not use it for capturing or extending canon —\n  adding entities, ideas, actions, or relations is the\n  worldbuilding-scribe's job, not this skill's.\n---\n\n# Canon viewer\n\nAll `assets/` and `scripts/` paths in this skill are relative to the directory\ncontaining this `SKILL.md`.\n\nYou turn a sentence into a picture. The author says what they want to see —\n\"only the religious conflicts\", \"who rules what\", \"just the coastline\ntowns\" — and your job is to find or write the **view** that shows it, then\nrender it. Running `view.py` is the easy, mechanical last step; picking or\nauthoring the right view is the actual work.\n\nWhen presenting the first render in a conversation, explain the boundary in\none compact sentence: **Everything** is the neutral audit of active canon and\nmay be crowded, while named custom views are selective interpretations that\nthe author should inspect and refine. If the graph records the wrong meaning,\nroute the correction to the scribe; if the canon is right but the picture is\nmisleading or incomplete, refine the named view. When the author asks for\nhelp, offer the public [viewer guide](https://github.com/vadim-chiriac/worldkeep/blob/main/Documentation/VIEWER-GUIDE.md).\n\n**What this skill must never do:** write or edit anything in `entities/`,\n`ideas/`, `actions/`, `relations/`, or `types/`. Those are canon and belong\nto the scribe — including `lens:` blocks, which live in type files. If a\nview would look better with a lens on some type, say so and let the author\ntake it to the scribe. The only files this skill creates are `views/*.yaml`,\nreusable `view-modules/*.yaml`, and — only when explicitly asked — an\nadjacent `views/<stem>.view.lock.yaml`.\n\n## Start with one command\n\n```\n& scripts/run-python.ps1 wb.py session <path> --task view\n```\n\n`wb` is the agent-facing entrypoint; run it through the same bundled launcher\nas everything else here. One read resolves the canon, names the world, reports\nKernel/tool versions and any compatibility problem, counts artifacts by kind\nand status, lists the type vocabulary, and lists the named views and view\nmodules already available — which is most of what picking a view depends on.\nAdd `--query \"<phrase>\"` for a small relevant-context section and `--json` for\na stable machine-readable form.\n\nPass the canon folder or a folder bounding exactly one; several worlds are\nlisted rather than chosen between. It is read-only.\n\nThen use `wb` for the work itself:\n\n```\n& scripts/run-python.ps1 wb.py view <canon> --all-views --vendor --output <out>.html\n& scripts/run-python.ps1 wb.py view <canon> --everything --vendor --output <out>.html\n& scripts/run-python.ps1 wb.py view <canon> --view views/<name>.yaml --vendor --output <out>.html\n& scripts/run-python.ps1 wb.py view <canon> --list-views\n& scripts/run-python.ps1 wb.py validate <canon> --view views/<name>.yaml\n& scripts/run-python.ps1 wb.py explain <canon> --view views/<name>.yaml --artifact entities/<id>\n& scripts/run-python.ps1 wb.py context <canon> --query \"<name>\"\n```\n\nThese wrap the viewer below without changing any of its behaviour, including\nthe built-in `Everything` contract. The lower-level `view.py` invocations\nremain valid and unchanged if you need them.\n\n## Which folder, which view\n\nFind the canon folder the same way the scribe would: a folder containing\n`world.yaml`. If the author named one, use it; if not and there's exactly\none nearby, use it and say so. `wb session` does this resolution for you and\nreports what it chose.\n\n**Default to every view.** \"Show me my world\" and anything like it renders\n`--all-views`: built-in Everything plus every view saved under `views/` —\nthe defaults that shipped with the world and any custom ones the author has\nsaved since. One document, all tabs, no choosing on the author's behalf.\n\nRender a **single** view only when the author expressly asks for one, when a\nnew view is being previewed before it is saved, or when the request obviously\nnames one (\"just the canon\", \"who's in what group\"). If it isn't clear, render\nall of them and say which one answers the question.\n\nThe three defaults are deliberately few:\n\n- **Everything** — built in, always exists, selected with `--everything`. It\n  needs no `views/` folder or copied recipe and is uncustomized: it ignores\n  view/type-file lenses, styles, emphasis, and direction, and its filters are\n  temporary to the browser session. Do not save filters into it.\n- **Canon only** — the same world with drafts dropped.\n- **Groups** — membership and command over the *default* group vocabulary\n  only (`part_of/membership`, `subordinate_to`, `participates`, `part_of`).\n  Say this out loud when it looks emptier than the author expects: a world\n  whose grouping lives in custom relation types will not appear here, and the\n  fix is a new named view, not widening this one.\n\nThen check `views/` beside `world.yaml` for durable interpretations:\n\n- **No `views/` folder** — render `--everything`; create a named view only\n  after the author approves a durable custom interpretation.\n- **Views exist** — render `--all-views` by default; run `--list-views` first\n  only when you need to name what's there or pick one deliberately.\n- **Nothing existing fits** — write a new `views/<name>.yaml`. This is the\n  main path, not a fallback: most interesting requests (\"just the coastline\n  towns\", \"who rules what\") don't match a pre-made view.\n\n## Rendering\n\nKeep runtime discovery invisible. The YAML dependency is bundled. On Windows\ninvoke the viewer through `scripts/run-python.ps1`; on macOS/Linux use\n`sh scripts/run-python.sh`. Do not mention Python, launchers, dependencies, or\nsetup while either bundled route remains available. Only surface the\nlauncher's single blocker after it has exhausted every candidate.\n\n```\n& scripts/run-python.ps1 view.py <canon> --all-views --vendor --output <out>.html   # default\n& scripts/run-python.ps1 view.py <canon> --everything --vendor --output <out>.html\n& scripts/run-python.ps1 view.py <canon> --view views/<name>.yaml --vendor --output <out>.html\n& scripts/run-python.ps1 view.py <canon> --list-views\n& scripts/run-python.ps1 view.py <canon> --json   # inspect the projection, no render\n& scripts/run-python.ps1 view.py <canon> --validate-view views/<name>.yaml\n& scripts/run-python.ps1 view.py <canon> --explain-view views/<name>.yaml --artifact entities/<id>\n```\n\n`--validate-view` and `--explain-view` never generate HTML, so they are cheap\nto run while iterating. Both accept `--json` for a stable machine-readable\nform. `--validate-view` exits `1` when the view is invalid.\n\nRead the style-rule match counts in validation too. A literal type such as\n`place` intentionally does not style `place/settlement`; validation notices\nselected descendants that a literal rule misses and suggests a separate\n`place/*` rule, without widening anything automatically.\n\nAlways pass `--vendor` for skill-driven renders — the HTML should still\nopen with no network in ten years. Use `--json` to diagnose a missing node\nwithout a browser: it dumps the exact node/edge set that would render.\n\n## Writing a view file\n\nA view is YAML in `<canon>/views/`. **It is not a canon artifact** — no\n`kind`, never validated by `validate.py`. Compact schema:\n\n```yaml\nname: \"Political map — who rules what\"\nrender: graph\nselect:\n  kinds: [entity, relation]        # optional; default: everything\n  types: [place/*, community/*]    # glob on the type path\n  status: [canon, draft]           # default: canon + draft\n  relation_members_only: true      # optional: keep only members of selected relations\n  where_under: entities/the-realm  # optional: place-chain filter\n  connected_to_kinds: [idea]       # optional: keep these kinds even isolated\n  connected_to_types: [person, person/*] # optional: typed anchors plus direct neighbours\nedges:\n  include: [subordinate_to, part_of/membership]\n  exclude: []\nlayout: dagre                      # dagre | fcose | concentric | preset\nemphasis:\n  color_by: valence\n  size_by: weight\n```\n\nUnspecified keys take sane defaults: everything selected, all relation\ntypes included, `fcose` layout. Start from the closest file in\n`views-library/` — `canon-only.yaml` or `groups.yaml` — and narrow\n`select`/`edges` rather than writing one from nothing.\n\nUse ordinary `select.types` for a category view. Use\n`connected_to_types` when the request is anchors plus their direct related\nartifacts: people and affiliations, polities and what they control, or texts\nand ideas they discuss. Make `select.types` broad enough to admit anchors and\npossible neighbours, use `edges.include` to name the relation meanings, then\nlet the typed anchors prune unrelated eligible artifacts. The field accepts the\nsame exact paths/globs as `types`; use both `person` and `person/*` when both\nare wanted. It is one hop only and does not justify changing canon, tags, or\ntypes just to force a viewer result. Save the outcome as an ordinary durable\nview and explain its selection in plain language.\n\nUse the view-local selector `relation_members_only: true` (in ordinary or\ncomposed views, not a selection-module field) for a relation-driven view such as Groups:\nafter the selected relation families are known, unrelated isolated entities\nand ideas are removed while their directly named members remain. Ordinary\nkind/type/status filters still win, and no matching relation produces an empty\nprojection.\n\nEvery rendered view has a local **Filter this view** panel with exact types\nused in that projection. It is presentation-only and resets on a named-view\nswitch. During the viewer session, filtering preserves known node positions\nand the current pan/zoom viewport; restored nodes return to their prior\nlocations. Hiding an intermediate container keeps what was inside it nested:\nthe contents re-attach to the nearest visible container, but only along an\nunbroken run of one relation type, because a custom nesting type need not\ncompose the way `part_of` does. Every view also has **Find by name**, which\ndims all but the matches and lists them; the match set survives an empty query,\nand a toggle controls whether it is painted on the graph. Use `Everything` plus its filters as the independent audit tool; a\ncustom view is a saved interpretation, not a claim to contain all relevant\nartifacts. If a request adds any durable selection, style, emphasis, layout,\nor lens behavior, preview a differently named custom view, explain it, and\nask for approval before saving it. Never create `views/everything.yaml` (or\n`.yml`) or name a view `Everything` in any casing: that reserved built-in\ncannot be customized. Migrate an old such recipe to a descriptive name (for\nexample `Styled world overview`) and retain its named-view lens behavior.\n\nThe inspector groups each connection into one relation card, preserving the\ncanon member order and roles; every relation card opens even when its relation\nis represented as an edge, chip, or hidden structural rule. **Focus relation** temporarily draws only that\nrelation and its participants; **Focus neighborhood** draws an artifact, its\ndirect relations, and their participants. Both are one-hop browser-only aids:\nthey honour the active view and filters, never reveal excluded canon, and clear\nfrom the right-hand inspector or on a view switch. The collapsible legend\ndescribes the final styles actually on screen; never treat it as a statement of\ncanon meaning or provenance. Everything labels standard state relation nodes\nwith their structured value or amount without applying any custom lens.\n\n## Composing reusable modules\n\nWhen a request keeps reappearing — \"the same people set again\", \"our faction\ncolours\" — lift that one concern into a **view module** and compose it. A\nmodule is a flat, typed YAML file directly under `<canon>/view-modules/`.\nModules never import each other and never reach outside that folder.\n\n**Classify the request before writing anything.** Almost every viewer request\nis a mix of four independent concerns, and each belongs to a different module\nkind:\n\n| the author is asking about | concern | module `kind` | payload |\n|---|---|---|---|\n| *which artifacts* are on the page | selection | `selection` | `select:` |\n| *which relationships* are drawn | relation policy | `relation` | `edges:` |\n| what things *look like* | style | `style` | `rules:` |\n| how a relation is *structured* — nested, chipped, directed | lens | `lens` | `overlays:` |\n\nSay the classification out loud before composing. \"Only guild members, in\nfaction colours, with leadership nested\" is three concerns, not one request,\nand it composes as three modules.\n\n```yaml\n# view-modules/people.yaml\nschema: wb.view-module/v1\nid: people\nversion: 1\nkind: selection\nselect:\n  kinds: [entity, relation]\n  types: [person, person/*]\n```\n\nA named view then composes them by id:\n\n```yaml\nname: \"People — affiliations and leadership\"\ncompose:\n  selection:\n    any_of: [people, organizations]   # union\n    all_of: [active-subjects]         # intersect the union\n    exclude: [private-notes]          # subtract; nothing resurrects these\n  relations:\n    include: [affiliations, leadership]\n    exclude: [superseded-links]\n  styles: [faction-colors]            # applied in this order\n  lenses: [organization-containment]\nselect:                               # optional: narrows only, never widens\n  status: [canon]\n```\n\nRules worth stating to the author because they surprise people:\n\n- **Exclusion always wins.** A view-local `select` or `edges.include` can only\n  narrow a composed result; it can never bring back something a module\n  excluded. The compiler warns when you try.\n- **Selection and relation policy are separate.** A selection module scoped to\n  entities does not suppress the relation modules you composed.\n- **Naming a relation module is what widens the picture.** With no explicit\n  include, the relation set is just a default and you get the *induced\n  subgraph* — the relations whose endpoints you already selected, and nothing\n  else. So `any_of: [people]` alone draws people and the links among them, not\n  every organization and place they touch. Add `relations: include: [...]`\n  (or a view-local `edges.include`) when you do want the relationship to pull\n  its far endpoint onto the page; those endpoints are reported as endpoint\n  completions, not as things the selection chose. A style-only composition\n  narrows nothing, so it still shows the whole graph.\n- **Styles settle presentation last; lenses own structure.** `as` and\n  `direction` may only appear in a `lens` module or the view's own `lenses:`.\n- **Two lens modules that disagree on `as` or `direction` are an error**, not a\n  silent winner. Resolve it with a view-local `lenses:` rule naming that exact\n  relation type — a `*` wildcard deliberately does not count. Until it is\n  resolved, the view still renders, but through a loud `UNVALIDATED FALLBACK`\n  warning, and it cannot be locked.\n\n### Before you save anything\n\nCompose and preview first, then validate, then ask. In order:\n\n1. Classify the request into the four concerns and say so.\n2. Compile and preview a named view — never touch `Everything`.\n3. Run `--validate-view`. Fix errors; do not weaken the recipe to dodge them.\n4. Run `--explain-view --artifact <id>` for anything that surprised the author\n   — a missing node, an unexpected colour, a relation that vanished. The trace\n   names the module or local rule responsible for each decision.\n5. **Ask before saving.** Durable view files, reusable modules, and lock files\n   are all things the author has to live with. Save only what they approved,\n   and report where each piece came from.\n\nA lock (`--write-lock`) records the modules, view, and type-lens inputs behind\na successful validation. It is written only on request and only after a clean\nresult. Later, a changed module, view, relevant type lens, or schema marks it\nstale and names what moved; ordinary canon edits do not.\n\n### A style-only request is still a new view\n\nIf the author only wants recoloring — \"make the polities blue\" — that is a\n**style module plus a differently named custom view over the whole world**,\nfor example `Everything — faction colours`. It is never a change to built-in\n`Everything`, which ignores every style, lens, and emphasis by design. Offer\nthe named view; say plainly that the audit view stays neutral on purpose.\n\n## The lens vocabulary\n\nA relation's `lens.as` (set in its **type file**, not here) declares a\n*behavior*, not a style — know these before writing a view that depends on\none:\n\n| behavior | what it draws |\n|---|---|\n| `edge` | a line between the two members (default) |\n| `nest` | one container member as a compound node holding one or more contained members |\n| `chip` | rendered on the member rather than between members (states) |\n| `hide` | present in the projection, never drawn |\n\nState chips show the property from their type and either its qualitative\n`value` (for example `Exploration: unexplored`) or numeric `amount`. Ranges\nremain numeric state data; they render as a readable lower bound, upper bound,\nor interval.\n\n**A nest reads its role names from the same `direction` an edge would.** The\npair is `[contained, container]`, which is why the standard `part_of` lens\ndeclares `[\"part\", \"whole\"]`. A world that nests by its own vocabulary declares\nboth keys together in the type file — `as: nest` with\n`direction: [seat, territory]` puts seats inside territories. With no\n`direction`, nesting means `part` and `whole`, as it always did.\n\nTwo limits worth stating to an author before they ask:\n\n- **Only the canon may declare containment.** A lens module may still override\n  `as: nest` for some other relation, but it does not get to reinterpret that\n  relation's role names as inside/outside — the declared pair counts only when\n  the type file itself says `as: nest`. Otherwise the shape of the graph would\n  depend on who drew it.\n- **Naming a direction is not the same as meaning containment.** `seat_of`\n  says \"is the capital of\", which is not \"is inside\". If the author wants the\n  geography drawn, that is usually a second `part_of` relation, not a nesting\n  lens on the first. Say so, and let them choose.\n\n`part_of/membership` renders as an ordinary edge. Earlier versions used a\nlayout-dependent `group` behavior; KERNEL v0.13 removed it because the same\ncanon could look materially different across renderers.\n\n**`dagre` and nesting are mutually exclusive.** dagre has no compound-node\nsupport: it ranks every node as if the graph were flat, so a nested canon\ncollapses into one very wide row inside stretched parent boxes. The viewer\nsubstitutes `fcose` and says so in the warnings rather than drawing that. If a\nview nests anything, choose `fcose` yourself and spare the author the notice.\n\nHierarchy is not in that list, and deliberately so. Whether a superior\nreads as sitting *above* a subordinate comes from the view's `layout` — a\n`dagre` view ranks top-to-bottom, `fcose` has no vertical axis to rank\nalong. If an author wants a chain of command to read as a tree, change the\nview's layout; there is no type-level behavior for it. (`rank` was that\nbehavior until KERNEL v0.13, and drew nothing outside dagre.)\n\nFor an asymmetric relation, use an explicit relation lens declaration such\nas `direction: [subordinate, superior]`. The two role names mean visual\nsource then target; the viewer follows them even if `members` are authored in\nthe opposite order and draws one target arrowhead. Do not infer direction\nfrom `constraints.roles_required`: its order is not semantic. Omit\n`direction` for an undirected relation; invalid or unresolved declarations\nwarn and safely render as ordinary lines.\n\nThese lens rules apply to named views only; built-in Everything ignores them.\nEverything still applies standard directions: `subordinate_to` is\n`subordinate` to `superior`, and explicitly rendered `part_of` and\n`part_of/membership` are `part` to `whole`. In a named view, ordinary\n`part_of` stays unarrowed nesting.\nIf the picture the author wants needs a behavior a type doesn't have yet,\nthat's a lens change — tell them, don't add it yourself.\n\n## Reading the output honestly\n\n- **Warnings on stderr are usually the world's, not the viewer's.** An\n  undefined type degrades to a kind default and warns; that's the canon\n  missing a type file, not a bug here.\n- **An empty graph almost always means `select` filtered everything out** —\n  check `types`/`kinds`/`status` before assuming the canon is empty.\n- Report warnings verbatim once; don't paraphrase or drop them.\n\n## What this skill is not for\n\nModeling questions — what a type should be, whether a relation needs a\n`lens:` block, what facets mean — are the scribe's and `KERNEL.md`'s\nterritory, not restated here.\n"
}

SHA-256 of public snapshot: 78d3551163662a25508acf89de07ccaa1c8f631528e144451dcac1b389453b1d