ADR 0007: The public export, make publish¶
Context¶
Agent instructions and process state are private working material, so no agent
file appears in a public repository: AGENTS.md, CLAUDE.md,
.agent-state/, .work/ and .claude/. That applies to software
repositories and to methodology repositories once their paper is published.
GitHub sets visibility per repository, not per branch, so one repository
cannot keep the agent files private and the code public. A public-facing
project is therefore two repositories: a private one with every branch and
the agent files, and a public one that receives only a curated export. The
three requirements for make publish are:
- leave out the agent files;
- refuse if a line contains an Overleaf project identifier, a home path or
a project-specific pattern in
.publish-deny; - push one branch to the public remote as a new release commit, so no private history goes with it.
Decision¶
scripts/publish.py and make/publish.mk ship in the methodology and
software templates, from _shared/publish/. The workspace and paper
templates never become public and do not get them.
| Requirement | How |
|---|---|
| Leave out agent files | The export is git archive HEAD without any path that is, or sits under, AGENTS.md, CLAUDE.md, .agent-state, .work, .claude, journal.md or .publish-deny, at any depth |
| Refuse private items | Built in: Overleaf project IDs and home paths (/Users/<name>/, /home/<name>/, except a CI runner's). Project-specific hostnames, groups, organisations, board numbers, names and unpublished titles go in .publish-deny |
| No private history | The release commit is built with git commit-tree in a fresh repository: its tree is the export and its only parent is the public branch's tip, or none for the first release |
Further rules:
- The export is
HEAD, so a working tree with changes is refused rather than published without them. - A refusal names the file, the line and the rule, never the matched text, so the refusal itself does not repeat the private item.
- An export identical to the last release is refused ("nothing to publish"), so a commit that only touched agent files adds no empty release.
.publish-denyholds one regular expression per line, matched without case, and a line that is not a valid expression is refused. The template ships it with comments only: the template cannot know a lab's hostnames, account group, organisation, board numbers, advisors or unpublished titles, and writing examples of them into a public template would be a private item itself.make publish-checkbuilds and scans and pushes nothing.make publish REMOTE=<name or URL>pushes. Neither is part ofmake ci; pushing is the owner's action.- The script uses only the standard library and git, so it runs before
make install, and it is tested where it is defined, as_shared/docs/is.
Left out¶
- Scanning file history. The export carries no history, so only the exported tree is scanned.
- Secrets. Every template's pre-commit already runs
detect-private-key, and the.envfiles are ignored. The publish scan covers private context that is not necessarily a secret. - Creating the public repository, or its settings. That is outward-facing and stays a person's step.
- Tags and GitHub releases on the public side. The release commit's
message names the version (
Release <version>frompyproject.toml); tagging it is left to the owner.
Consequences¶
- The public repository's history is one commit per release, each built on the last, which is what a reader of a published paper's code needs.
- A private item the rules do not know about still gets through. The
built-in rules catch Overleaf identifiers and home paths; the rest depends
on
.publish-denybeing kept up to date. scripts/publish.pyandmake/publish.mktravel into the public copy, so itsMakefilestays valid. Run there,make publish-checkfinds no agent files to leave out.