The remaining half of #16 was not a decision. It was three documents being wrong
about their own audience.
A freshly scaffolded project failed doc-claims on four PATH claims:
docs/architecture/scripts (twice), docs/architecture/githooks, and
docs/architecture/scripts/release.sh -- named by DOC_TRUST_MAP.md, TOOLS.md and
WORK_CYCLE.md. I had modelled that as a tension between documents that were
correct and a scaffold that declined to create what they named, and filed it
needing a call from Kaspa between three unattractive options.
The evidence says otherwise. Every script's own header reads "Copy to
`scripts/<name>`", the hooks install to `.githooks/`, and FIVE documents already
use that project-relative form -- OPERATIONS.md, architecture/README.md,
GUARDS.md and parts of TOOLS.md and DOC_TRUST_MAP.md. Only three used
`docs/architecture/...`, which is where the scripts live in THIS repository and
nowhere a project that adopts them will ever look.
So the documents now name the layout their reader will actually have. No tooling
change, no empty directories, and the claims get more accurate rather than
vaguer -- the opposite of the direction I was leaning.
Verified both ways, since a fix that only works in one tree is what produced the
bug: the template stays green at 112 claims, and a freshly scaffolded project
committed and checked exits 0 for the first time, with the bare-filename notes
from f5fd67b reported as information rather than failure.
Worth recording why this was invisible from inside: every path in question
resolves here. The documents were only wrong from a vantage point this
repository does not have, which is why scaffolding into a scratch directory
found it and reading it here never would.
closes#16
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collection started at DOCS.rglob("*.md"), so nothing at the root was read.
README.md, project-readme-template.md and both START-HERE documents carry a full
status header, and Governs lines nothing ever looked at. Changing a file they
govern fired nothing, and the run said "No document's Governs matched these
paths" -- true of the tool, false of the repository.
Third instance of one shape in two days, each a level further out. Documents
whose Governs carried a gloss were handed a glob no file could satisfy; before
that a root resolved by depth pointed the whole tool outside the repository; here
four documents were never collected at all. Every one of them printed something
reassuring while checking less than it claimed.
Root documents are now collected alongside the tree. The root walk is glob, not
rglob -- deliberately one level deep, so a vendored copy of this template, a
scratch checkout or somebody's directory of notes cannot enrol its documents as
governing the project that holds it. Verified: a vendor/Template/README.md
declaring Governs: src/** is not consulted.
## Status is what separates a document from a template for one
project-readme-template.md carries `Status: <Current | Draft | Superseded |
Archived>` and a Governs describing the README of whichever project copies it.
Collecting the root without a guard would trade a document that never fires for a
template that always does, which is the pair of failures this script has spent
two days on.
DOC_TRUST_MAP.md already makes the status vocabulary a rule with a checker behind
it, so that is the test: a document whose Status is not one of the four words is
not treated as governing anything here. It is **named, not dropped** -- when such
a document governs a path in the change it is listed with its status, because a
silent exclusion is the failure being fixed, not a smaller version of it. A
document that governs a subject rather than paths can never fire mechanically, so
an unfilled one is left out of the judge-these-yourself list entirely rather than
sitting in it permanently.
Both branches were exercised: a root template governing src/** is named and not
fired; the same file with Status: Current fires normally.
The no-match message now covers this case too, rather than claiming nothing
matched when something did and was set aside for a stated reason.
## Documents
architecture/README.md's row says where doc-triggers reads from, which has
changed. DOC_TRUST_MAP.md owns the status header: it now says root documents
carry one and are read, that the root is one level deep and why, and what the
four status words separate.
Verified from a clean clone: touching START-HERE-New-Project.md fires README.md;
docs/ behaviour is unchanged across a modified script, a modified document, a
modified githook, a branding asset and a staged deletion; the scripts/ copy still
resolves its own root. doc-claims reads 116 claimed paths across 23 files, all
present.
closes#22
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs/data/README.md, docs/data/img/README.md and
docs/architecture/githooks/README.md have never fired for anything, since the
first commit. They were not reported as skipped either -- they fell into neither
list, so nothing on screen said they had not been checked.
Each carries a Governs entry that explains itself after the glob:
Governs: docs/data/** -- the assets privacyllc.dev renders for this project
Governs is split on commas only, so that is one entry and the whole string was
used as the glob. It contains a slash, so looks_like_path() called it a path and
the document was classified as path-governing -- which also kept it out of the
"govern a subject rather than paths, judge them yourself" list, the one that
exists so a reader does not conclude everything was checked. Then matches()
tested the file against a glob ending "renders for this project", which is false
and always would be.
Same class as the previous commit and the opposite sign, which makes it worse.
That one fired a document when it should not: a false prompt, costing a glance.
This one silently did not fire when it should, costing a document that goes
quietly stale while the tool reports success. GUARDS.md opens with the sentence
that applies -- a guard that cannot fail is worse than no guard, because it is
trusted.
docs/data/img/README.md governs the branding assets, which is the subject of open
issue #14. Editing them had never once prompted the document that specifies their
names, dimensions and ceilings.
The glob is now extracted from the entry: cut at the first spaced em dash, en
dash or --, then take the tokens on the left that themselves look like paths,
falling back to the entry unchanged if that yields nothing.
Three details are load-bearing:
- The cut requires whitespace both sides. A bare - would halve source-grep and
doc-claims, both of which appear in these headers.
- Tokens come from the left of the gloss, not the whole entry. privacyllc.dev in
the docs/data gloss passes looks_like_path on the extension rule and would
otherwise become a glob firing on a file nobody has.
- Classification still reads the whole entry. Deciding path-or-subject on a token
would move documents between the two lists as a side effect of this fix.
A trailing / on a glob now means the directory and everything under it.
githooks/README.md governs "the .githooks/ a project installs", which extraction
yields as a bare .githooks/, and fnmatch would not match a file inside it.
DOC_TRUST_MAP.md owns the header schema, so it now states the gloss form and that
it is the only one recognised -- a gloss in parentheses or after a colon puts a
document straight back into silence, which is the failure that was invisible here
for the life of the repository.
Verified: both docs/data documents fire on docs/data/img/icon.webp when it is
added, deleted and modified; githooks/README.md fires on
docs/architecture/githooks/pre-commit and on .githooks/pre-commit; privacyllc.dev
matches nothing; the split stays 7 path-governing and 12 subject-governing,
exactly as before. The previous commit's behaviour is unchanged -- a modified
script still fires TOOLS.md and architecture/README.md and not DOC_TRUST_MAP.md.
closes#21
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DOC_TRUST_MAP.md declares `Governs: docs/**`, the broadest glob in the tree,
while its Review trigger is one of the narrowest -- any doc added, deleted or
moved. Matching on the glob alone fired it on every edit to every document,
forever, and correctly by the only rule the tool had. Touching one script fired
three documents and exactly one of them applied.
A prompt that always fires is one people stop reading, and it takes the true
positives with it. This tool exits 0 by design -- it is a prompt, not a gate --
which makes it more vulnerable to that, not less, because nothing forces the
reading.
Documents now declare the kinds of change their trigger names, in an optional
`Fires on:` header field, read against git's own status letter. Absent, empty or
unparseable means every kind, so nothing changes for the six other
path-governing documents and a document is only ever quietened by somebody
writing the line deliberately.
## Why declared rather than read out of the trigger prose
The obvious first cut is to look for added/deleted/moved with no changed/change
to. Tried against the seven path-governing documents here, it misclassifies the
one it exists to fix: DOC_TRUST_MAP.md's trigger ends "any change to which doc
owns a subject", so it reads as a change-verb. That clause is about which
document owns a subject, not about a file being edited, and nothing lexical
separates it from architecture/README.md's "any change to a module boundary or a
data shape", which genuinely does mean modification.
Guessing at English is silent in the expensive direction: a document wrongly
read as existence-only stops being prompted for and goes quietly stale, which is
the failure this whole tool exists to prevent. So the narrowing is declared or it
does not happen.
## Also
changed_paths now carries a status letter per path, from --name-status for
--staged and --range and from the porcelain columns for the working tree. Paths
named on the command line have no diff to read, so the kind is inferred: absent
from disk is a deletion, present but untracked is an addition, otherwise a
modification.
Documents that govern a path in the change but do not fire on its kind are named
in their own short block rather than dropped, because a reader who saw nothing
would have to guess whether they had been considered. The no-match message now
distinguishes "nothing governs these paths" from "governed, but not this kind of
change" -- the second is a declaration somebody wrote, not an unclaimed area.
Verified: modifying a script fires TOOLS.md and architecture/README.md and not
DOC_TRUST_MAP.md; adding, deleting and moving a document under docs/ each still
fire it; modifying a document fires nothing; an unknown word warns and fires on
everything; an empty or absent field fires on everything.
closes#20
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The template shipped scripts to back up, deploy, check health and read the
deployed version, and no document saying where errors go, what alerts, who
receives it, or what to run first when it is down. Grep across docs/** found
zero mentions of error tracking, observability or database restore.
Five sections. Where errors go, with the distinction that matters at 3am --
healthcheck.sh answers "is it up", error tracking answers "is it working", and a
service returning 500 to everything is up. What alerts and to whom, naming a
person rather than a channel nobody owns. Backups, whose last row is the date of
the last verified restore, because a backup nobody has restored is a guess.
Rate limits and cost ceilings, *(precautionary)*. And an ordered "it is down,
what now" where every step is a command that changes nothing.
Marked *(only for a deployed service)*, with the instruction to delete rather
than keep the headings unanswered: an empty runbook reads as one nobody wrote,
which is worse than one that never applied.
scaffold.sh now lays it down (17 files, 0 skipped) and DOC_TRUST_MAP.md points
at it from both tables. Two prose counts in scaffold.sh's header said
"thirteen documents" and were already stale; they no longer carry a number,
since a count in prose beside a list in code drifts the moment the list grows --
which is what just happened.
closes#6
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This tree is copied into every project and some of what it carries will not
apply to all of them. Rather than several templates, or scaffold profiles the
website's conformance reader would have to know about before it could tell a
legitimately-absent file from a missing one, entries say so inline.
Two markers, on axes that are deliberately not merged:
applicability -- *(only for a deployed service)*, *(only where money moves)*,
*(only where there are accounts)*. Does this project have to
do this at all?
provenance -- *(precautionary)*. Was the rule earned here, or borrowed?
They are independent, and one word cannot say both. Rate limiting is
universally applicable and has never bitten us. Money flowing backwards applies
only to projects that take money and is the most common defect in the audits it
came from. Applicability tells a reader whether to keep a rule; provenance tells
them whether to argue with it.
The instruction that travels with the marker is ClaudeQAPlan.md's rule about
passes, generalised: if it does not apply, delete it. A pass that never applies
is noise; a pass that is always skipped is a lie -- and so is a checklist row,
and so is a whole document. Deleting is safe because DOC_TRUST_MAP.md is the
trust map: what a project keeps is what it meant to keep.
Applied where it was already true: the authorisation group is conditional on
having accounts, and the header/TLS and test-environment rows on being a
deployed service. A library was being told it lacked a CSP.
closes#1
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The basis for every project here was itself unversioned: no .git, no remote,
no history. Changes to it had no diff and no revert, and two of its own guards
could not run at all -- doc-claims.sh and doc-triggers.py both read git
history, so the script written to catch documentation drift could not be run
against the documents that define drift.
This is the tree as it stands, including work that until now existed only as
loose files on disk: WORK_CYCLE.md, TOOLS.md, the Portainer image-line fix in
deploy.py, the status vocabulary corrected to the four words the conformance
checker actually enforces, the Exempt: mechanism documented, and the Forgejo
instance named in README.md.
secrets.sh --tracked reports one candidate, migrate.sh:480. It is the comment
documenting the three Postgres credential shapes that script redacts, with
literal placeholders, and it is left alone deliberately: GUARDS.md section 2
is that a source-grep guard must tell code from the comment about code, and
deleting an explanation to quiet a scanner is the failure it names.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>