Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ The verdict vocabulary is [`WORKFLOW.md`][workflow]'s: **operational / not opera

```mermaid
flowchart TD
s0["0: has the repo been stood up? if not, STANDUP.md"] --> s1["1: scope, ground-truth branch (main)"]
s0m["0m: fleet membership, every owned non-fork repo has a registry entry"] --> s0["0: has the repo been stood up? if not, STANDUP.md"]
s0 --> s1["1: scope, ground-truth branch (main)"]
s1 --> s2["2: resolve the repo's type(s)"]
s2 --> s3["3: applicability gate, per check"]
s3 --> s4["4: per-dimension checks, letter and intent"]
Expand All @@ -28,6 +29,7 @@ flowchart TD

This audit is not occasional. Run it whenever you **create, adopt, or materially change** a fleet repo, and on demand for any known repo:

- **A full sweep opens with a fleet membership check, not a per-repo one.** `spec/audit.py`, run with no repo names, first lists every non-fork repository the registry `owner` actually owns on GitHub and diffs it against `registry/repos.json`. A repo that exists but carries no entry is invisible to every other check in this file, since all of them iterate the registry and never look past it, so this is the only place that gap is caught (ptr727/ProjectTemplate#550). The check also reconciles one field: a registry `status: "archived"` must agree with GitHub's own archived flag, in either direction. A name-filtered run or `--issue` skips it, since those are scoped to repos already known to the registry. Run this as `gh auth login` for the owner's own account: a fine-grained PAT scoped to "selected repositories" returns an incomplete list with no error, so the sweep would read clean while some repos were never inspected.
- **Onboarding a repo is complete only when it either passes this audit** (operational on every applicable check) **or carries a committed `reports/<repo>/audit.md` plus a tracking issue** enumerating every residual delta. A repo that is partially set up but never audited is itself a **defect**, the exact state this process prevents. The create-to-conformance counterpart is [`STANDUP.md`][standup]. Because both read the same manifests, a repo stood up by that file passes this audit by construction.
- **Touching a repo** (any conformance-affecting change) ends by re-running the applicable checks and **reconciling the registry entry to reality**: `status`, `types`, `releaseTrigger`, `workflowModel`, `driftNotes`. The registry records reality, not intent. [`spec/validate.py`][validate] proves the catalog is self-consistent, not that it matches the live repo. Closing that gap is this audit's job. The deterministic subset (settings, rulesets, secret names, file presence, per-scope Markdown section presence, workflow interface conformance, verbatim content, hub-hosted files a repo carries, branch facts) is mechanized in [`spec/audit.py`][audit-runner]: owner-initiated, run on demand when onboarding a repo, on suspected drift, or before fleet-wide changes. A required section missing from a carried Markdown file is a **drift finding**, not a letter, because a heading rename reads as missing and equivalence is judged by hand. A carried `interface` workflow (spec/fidelity-model.md) is checked by name and wiring (required jobs, the ruleset-bound check name, the artifact-name handoff, and the forbidden `artifact-ids:` fork), all at **drift**, since the body is owned and a rename is a hint to verify. A carried `verbatim` unit, whether a whole file (`.markdownlint-cli2.jsonc`) or a canonical workflow job region (the `github-release` job), is content-hashed against the hub's canonical after line-ending normalization. A mismatch is classified **stale** (matches a past hub revision, re-vendor) or **modified** (matches none, the repo changed fixed content), both at **drift**, since equivalence is intent-governed and a byte diff is a hint to review. A carried `intent` unit gets one advisory beyond presence, a last-modified comparison: a hub canonical changing after the copy's own last commit marks the copy as possibly trailing, at **drift**, a hint rather than proof, since a copy touched without reconciling reads current and content is never judged.

Expand All @@ -47,7 +49,9 @@ This holds for **both workflow models**. An `operational` repo commits directly

## 2. Resolve the Repo's Type(s)

Look up the repo in [`registry/repos.json`][repos] and read its `types[]`. If the entry is `classificationPending` (a backlog repo), classify it from the tree and propose a registry update:
Look up the repo in [`registry/repos.json`][repos]. An entry with status `archived` or `excluded` is out of scope for the rest of this procedure, so stop here rather than proceeding to section 3. `archived` means GitHub itself reports the repo archived, so no further conformance work applies. `excluded` means a maintainer decision took it out of audit scope, recorded in the entry's `exclusionReason`. Both still carry a registry entry precisely so the decision stays visible, per section 0's membership check, rather than the repo reading as an oversight.

Otherwise read its `types[]`. If the entry is `classificationPending` (a backlog repo), classify it from the tree and propose a registry update:

- `*.csproj` / `*.slnx` -> `csharp`, a `dotnet nuget push` workflow -> `nuget`, a `System.CommandLine` console -> `console`.
- `pyproject.toml` / `setup.py` -> `python`, a `pypa/gh-action-pypi-publish` workflow -> `pypi`.
Expand Down
1 change: 1 addition & 0 deletions GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ Every fleet repo is a standard-style repo the hub audits **downward** against it

- **Definition of done is the audit.** Onboarding or materially changing a repo is complete only when it passes [`AUDIT.md`](./AUDIT.md) for its type, **or** carries a committed `reports/<repo>/audit.md` plus a tracking issue for the residual deltas. Do not leave a repo partially stood up and unrecorded, which is itself a defect. Stand a repo (or a new type) up with `STANDUP.md`, and verify it with [`AUDIT.md`](./AUDIT.md). The two share the same manifests, so a repo stood up correctly passes the audit by construction. A repo that is stood up already and has fallen behind the hub is brought back into line with `RESYNC.md`, which is the third procedure and the one a request to sync a repo with the hub means. It exists because a findings list does not state the order its remedies apply in, and one of them deletes a file rather than updating it.
- **The registry is ground truth about reality, not intent.** After any conformance change, reconcile the repo's `registry/repos.json` entry (`status`, `types`, `releaseTrigger`, `workflowModel`, `driftNotes`) to what the repo actually is. `spec/validate.py` proves the catalog is self-consistent. It does **not** prove the catalog matches the live repo. A `validate.py`-clean entry can still be false, and a stale entry is a finding.
- **Every owned, non-fork repo carries a registry entry, full stop.** `spec/audit.py`'s fleet membership check (a full sweep, not a per-repo one) lists the owner's actual GitHub repos and flags any absent from the registry as a `DEFECT`, since every other tool here only ever iterates the registry and cannot see past it (ptr727/ProjectTemplate#550). An archived repo still gets an entry, `status: "archived"`, and an entry a maintainer has decided not to audit is `status: "excluded"` with a required `exclusionReason`. Both keep the decision visible rather than letting the repo disappear from the catalog.
- **Prove the docs suffice.** The onboarding docs are sufficient only when a context-free agent stands each supported repo shape (project type(s) plus workflow model) up from them alone. The cold-start self-test in `STANDUP.md` and the conformance matrix track that. A shape that cannot be stood up cold is a documentation defect, fixed in the hub, not worked around per repo.

## Hub-Hosted Tooling
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ This repo is the single home for those rules, a machine-readable spec they are c
- **[AUDIT.md][audit]** - how an agent audits a repository against the spec and reports drift.
- **[RESYNC.md][resync]** - how an agent brings an already-stood-up repository back into line, in the order the remedies require.
- **[spec/][spec]** - the machine-readable ground truth: project-type requirements, the file/section baseline, required/forbidden secrets, the host tool contract and its version floors, and the preferred README structure.
- **[registry/repos.json][repos]** - the fleet registry: every project, its type(s), publish mechanism, and status (cataloged or standardization backlog).
- **[registry/repos.json][repos]** - the fleet registry: every project, its type(s), publish mechanism, and status (cataloged, standardization backlog, archived, or excluded). `spec/audit.py` checks the registry against the owner's actual GitHub repos, not just against itself, so a repo can't go missing from it unnoticed.
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
- **[repo-config/][repo-config]** - hub-only branch rulesets, fleet settings, the apply script, and the GitHub setup reference (kept out of `.github/`, which is Actions-owned).
- **[host-setup/][host-setup-dir]** - the host guardrail kit, which is per machine rather than per repository.
- **[catalog/][catalog]** - reusable reference snippets (workflow tasks, config exemplars, devcontainers) the audit compares implementations against.
Expand Down
2 changes: 2 additions & 0 deletions STANDUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ The rename runs unconditionally rather than behind a test of `init.defaultBranch

## 1. Classify and Catalog

A repo lacking any entry is exactly what [`AUDIT.md`][audit] section 0's fleet membership check surfaces on a full sweep, a `DEFECT` naming the repo by its GitHub `full_name`. That finding is what should send an agent here in the first place for a repo nobody has stood up yet, rather than the omission sitting unnoticed (ptr727/ProjectTemplate#550).

Resolve the repo's type(s) with the [`AUDIT.md`][audit] section 2 detection rules, then write or repair its [`registry/repos.json`][repos] entry: `status`, `types[]`, `groundTruthBranch`, `hasDevelop`, `publish[]`, `requiredSecrets[]`, `consumerModel`, `releaseTrigger`, `workflowModel` (omit to take the `release` default), `configLayout`, and `driftNotes` that describe what the repo **actually is**. Run [`spec/validate.py`][validate] to confirm it classifies cleanly. The registry is ground truth about reality, not intent, and a `validate.py`-clean entry is still false if it disagrees with the live repo.

## 1A. Carry the Instruction Set, Before Authoring Anything
Expand Down
18 changes: 0 additions & 18 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,6 @@ One pull request moving the canonical short description into declared data, so e
- **Settled** - The field is optional at first so the audit falls back to the README intro while repos adopt it, and it needs a schema entry because `registry/repos.schema.json` sets `additionalProperties: false`.
- **Settled** - The ask on the Docker repos meanwhile is only that the parsing step is not propagated further.

### Registry Membership Coverage

One pull request asking the inverse question the fleet tools never ask, whether a repository that exists has a registry entry, since every tool iterates the registry and an omission at standup is permanent and silent.

**State** `decision`. **Touches** [`spec/audit.py`][audit], [`registry/repos.json`][repos] and its schema, and [`STANDUP.md`][standup]. **Cost** one hub edit, hub-only.

- **Report a non-fork repository under the owner that has no registry entry.** The reports read as complete while under-counting today.
- **Blocked by** - Nothing.
- **Issue** - [#550][issue-550], which carries the four repos the comparison found.
- **Checked** - `develop` at `362aec8`, per the issue, and unverified since.
- **Open** - How a deliberate exclusion is recorded, since without one the check becomes a permanent four-line complaint people learn to scroll past, and the candidates are a third `status` value or a separate list carrying a reason per entry.
- **Open** - Where the check runs, since neither `validate.py` in CI nor an owner-initiated audit catches an omission at the moment it is made, which is the standup itself and the moment the fix costs one line.
- **Settled** - The consequence is worse than a gap, because the reports are confidently wrong rather than silent: [reports/divergences.md][divergences-report] counted 19 repos owing `AGENTS.md` "Fleet Bootstrap" when the real number was 20.
- **Settled** - The procedure is not the gap, since [`STANDUP.md`][standup] section 1A already says to write the entry and names every field, and nothing verifies it happened.
- **Settled** - The reason matters more than the mechanism, since an unexplained exclusion is the same silent omission in a different file.
- **Settled** - Private repositories are outside the public listing the issue used, so the true count is a floor rather than a total.

### Reducing the Carried Surface Further

One pull request measuring the remaining carried surface against the carry-versus-reach test and moving whatever qualifies, now that the model is settled rather than open.
Expand Down Expand Up @@ -535,7 +518,6 @@ Regenerate [reports/divergences.md][divergences-report] before using it as the w
[issue-509]: https://github.com/ptr727/ProjectTemplate/issues/509
[issue-521]: https://github.com/ptr727/ProjectTemplate/issues/521
[issue-523]: https://github.com/ptr727/ProjectTemplate/issues/523
[issue-550]: https://github.com/ptr727/ProjectTemplate/issues/550
[issue-558]: https://github.com/ptr727/ProjectTemplate/issues/558
[issue-578]: https://github.com/ptr727/ProjectTemplate/issues/578
[issue-580]: https://github.com/ptr727/ProjectTemplate/issues/580
Expand Down
18 changes: 18 additions & 0 deletions registry/repos.json
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,24 @@
"releaseTrigger": "dispatch-only",
"configLayout": { "rulesetsDir": "repo-config", "pythonConfig": null },
"driftNotes": ["Hugo static site migrated off WordPress.com, stood up 2026-08-01; release model with a dispatch-only publisher that cuts the tag and a source archive.", "lineEndings lf on a release repo, where the rule grants the native-platform default to operational repos only: every consumer is Linux (the Hugo build, the Caddy container, and the rsync deploy).", "content/ is an imported WordPress archive, so the prose, spelling, and style gates are scoped to exclude it.", "Deploy credentials are per-environment GitHub Environment secrets and variables, which spec/secrets.json has no vocabulary for, so requiredSecrets stays empty and the names are declared in this repo's own secrets.json environments block; listing them here would make the audit demand them in the repository actions store."]
},
{
"name": "DiskSpeedTest",
"url": "https://github.com/ptr727/DiskSpeedTest",
"status": "archived",
"driftNotes": ["Archived on GitHub. Found missing from the registry by the fleet membership check (ptr727/ProjectTemplate#550). No further conformance work is expected while archived."]
},
{
"name": "GEM-Echo-Server",
"url": "https://github.com/ptr727/GEM-Echo-Server",
"status": "archived",
"driftNotes": ["Archived on GitHub. Found missing from the registry by the fleet membership check (ptr727/ProjectTemplate#550). No further conformance work is expected while archived."]
},
{
"name": "GoogleTo1Password",
"url": "https://github.com/ptr727/GoogleTo1Password",
"status": "archived",
"driftNotes": ["Archived on GitHub. Found missing from the registry by the fleet membership check (ptr727/ProjectTemplate#550). No further conformance work is expected while archived."]
}
]
}
12 changes: 9 additions & 3 deletions registry/repos.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,19 @@
"$comment": "An operational repo must declare its line endings (release repos use the fleet LF default).",
"if": { "properties": { "workflowModel": { "const": "operational" } }, "required": ["workflowModel"] },
"then": { "required": ["lineEndings"] }
},
{
"$comment": "An excluded repo records the deliberate decision that put it out of audit scope, not just the fact.",
"if": { "properties": { "status": { "const": "excluded" } }, "required": ["status"] },
"then": { "required": ["exclusionReason"] }
}
],
"additionalProperties": false,
"properties": {
"name": { "type": "string" },
"url": { "type": "string", "format": "uri" },
"status": { "enum": ["cataloged", "backlog"] },
"name": { "type": "string", "minLength": 1 },
"url": { "type": "string", "format": "uri", "minLength": 1 },
"status": { "enum": ["cataloged", "backlog", "archived", "excluded"] },
"exclusionReason": { "type": "string", "minLength": 1, "pattern": "\\S" },
"types": { "type": "array", "items": { "type": "string" } },
"profiles": { "type": "object", "additionalProperties": { "enum": ["lint-only", "build"] } },
"classificationPending": { "type": "boolean" },
Expand Down
Loading
Loading