Project-Template/README.md

17 KiB

Project Template

A working documentation skeleton, the tracker convention that goes with it, and twenty scripts that enforce both. Copy docs/ into a new project, replace the angle-bracket placeholders, delete the paragraphs that do not apply — then create the labels and the first milestone in the repository's issue tracker.

Status: Current
Owner: _null
Last reviewed: 2026-08-12
Governs: Projects/Template/**
Review trigger: Any change to the tracker conventions or the QA run-state
                grammar in the Privacy LLC Command Center's src/lib/; any
                change to which project fields the agent API can write, or to
                what reconcile reads; any script added to or removed from
                docs/architecture/scripts/

Why this exists

The Command Center at privacyllc.dev reports on a repository: how much is done, what the QA verdict is, and what the next milestone entails. It can only do that for repositories that keep those things somewhere it knows to look.

Eight repositories were surveyed before this was rewritten. Exactly one kept the markdown batch ledger the previous version of this template asked for. Three had invented their own dialects; one had pushed nothing but a README. Meanwhile, with no convention at all, those same repositories had accumulated 69, 54 and 205 issues — because an issue tracker has structure built into it and a convention has only whoever remembers it.

The split

Kind of truth Lives in Why
Narrative documents — vision, trust map, QA playbook, QA verdict, security git, as markdown prose with an argument, versioned with the code, readable at any sha
Work items — batches, tasks, defects, blockers the issue tracker built for concurrent uncoordinated writers, atomic state, closes #N from a commit, real timestamps

Nothing belongs in both. A to-do list in markdown and a tracker with the same items is two records that will disagree, and neither will say which is right.

What the Command Center actually reads

Source What is read
The repository's issue tracker issue counts excluding pull requests, milestones with their own counters, and issues carrying the four severity labels
docs/qa/ClaudeReport.md the three run-state lines — round, build SHA, and the overall sentence, quoted verbatim
docs/data/img/ icon.webp, logo.webp, banner.webp — fetched and rendered on the project page

Everything else in this tree is here because a project needs it, not because something parses it. Rename, split or extend those freely.

A repository that has not adopted the labels is reported as not adopted rather than as zero defects, and one whose tracker is switched off is reported as switched off rather than as an empty backlog. Absence is never rendered as a measurement.

The files in that table are read at a commit, and the commit is remembered. When the SHA they were read from falls behind the repository's newest commit, the docs report is marked stale — which is the accurate description of a document that described the code at a commit nobody is running any more. It is also what an unpushed correction looks like from outside. Documentation edits therefore ride in the same commit as the change that caused them, and reach the site on the same push. See docs/WORK_CYCLE.md.

What updates itself, and what does not

The numbers on the project screen come out of this repository's tracker. The percentage is defined rather than picked: closed issues over all issues, asked with type=issues so pull requests are excluded and state=all rather than the API's default of open. Beside it sits a second figure counted from the repository's own milestones, closed over all — this deployment keeps milestones in the tracker and never mirrors them into the site's table, so that is where the milestone reading comes from.

On the project screen Source How it gets there
Headline % — issues closed / all issues the repository's tracker automatic — on reconcile, and immediately on a webhook delivery
Second figure — milestones closed / all milestones the repository's milestones same read, same moment
Milestone coverage how many issues sit inside a milestone at all same read
QA verdict — round, build SHA, overall sentence docs/qa/ClaudeReport.md push the repository, then reconcile
Current summary / Next action manual fields on the project PATCH /agent/projects/<slug> — nothing else writes them
The Milestones card, and a typed weighted plan the Command Center's own milestone table admin UI only
Blockers the Command Center's own blockers table admin UI only

So closing an issue and closing a milestone each move a number a stakeholder can see, the same day, with no further step. That is the whole reason the convention insists on both.

The last two rows are the ones that surprise people, and they are lists rather than figures. The Milestones card reads the site's own table, which is empty on this deployment by design — a repository can carry twenty-nine milestones, have them counted in the figure above, and still show "No milestones have been added yet" on that card. It is not a sync failure and reconciling will not change it.

Blockers have a sharper version of the same split. The agent API can narrate a blocker in a check-in's blockers field, and it has no route that creates or resolves one in the table. A run that posts the check-in and reports "blocker filed" has told the truth about the note and a falsehood about the record.

An issue in no milestone is counted in the headline and invisible to the milestone figure. The site measures that gap on purpose — it is the reason Verify: lines and milestones are per-issue requirements here rather than housekeeping. Portfolio-wide when this was written, 272 of 650 issues belonged to no milestone at all, including every one of the 205 in the repository that had never adopted the convention.

The digest is the part somebody actually reads

A worker polls the site every sixty seconds and sends a project digest to Discord. It is where this convention stops being bookkeeping: the digest's per-project figures come from issues opened and closed and from pushes, and its next-step line comes, by rule, from nextAction or the live milestone's open issues — never from a guess.

Three consequences, each the direct form of a step in the cycle below:

  • Work with no issue opened or closed produces a push and nothing else. The project reads as quiet on a day it was not.
  • A project with no nextAction and no open milestone gives the digest nothing to say about what happens next, and the digest will not invent one.
  • An opened issue labelled P0 raises an immediate alert — but only from a repository whose webhook is registered. Without one, the site sees the repository through the periodic sweep alone, and a P0 filed at nine is news at the next reconcile rather than at nine.

After each piece of work

docs/WORK_CYCLE.md is the seven-step version, with the argument for each step. The short form:

close what you finished, with the evidence · file what you found · close the milestone if the batch landed · update the documents the change triggered, in the same commit · push · log the entry, with Next action and Blockers · then reconcile and write the summary and next action.

The last two steps are the ones that get skipped, and the only ones nobody can do for you later — by the time somebody notices the project screen is a week stale, the person who knew what came next has forgotten.

Where each of those lives is deliberate and worth reading once: the next action has a live copy (the project field) and a dated copy (the newest log entry) and they are different kinds of fact; a blocker is an issue in the tracker first, and a row on the site only when a human puts it there.

The tracker convention

Where the tracker is

dream.scheller.ltd — a Forgejo instance, and naming it is not a detail. The convention below is portable: milestones, issues and labels exist on GitHub, GitLab and Jira, and every rule in it would work there unchanged. The reporting is not portable at all. Every figure the Command Center shows — the headline percentage, the milestone count, milestone coverage, the alert an opened P0 raises — is read from a Forgejo repository mapped to the project. A project that follows every rule here in a different tracker satisfies the convention completely and appears on privacyllc.dev as having no tracker at all.

Credentials are in ~/.openclaw/docker-registry.env, which carries FORGEJO_REGISTRY, FORGEJO_REGISTRY_USER and FORGEJO_REGISTRY_TOKEN:

set -a; . ~/.openclaw/docker-registry.env; set +a
python3 docs/architecture/scripts/forgejo-issue.py list

Despite its name that token is not registry-scoped — it works across the whole API, with push and pull. Never print it into a log or a chat.

Two things that cost an hour each otherwise. Cloudflare fronts the instance and 1010-blocks clients that do not look like a browser or curl — Python's urllib among them — so every request needs User-Agent: curl/8.5.0; forgejo-issue.py already sends it and anything new must too, or the failure arrives as a Cloudflare HTML page that looks nothing like a Forgejo error. And /issues returns pull requests too unless type=issues is passed, which is why the counts here exclude them and a hand-rolled query disagrees by exactly the number of open PRs.

forgejo-issue.py takes the repository from this checkout's git remote, so it cannot file into another project's tracker by accident. --repo owner/name overrides that deliberately, and --dry-run prints the payloads and changes nothing.

This block names one instance and one path on the machine that runs it. If this repository will be public, replace both with your own before the first push.

The rules

  • Milestone = batch. Name it 0.2 Payments. The description says what the batch is for and how anybody will know it landed. A due date means a commitment — leave it empty rather than inventing one.
  • Issue = deliverable. One per item, in its milestone, ending with a Verify: line stating the acceptance check.
  • Labels: P0 ships broken or loses data · P1 materially wrong but shippable · P2 cosmetic or low impact · release-blocker means a release built today would be wrong rather than merely incomplete. Exactly these names — they are queried by name.
  • Defects are issues with a severity label. The QA verdict stays in ClaudeReport.md; the counts come from the tracker.
  • Close with closes #N in the commit that does the work, so the record comes from the thing that happened rather than from a date typed afterwards. Where no single commit finished it, close it by hand with the evidence — a path, a symbol, a test name, or the command that proves it. "Done" is not a close, because a close with nothing in it cannot be reopened with confidence.
  • Close the milestone when the batch lands. Closing the last issue under it does not, and a milestone that is complete but open reads as a batch still in progress. It is how a batch becomes visible as shipped rather than merely finished — and it is counted: milestones closed over milestones total is one of the two figures the project screen shows.
  • Every issue belongs to a milestone. An issue filed outside one still counts against the headline percentage and is invisible to the milestone figure, so the two readings describe different amounts of work. The site measures that gap rather than ignoring it.

The rule that keeps the numbers honest

Do not pad the tracker. Every open issue is a denominator. If the real answer is "one milestone, three issues", file exactly that. Invented future work makes every percentage drawn from the tracker wrong, permanently and in the same direction.

The same arithmetic runs the other way, and this half is the one that actually happens: an issue left open after the work is done understates the project for as long as it stays open, and the understatement compounds. A fortnight of finished work with unclosed issues reads, from outside, as a fortnight of no progress at all.

What comes with it, besides the documents

docs/architecture/scripts/ holds working code, not examples. Each takes its configuration from the environment and hard-codes nothing about any deployment, so a script copied from here cannot quietly point at the project it came from. docs/architecture/README.md has the full table; the ones worth knowing about on day one:

release.sh version bump, guards, build, verify, push. Refuses to build on a half-run test suite or a malformed public origin — both of which had shipped in every release of the project this came from.
verify.sh every check the project has, in one command, with one table saying which passed, which failed, and which did not run
check-env.sh which variables are set and which are missing, before anything reads them
secrets.sh credential shapes in a staged diff, using the project's own patterns where it has written them down
prove-guard.sh breaks what a guard protects and requires the guard to go red, then restores the file from a trap. GUARDS.md §1 as a command — the rule that a guard nobody has seen fail is not yet evidence.
doc-claims.sh every file a document names must exist — and --covers, whether every file that exists is named. The second is the one that catches a list quietly missing rows.
duplication.py dead-code.py code that exists twice; exports nothing imports and assets nothing renders
forgejo-issue.py files and closes issues in the convention below, with every rule of the convention above as a check the script refuses to break
deploy.py updates a running stack to a published image. Publishing and deploying are separate decisions; release.sh makes the first and this makes the second.
release-notes.mjs tags the release and writes the notes: sections from the commit types the hook enforces, the image and its digest, and the batches the release finished. Set RELEASE_DEPLOY_NOTE to say how this project deploys, or empty where publishing and deploying are one act.

docs/architecture/githooks/ holds three hooks — typecheck and tests before a commit, a conventional type in the message, and a push afterwards so a guarded commit does not sit unpushed. They live in the repository rather than in .git/hooks, which is not versioned; see their README for the one command that points git at them.

docs/architecture/GUARDS.md is worth reading before you write a check. Every rule in it was learned from a guard that had been green for months over something broken, and the first — prove a guard fails before believing it passes — is the one that costs thirty seconds and catches the rest.

Adopting it

  1. cp -r Projects/Template/docs <your-project>/docs
  2. Fill in the status header on each file — especially Review trigger, which is the line that keeps a document from going quietly stale.
  3. Create the four labels, one milestone, and an issue per deliverable you can actually see ahead of you — in the tracker named above, not whichever one the host happens to offer.
  4. Point git at the hooks: cp docs/architecture/githooks/{pre-commit,commit-msg,post-commit} .githooks/ && chmod +x .githooks/* && git config core.hooksPath .githooks. It is per-clone, so every checkout runs it once.
  5. Run bash docs/architecture/scripts/check-env.sh and bash docs/architecture/scripts/secrets.sh --tracked — the first says what is not configured, the second is the one-time audit for what is already committed.
  6. Map the repository on the project's edit screen in the Command Center, then press Pull. One thing does not follow from mapping it and is silent when missing: register the repository's webhook and confirm a delivery arrives. Without one the site sees this repository only through the periodic sweep — the counts still arrive, a day late, and an opened P0 raises no alert at all.
  7. Skim docs/TOOLS.md — the fixed path every project has, saying which scripts can stop you and where to start in a fresh clone. Then read docs/WORK_CYCLE.md once, now rather than at the end of the first session. It is what happens every time a piece of work finishes, and the two steps it exists for — recording the next action and the blockers, and telling the site what changed — are the two that are invisible when they are skipped.

There is also a generated prompt for doing all of this with an agent, on the Docs template screen of the Command Center — editable before you copy it.