labkit automates graduating a labs/* project into its own public GitHub repo
(Pitot, Boatstack, Value Map today). One declarative publish.config.json per
lab drives everything: the projection of curated lab bytes into the public
repo, the workflows on both sides of the sync, and a drift gate that keeps them
from quietly diverging.
The monorepo (operatorstack/intelligence-flow) is the single source of truth.
Public repos are byte-for-byte projections of a curated subset of one lab —
strictly one-directional. Nothing flows back.
labs/<lab>/publish.config.json # single source of truth for one public repo
│
├─ projection engine ────────► public repo files + UPSTREAM.json (provenance)
├─ generator ────────────────► .github/workflows/<project>-publish.yml (dispatcher)
│ labs/<lab>/.labkit/generated/sync-upstream.yml
└─ doctor ───────────────────► fails CI if any generated file drifts from the config
Two manifest styles, mirroring what the labs already do:
surface— a{schema_version, files{path: sha256}}manifest committed in the lab as a drift gate over the public surface (Pitot). Such a lab may also opt into a downstreamprojectionprovenance manifest (manifest.downstream) so the public repo carries the source commit.projection— a richer{schema_version, generator, source, files}manifest written into the public repo (Value Map, Boatstack).
Genuinely bespoke labs (Boatstack rebrands and recomputes fingerprints) supply a
.labkit/hooks.py whose build_files(ctx) returns the complete file map; the
engine treats it as the projection.
python -m labkit <cmd>| Command | What it does |
|---|---|
project --config <cfg> --repo <dir> --source-commit <sha> --write |
Project a lab into a public-repo checkout (add --adopt on first import). |
project --config <cfg> --check |
Verify the committed in-lab surface manifest (surface style). |
gen [--repo-root <root>] [--check] |
(Re)generate the dispatcher + staged sync-upstream.yml for every lab; --check fails on drift. |
doctor [--repo-root <root>] [--remote] [--remote-governance] |
Validate every config, fail on generated drift, assert each verify gate exists; --remote diffs live public workflow control planes, while --remote-governance verifies auto-merge and required checks with an Administration-read token. |
publish init <lab> |
Scaffold publish.config.json + workflows + a first release note for a new lab. |
release-notes <check-policy|validate|preflight> |
The shared append-only release-note policy. |
python -m labkit publish init labs/NN-yourlab --project your-lab --display-name "Your Lab" --repo your-lab- Edit the generated
publish.config.json— declare the realsurfacerules. python -m labkit genthen... doctor— regenerate and validate.- Add the hand-maintained verify gate
.github/workflows/your-lab-lab.yml(bespoke per lab; not generated). - Create the public repo, install
labs/NN-yourlab/.labkit/generated/sync-upstream.ymlinto it at.github/workflows/sync-upstream.yml, and set the publisher app vars/secrets on both repos. (The projection token deliberately cannot rewrite.github/**, so the control plane is installed once by hand.)
- Generated (config-driven, drift-gated):
<project>-publish.yml,labs/<lab>/.labkit/generated/sync-upstream.yml. - Hand-maintained (bespoke, reviewed):
<project>-lab.yml(the per-lab verify gate — Go matrices, wind-tunnel, pnpm evals).doctoronly checks it exists.
Omitted config fields fall back to defaults.py: the shared Operator Stack
publisher app (with the Boatstack app as fallback), a deterministic per-project
cron minute derived from the slug (no hand-picked collisions), stale-guard on,
and .github/** always excluded from projection.