Skip to content

GitHub milestones and issues are generated from the canonical model, and a hand-edited generated region is reported rather than overwritten

scripts/github-sync renders each canonical record through majordomus plan body and projects it onto a GitHub milestone or issue: title, body region, milestone assignment, labels, and whether the issue is open. Comments, assignees and any text a human writes outside the generated region belong to GitHub and are never touched. A human edit *inside* the generated region is reported as drift and left alone.

declared statusdeclared status: guaranteed Declared deterministic and blocking, implemented, and naming the behavioural test that is to prove it. Whether a recorded run supports it is the evidence verdict, shown beside it.

recorded evidencerecorded evidence: not run Declared guaranteed, and not supported by a recorded run: the test is named, and no passing run of the current inputs is recorded. how evidence is recorded

The adapter lives in scripts/ because the tool itself makes no network call, which test/cases/08_no_forbidden_constructs.sh proves. The begin marker carries the hash of the record it was generated from, which is what separates a moved plan from a hand edit; a second marker carries the record's identity, so a title a person rewrites cannot orphan it. This claim is about what the adapter renders and refuses. Whether the remote has actually received it is a different question, and github-projection-gated is the claim that answers it.

What it means

scripts/github-sync renders each canonical record through majordomus plan body and projects it onto a GitHub milestone or issue: title, body region, milestone assignment, labels, and whether the issue is open. Comments, assignees and any text a human writes outside the generated region belong to GitHub and are never touched. A human edit inside the generated region is reported as drift and left alone.

How it works

The body is spliced between <!-- majordomus:begin <hash> --> and <!-- majordomus:end -->, the same region mechanism that lets Majordomus share a CLAUDE.md with a hand-written one. The hash in the begin marker is the hash of the canonical record the region was generated from, which is what separates the two cases: a different hash means the plan moved and the region is refreshed; the same hash with different content means a person edited it, and --apply refuses without --force.

GitHub is shared, so --apply projects only the trunk: it refuses (exit 15, with the reason and the command that reproduces it) unless HEAD is the tip of the remote's default branch — refs/remotes/<remote>/HEAD, confirmed against the remote itself — and the tree is clean, and only one apply runs at a time, under a lock in the git common directory. The adapter's own code is held to the same rule: when scripts/github-sync is loaded from a checkout of the repository it projects, that checkout must be the trunk at the same commit and clean, because its renderer and state mapping are what get posted. Every write it makes prints a line naming what changed and why; a write GitHub refuses is a FAIL line and the run exits 13 instead of reporting a half-applied remote as done. --plan, --check and --render only read and run from any branch. A listing GitHub does not answer is not an empty remote: --check and --apply exit 12 naming the listing, report no drift and write nothing, where reading it as empty would have reported every record missing and created each again.

The adapter lives in scripts/ rather than in lib/, because bin/, lib/, share/ and test/ contain no network client and test/cases/08_no_forbidden_constructs.sh proves it. The model it projects comes from the same lib/project.sh the CLI uses.

How to see it

scripts/github-sync --plan        # offline: what would be created or changed, and the body hashes
scripts/github-sync --check       # compare against the live repository; exit 11 on drift
scripts/github-sync --apply       # from the clean trunk only: create what is missing, update what changed

What it does not cover

Nothing is read back. Closing an issue on GitHub does not complete it canonically; the next --check reports the disagreement and the canonical record wins. There is no mapping file either. An issue is matched by the <!-- majordomus:record <id> --> marker its generated region carries, and only an issue with no marker is matched, once, by the id that prefixes its title; a milestone carries no marker and is matched by that prefix alone, so renaming the prefix of a milestone on GitHub orphans it.

Why it exists

GitHub is where the conversation happens and a poor place for the plan to live: it has no dependency graph, no validation, and no way to refuse a status that contradicts one. Making it a projection keeps the collaboration and moves the truth into the repository.

Detail rendered from docs/claims/github-projection.md.

From the statement to the proof

  1. 1 · the public statement

    GitHub milestones and issues are generated from the canonical model, and a hand-edited generated region is reported rather than overwritten

    Made on Planning, Plan.

  2. 2 · the canonical claim

    github-projectiondeclared status: guaranteed in docs/CLAIMS.yaml

    Defined in read it on this site · docs/PLANNING.md

  3. 3 · the implementation

    scripts/github-sync

  4. 4 · the test and the scenarios

    Test: test/cases/45_github_projection.sh

    Executed in Complete an issue only when its evidence exists.

  5. 5 · the recorded runs

    recorded evidence: not run how evidence is recorded

Verify it yourself

The test runs in a disposable temporary repository and asserts the behaviour, not a string in the source.

from a clone of the repository
bash test/run.sh 45_github_projection

Where this claim is used

responsibilities it covers

The moments this answers

Searches the titles and descriptions of every page this site publishes.