298 lines
18 KiB
Markdown
298 lines
18 KiB
Markdown
# Project Template
|
|
|
|
A working documentation skeleton, the tracker convention that goes with it, and
|
|
the 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](https://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](https://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`:
|
|
|
|
```bash
|
|
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 `Batch 02 — Payments`, and **start it with a
|
|
word rather than a version number**: the dashboard reads a leading version
|
|
token as the whole phase and drops the rest, so `0.2 Payments` displays as
|
|
`0.2` and the batch loses its name on the project card. No comma either — a
|
|
comma breaks the `milestones=` filter and the card shows the wrong next
|
|
action. 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 — and `--built`, the compiled bundle, which is the artifact users actually receive and the one the repository scan never sees |
|
|
| `backup.sh` `restore-check.sh` | a dump verified before it is trusted, and the restore that proves it is a backup rather than a file. The second exists because the first says so: *a backup nobody has restored is a guess.* |
|
|
| `preflight.sh` | headers, TLS, and — behind `--auth` — login rate limiting and account enumeration, against a live URL. Refuses any host but its configured origin. |
|
|
| `controls.sh` | which operational controls this project has, each row saying whether that is measured, declared, not applicable, or simply unknown |
|
|
| `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.
|