Metadata-Version: 2.5
Name: specsgraph
Version: 0.2.2
Summary: Specification graph tooling and hypermedia interface for a venture's specification graph
Project-URL: Repository, https://github.com/UP-Labs-Caterpillar-Lab/specsgraph
License: Proprietary
Requires-Python: >=3.12
Requires-Dist: jinja2<4,>=3.1.6
Requires-Dist: jsonschema<5,>=4.23.0
Requires-Dist: markdown-it-py<5,>=3.0.0
Requires-Dist: pyyaml<7,>=6.0.3
Description-Content-Type: text/markdown

# specsgraph — specification graph tooling

SpecsGraph is the class tooling and hypermedia interface for a venture's
specification graph: a tree of YAML-frontmatter Markdown notes that states,
level by level, what a venture is betting on. Level 1 is the value the venture
exists to create; level 2 is a set of falsifiable capability hypotheses, each
with criteria whose failure triggers a pivot rather than a bug fix; level 3
groups the engineering concerns and delegates the detail to OpenSpec
requirements and scenarios. Around the tree sit a class definition that is the
single source of truth for its rules, a conformance lockfile and ratchet so
conformance can only improve, a hypothesis registry with per-layer coverage,
and a pre-rendered static site for reading it all.

It is a **library with a thin CLI (`sg`) on top**, not a CLI with a library
hidden inside: eighteen operations each return one `Report`, and the CLI only
formats it and picks an exit code. A venture builds its graph in its own
repository and installs this tooling rather than copying it, so there is one
version to move and every graph answers to the same class.

## Documentation

- [Getting started](docs/getting-started.md) — from an empty repository to a
  green gate, a site you can read, and a private copy of it on GitHub Pages
- [Writing a graph](docs/authoring.md) — your first capability, requirement,
  code references and evidence, and what happens when a bet fails
- [Architecture](docs/architecture.md) — UML diagrams: components, core types,
  the graph model, a gate run, sources to artifacts, statuses and deployment

This README is the reference: every operation, configuration key, exit code
and ground rule.

## Setup

Python >= 3.12 and [uv](https://docs.astral.sh/uv/) are the only requirements.
From a checkout:

```
uv venv --python 3.13       # or any interpreter >= 3.12 — a bare `uv venv`
                            # may select an older one that the sync then refuses
uv sync --all-groups        # install, dev tools included
```

The CLI installs from PyPI with `uv tool install specsgraph`; from a checkout,
run it as `uv run sg`.

## Working on it

```
uv run python -m pytest       # the test suite
uv run sg gate                # the 9 gates, in CI order, stopping at the first red one
bash scripts/serve.sh         # build the site, then serve it on 8787
uv run ruff check .           # lint (ruff is pinned; the rule set is explicit)
uv run ruff format --check .  # format check
```

The 9 gates, in CI order: `paths check verify validate graph cascade freshness
ratchet site`. `sg gate` runs them as one command, and that one command is what
`.github/workflows/specsgraph-gate.yml` runs — the reusable gate every adopting
repository calls, including this one through
`.github/workflows/specification-graph.yml`. The tooling is gated by the same
file it hands to ventures. Data contracts are verified with the `datacontract`
CLI (`uv tool install datacontract-cli`); CI installs it, and a checkout
without it says what it skipped.

## A repository's first green

`sg init` scaffolds an adopting repository: the config, the vendored class
source, the generated JSON Schema, the seed graph (this repository's own
graph — dogfood and seed are one artifact — written only into a repository
with no notes and no OpenSpec specs yet), the caller-side CI workflow, a
`.gitignore` entry for the build directory, and three agent skills. Its report
carries a `next_steps` sequence naming the commands that take the scaffold
green.

Two things `sg init` deliberately does not do. It does not run `git init` —
if the directory is not a repository it notes the absence on stderr and
scaffolds anyway, so create the repository yourself. And it does not run the
generators, so **the gate is red immediately after init, for named reasons**:
`lock` and `registry` have not run yet, and a gate never writes. The full
first-green sequence — generator order, git identity, and the tagged ratchet
baseline — is in the installed gate skill,
`.agents/skills/specsgraph-gates/SKILL.md`.

## The naming scheme

One vocabulary, everywhere:

| Concept | Name |
|---|---|
| Distribution + import package | `specsgraph` |
| CLI executable | `sg` |
| Config file / table | `specsgraph.toml` / `[specsgraph]` |
| Class source | `specsgraph-class.yaml` |
| Conformance lockfile | `specsgraph-conformance.lock` |
| JSON Schema (generated) | `specsgraph.schema.json` |
| Note identity key | `sg_id` |
| Required tag literal | `specsgraph` |
| Node URI | `sg://{venture}/l{level}/{sg_id}` |
| Graph directory | `docs/specsgraph` |
| Root override env var | `SPECSGRAPH_ROOT` |
| Level 1 node type | `value` |
| Level 2 node type | `capability` |
| Level 3 node type | `domain` |
| Non-tree index type | `index` |

Value, Capability, Domain are the SpecsGraph kernel — the three tree levels,
and the seed every new graph starts from.

## The tag vocabulary

Tags are a required frontmatter list on every note. The complete vocabulary
and each part's standing — the generated JSON Schema requires exactly the
first three rows and nothing more:

| Tag | Standing |
|---|---|
| `specsgraph` | required on every note — the literal tag |
| node-type tag | required — the node type key itself: `value`, `capability`, `domain`, or `index` |
| level tag | required on tree nodes — `level-1`, `level-2`, or `level-3`, as the node type declares; an index carries none |
| status tag | optional and **entirely unenforced** — not checked by the schema; where used, one of `planned`, `future`, `superseded` |

Additional tags are permitted and unchecked. The one retired tag literal is
refused outright by the schema; never carry it forward.

## The eighteen operations

Nine **gates** — checks, in CI order. A gate never writes, except that `site`
renders into the gitignored build directory:

`paths` · `check` · `verify` · `validate` · `graph` · `cascade` · `freshness` · `ratchet` · `site`

`validate` judges notes against the class, data contracts against the ODCS
standard, and evidence records against the class's evidence rules; `graph`
refuses code and spec references that no longer resolve, naming where each
went; `cascade` judges decision records as well as their recorded blast radii;
`ratchet` refuses regressed conformance, contract drift, unrecorded code
references and edited UAT records, and warns when referenced code changed. One
more runs them all: `gate` — the nine in order, stopping at the first red one,
one `Report` with every finding tagged by the gate that raised it. That is the
whole of what CI does.

Three **generators** — they write, and the gates consume what they produce:
`lock` (record conformance), `registry` (the hypothesis registry), `generate`
(the projections the class source implies).

Five more: `writeback` (materialize the derived `down:` edges into note
frontmatter, reporting any authored value it overrode), `init` (scaffold a
repository), `migrate` (bring a repository adopted on an earlier release up to
this one, naming any decision it cannot make), `serve` (serve the built site), `walkthrough` (compose a
blast-radius video composition — a self-contained HTML file plus manifests; it
never invokes a renderer). The CLI also offers `sg version`, which reports the
SDK version, the supported class versions, the JSON envelope schema version,
and the class name, and `--version`, which prints the bare `sg <version>`
string and exits.

Every command accepts `--json` for a machine-readable envelope on stdout, with
diagnostics on stderr; `sg gate --json` also streams each gate's human
rendering to stderr as it runs. The generators, `writeback`, `init` and `migrate` accept
`--dry-run`: they report their targets and write nothing. `ratchet` and `gate`
accept `--base <ref>`; `init` and `migrate` accept `--no-skills`, and `init` an optional tooling
ref to pin the scaffolded workflow to.

### Exit codes

The set is closed; a caller that distinguishes these never parses prose:

| Code | Meaning |
|------|---------|
| 0 | success |
| 1 | findings — the graph itself is invalid |
| 2 | usage error — bad arguments |
| 3 | config error — `specsgraph.toml` missing or malformed |
| 4 | root not found |
| 5 | class error — unsupported `class_version` or malformed class source |
| 6 | internal error — a bug in SpecsGraph; the only code that prints a traceback |
| 130 | interrupted — Ctrl-C; neither a verdict nor a bug |

Codes 2–6 and 130 mean nothing was evaluated: no evaluation statistic is
emitted on those paths, so a CI log cannot mistake a setup failure for a
measured result.

## Configuration

A repository states its layout once, in `specsgraph.toml` under the
`[specsgraph]` table. Known keys: `app`, `tree`, `contracts`,
`openspec_specs`, `openspec_changes`, `adr` and `evidence` (source paths);
`tests` (a path or list of paths the pipeline executes, which evidence records
must cite their tests from); `venture` (the
slug in every URI this repository mints); `provisional_scope` (the paths held
to a provisional class version); `dereference_base` (the origin this
repository's site is published at, such as its GitHub Pages address);
`ticket_prefixes` (tracker prefixes such as `COR`, whose `COR-76` tickets
requirement text names and which are therefore not contract ids); `projections` (override a projection target,
or switch one off with an empty value). An unknown key is refused, not
ignored; every path value is repository-relative. `sg paths` prints the
resolved layout, including where the agent skills land (the `skills` summary
key).

`provisional_scope` lives here and only here: it is a list of repository
paths, so it belongs to the repository, and the vendored class source carries
none. Left undeclared, the scope is the configured tree.

`dereference_base` lives here for the same reason: where a site is published is
a fact about a repository. It must be an http or https origin with no path,
because the pages link from the root of their origin. URIs dereference there;
the class keeps its own `http://localhost:8787`, which `sg serve` binds either
way, so the vendored class source and its schema stay identical everywhere.

### Publishing the site on GitHub Pages

The workflow `sg init` writes carries a `pages` job. It builds the site with
`sg site` and deploys it through the shared `specsgraph-pages.yml` workflow
after every green gate on the default branch, never from a pull request, and
only once the repository opts in. On an internal or private repository of an
Enterprise Cloud organization the site is private: readable by whoever can read
the repository, at its own `*.pages.github.io` subdomain. Standing it up is
once per repository:

```text
gh api -X POST "repos/{owner}/{repo}/pages" -f build_type=workflow
gh variable set SPECSGRAPH_PAGES --body true
gh api "repos/{owner}/{repo}/pages" --jq ".html_url, .public"
```

Then set `dereference_base` to that `html_url` and push. The job refuses to
deploy a site Pages would serve under a path (a public project site is served
under `/<repository>/`), and warns when `dereference_base` disagrees with the
address Pages reports. `sg init` prints these steps too.

Root discovery is settled: an explicit `SPECSGRAPH_ROOT` wins (an existing
directory is trusted as-is, even one that is not a repository; a value that is
not a directory is exit 4), then the nearest `.git` boundary walking up from
the working directory, then the config file, then exit 4. The installed
package's own location is never an anchor.

## Ground rules

These are the traps. Each one has already caught someone.

1. **Never hand-edit a generated region.** Two marker systems exist. Markdown
   tables live between
   `<!-- BEGIN GENERATED specsgraph-class:{name} -->` and
   `<!-- END GENERATED specsgraph-class:{name} -->` HTML comments; a note's
   derived `down:` list lives between `# BEGIN GENERATED sg:down` and
   `# END GENERATED sg:down` in its frontmatter. Edit the class source and run
   `sg generate`, or let `sg writeback` materialize `down:`; `sg verify` fails
   the build on drift, and `sg generate` over a target whose markers were
   deleted is a finding (exit 1) that names the file.
2. **`specsgraph-class.yaml` is the only source of truth for the class.** The
   JSON Schema, the agent skill's tables and the graph README's tables are
   projections of it. Editing a projection directly is the exact failure the
   class exists to end.
3. **`registry.json` is generated and committed.** Regenerate with
   `sg registry`; `sg freshness` fails CI if the committed copy is stale.
4. **`build/` is gitignored.** Never commit rendered pages.
5. **Files are written LF-only.** Every gate is an exact byte comparison, and
   reads normalize line endings, so CRLF drift is invisible to the tooling and
   surfaces far from its cause.
6. **The ratchet compares against a recorded baseline revision.** Resolution
   order: an explicit `--base <ref>` wins; without one, `origin/main` first,
   then a local `main` fallback. Two consequences. CI must check out with
   `fetch-depth: 0` — a shallow clone leaves no baseline to resolve, and the
   ratchet then errors naming the refs it looked for, deliberately not a pass.
   And on a never-pushed repository whose branch *is* `main`, the bare form
   resolves the local `main`, which is HEAD, and compares HEAD against itself:
   a vacuous pass that proves nothing. Until `origin/main` exists, pin an
   explicit tag with `--base` — the gate skill's first-green flow tags the
   scaffold commit `sg-baseline` for exactly this reason.
7. **A repository states its own scope in `specsgraph.toml`** rather than
   editing the class, so the vendored class source stays byte-identical
   everywhere and "are we all on the same class?" remains a question a diff
   can answer.
8. **The video pipeline is optional and isolated.** `walkthrough` is imported
   lazily by its operation; the package builds, gates and serves with the
   video toolchain (Node, headless Chrome, FFmpeg) absent.
9. **Only `layout.py` may name a path.** Every path the tooling reads or
   writes is declared there and overridable from `specsgraph.toml`, and a test
   asserts from the syntax tree that no other module contains one. A path
   added anywhere else fails nowhere here and everywhere in an adopting
   repository.
10. **Publishing is deliberate, never routine.** `release.yml` triggers on a
    version tag or a manual dispatch only, runs in the `pypi` environment so a
    reviewer can hold a release, and refuses to publish when the tag, the
    package and the built distributions disagree on the version.

## Operational hazards

- **Piping `sg` output through `head` truncates the output, not the
  artifact.** Every generator writes before the CLI prints, so a broken pipe
  leaves the file on disk complete; only the table in your terminal is cut
  short, and the command exits 1 rather than pretending to have printed it.
- **`sg serve` refuses to start when nothing is built.** Run `sg site` first.
  It binds to the class's dereference base (8787), takes no port flag, and is
  single-threaded — point at most two browser sessions at one instance.
- **Node routes answer 301 then 200.** Any `curl` needs `-L`.
- **`down:` is generated, never hand-authored.** `up` is the only authored
  direction; on conflict the derived value wins and the discrepancy is
  reported.
- **`sg cascade <uri>` takes a full `sg://` URI**
  (`sg://<venture>/l2/<sg_id>`); a bare identity is refused
  (`cascade.unknown-origin`), because a bare id names no venture and no level.
- **`sg site` rebuilds the whole tree at the current graph revision** and
  replaces the previous build uniformly; it never merges with what is already
  there.
- **Re-running `sg init` is safe.** Pre-existing files are left byte-unchanged
  and reported as kept, and the `.gitignore` entry is not duplicated.
- **Upgrading an adopted repository is `sg migrate`, not `sg init`.** `init`
  never overwrites anything. `migrate` re-vendors the class and the installed
  skills, carries a provisional scope the old class held into
  `specsgraph.toml`, turns `falsified_by` labels into UAT evidence records, and
  removes the retired fields. It runs even over a class this tooling refuses,
  and `--dry-run` shows every change first.
- **A contract's identity is its filename.** `docs/data-contracts/<venture>/
  XXX-NN-name.odcs.yaml` is addressed as `odcs://<venture>/XXX-NN@<version>`;
  the file's `id` must agree, a requirement names it by that id, and a change
  to its bytes must move its `version` (a new major when the `datacontract`
  CLI classifies the change as breaking).

## CI

`.github/workflows/specsgraph-gate.yml` is the reusable gate
(`on: workflow_call`). It checks out with `fetch-depth: 0`, installs the SDK
from the package index and the `datacontract` CLI at a pinned version, and
runs `sg gate --json` — the nine gates in CI order, human rendering on stderr,
one JSON envelope on stdout; for this repository only it also runs the test
suite and ruff. Every check it runs is a command above, with the same exit
code, so a red build is always reproducible locally. `.github/workflows/specification-graph.yml` is this
repository's own caller: the tooling is exempt from nothing it imposes on an
adopter.

## Release

`.github/workflows/release.yml` publishes to PyPI with uv's native OIDC
trusted publishing — no twine, and no long-lived credential anywhere: the job
declares `permissions: id-token: write` for the OIDC token uv exchanges with
PyPI, and runs in the `pypi` environment so a required reviewer can hold a
release. It triggers on a `v*` tag push or a manual dispatch naming the tag,
never on a branch push. Between `uv build` and `uv publish`,
`scripts/check_release_version.py` refuses the release unless the tag, the
package and the built distributions all name one version; the same guard runs
locally:

```
uv build && uv run python scripts/check_release_version.py v<version>
```

The trusted publisher on PyPI names project `specsgraph`, owner
`UP-Labs-Caterpillar-Lab`, repository `specsgraph`, workflow `release.yml` and
environment `pypi`. **Type the repository name in lowercase**: PyPI finds a
publisher by the exact text it stores, so `Specsgraph` never matches the token
GitHub sends, and the upload fails with `invalid-publisher` even though every
field looks right. 0.2.0 was the first release published this way.

To release: bump `version` in `pyproject.toml` and the `sdk-version` defaults
in both shared workflows (the test suite asserts they agree), merge, then push
a `v<version>` tag on `main`. A failed upload leaves the tag usable: re-run the
job once the cause is fixed.

Be clear-eyed about what publishing means: a wheel on public PyPI publishes
the source, even though this repository stays internal. That trade was chosen
knowingly — it is why the distribution metadata declares a Proprietary
license.

## The library

`import specsgraph` is the primary surface; the CLI is a thin presenter over
it. The public names are exactly those in `specsgraph.__all__`:

- entry points — `Context.discover()` (resolves root, layout and class source
  once), `Project` (a caching facade over the same operations; `refresh()`
  drops the cache), `ops` (the eighteen operations), `gates.ALL` (the nine
  gates as a mapping of name to `Context -> Report` callable)
- the one result shape — `Report` and `Finding`, with `Severity`; advisories
  travel as `note`-severity findings, and `Report.notes` carries location-less
  prose
- domain types — `Graph`, `Node`, `Edge`, `BlastRadius`, `Member`,
  `HypothesisRegistry`, `HypothesisEntry`, `HypothesisStatus`, `LayerCoverage`,
  `Note`, `Venture`, `Lockfile`, `Site`, `ClassDef`, `Uri`, `SchemeTable`,
  `Layout`, `find_root`
- typed errors, each mapping onto one exit code — `SpecsGraphError`,
  `ConfigError` (3), `RootNotFound` (4), `ClassNotFound` (5),
  `ClassUnsupported` (5), `UriError` (1), `MarkerError` (1)
- version negotiation — `__version__`, `SUPPORTED_CLASS_VERSIONS`, and
  `JSON_SCHEMA_VERSION`, the envelope schema version agents pin on, stable
  independently of the SDK release

Importing reads nothing from disk: no config file and no checkout are needed
to `import specsgraph`, and `classdef.load()` with no arguments falls back to
the packaged class when no repository exists, so a library consumer can reach
the class without a repository at all.

A full validate-and-render cycle without the CLI:

```python
from specsgraph import Context, ops

ctx = Context.discover()
for report in (ops.check(ctx), ops.generate(ctx), ops.validate(ctx), ops.verify(ctx)):
    if not report.ok:
        raise SystemExit(1)

ops.lock(ctx)  # writes specsgraph-conformance.lock
ops.registry(ctx)  # writes registry.json + build/registry.html
site = ops.site(ctx, write=False)  # every page, in memory only
print(len(site.payload.pages), "pages at", site.summary["revision"])
```

## Agent skills

`sg init` installs three agent skills alongside the rest of the scaffold, and
`sg paths` reports where they landed (the `skills` summary key):

- `.agents/skills/specsgraph/SKILL.md` — note authoring. Its tables are
  generated regions of the class source (the `skill` projection): change
  the class, run `sg generate`, never edit inside a region. It also documents
  the complete tag vocabulary — the required literal tag, the node-type and
  level tags, and the optional, entirely unenforced status tag.
- `.agents/skills/specsgraph-gates/SKILL.md` — gate operation: `sg gate` and
  the nine gates it runs in CI order, the generator sequence a fresh
  repository needs first, the exit codes, and the operational hazards.
- `.agents/skills/specsgraph-reverse-engineering/SKILL.md` — preparing an
  existing codebase to be reverse-engineered into a graph: the inventory pass,
  the evidence rule for hypotheses, and the handoff to the other two skills.

Pass `--no-skills` to `sg init` to skip them. An adopting agent can author a
valid note and run the gate reading nothing but the installed skills.

## Design rationale

Three invariants hold the design together.

1. **One place names paths.** `layout.py` is the sole module permitted to name
   a directory or filename the tooling reads or writes. A path hardcoded
   anywhere else breaks every adopting repository and nothing in this one —
   the failure is invisible where it is introduced and total where it is
   consumed.
2. **One source defines the rules.** `specsgraph-class.yaml` is the contract:
   node types, levels, frontmatter, URI schemes, identity and edge shapes.
   Everything else is a generated projection, so a rule changed anywhere but
   the class source is a defect.
3. **One shape of result.** Every operation returns one `Report`. Operations
   never print and never exit; the CLI formats and picks the code. That is
   what makes the library drivable programmatically and the CLI validatable
   as a black box.

Why the pieces exist:

- **The class source is single** because three disagreeing definitions of the
  class existed before. Now there is one, and the others are generated from
  it.
- **Conformance is a lockfile plus a ratchet, not a hard gate**, so a
  repository can adopt a stricter class without a flag-day migration: where a
  class carries a legacy profile, notes below the current version keep
  passing, their count may never grow, and any note you touch must come up.
  This class carries none — ADR-0003 retired the v1 profile once the corpus
  it described was gone — so here only the current version validates.
- **The graph is built offline** from notes, OpenSpec specs, ADRs and
  evidence, so it works in a fresh checkout with no services.
- **`down:` is computed** because the authored reverse direction goes stale;
  requiring both directions by hand has already failed by a wide margin.
- **The cascade answers "if this hypothesis is false, what else breaks"**,
  with per-edge-type cascade strength, because consequences do not always
  travel the way an edge points.
- **The registry carries per-capability status and per-layer coverage**, gated
  by a freshness check so the committed copy cannot rot.
- **The site is pre-rendered pages** — no server, no build step beyond
  `sg site`, note bodies rendered from Markdown — because a graph that cannot
  be rendered is one nobody will look at, and a gate nobody reads gets routed
  around. The pages carry `hx-*` attributes and load htmx as a progressive
  enhancement (one pinned, integrity-checked script), so navigation swaps in
  place with the URL pushed; offline, every link is still a plain page load.
  The map of the whole graph is pre-rendered too: its layout is computed at
  build time from the same graph as the pages, so it cannot drift from them,
  and tracing a node's line through it is a stylesheet rule, not a script.
  Every page carries the tree with its own branch open and a breadcrumb to the
  value it serves, and `/search` lists every page.
- **The gate is one reusable workflow** every repository calls, including this
  one, and one command inside it: a gate the class authors exempt themselves
  from is a gate that gets broken for everyone else first, and a gate whose
  order lives in two places drifts the moment a gate is added.
- **Data contracts are verified, not decorative.** The registry's own output
  has a contract (`SGR-01`) with quality rules, a requirement names it, and so
  level-4 coverage can bind and a cascade can cross a real contract boundary.
  `validate` lints every contract and `ratchet` refuses drift, so the boundary
  dimension is held to the same standard as the tree.
- **Evidence is a record, not a label.** One Markdown document per observation
  or test suite under `docs/evidence/`, addressed as `evidence://<id>`, of a kind
  that is one of the test harness's layers: strategic UAT, domain-driven,
  behaviour-driven and end-to-end testing. A criterion counts as bound only when
  a record supports it, quoting it verbatim or addressing its scenario, and every
  test it cites runs in the pipeline. This is where the strategy track meets the
  harness (ADR-0004).
- **The framework references the code; the code never references the
  framework.** A requirement names its implementation with `code://path#anchor`
  in its own text, and nobody working in the code has to know. The lockfile
  records content hashes, so a moved file or renamed symbol is found and named,
  an unresolvable reference is an error, and changed code is reported as drift.
- **References follow OpenSpec's lifecycle.** A capability is a path, a scenario
  is a fragment, a proposed requirement is pending, and a renamed or removed one
  fails naming the change that did it.
- **Distribution is a wheel that carries the class source**, so `sg init`
  vendors byte-identical rules into every adopter, and an agent skill ships
  with the tooling it documents.

## Layout

```
src/specsgraph/       the package (src-layout)
  _assets/            vendored class source, schema, seed graph and skills —
                      byte-identical to the copies they vendor, except the
                      authoring skill, whose generated regions are placeholders
                      `sg init` fills
tests/                the test suite
docs/                 guides (getting started, writing a graph, architecture)
  specsgraph/         this repository's own graph (dogfood, and the seed sg init scaffolds)
  adr/                decision records
  evidence/           evidence records, filed under the capability they cover
  data-contracts/     ODCS data contracts
openspec/             the OpenSpec specs the graph delegates to
scripts/              serve.sh and the release version guard
dev/                  reserved for testing-only tooling — empty, excluded from the wheel
.github/workflows/    the reusable gate and publishing workflows, this repository's caller,
                      and the release pipeline
.agents/skills/       the three installed agent skills
```
