Depending on id-less documents
Point a depends_on entry at a document with no id – a README, a guide, a
campaign brief. Then pick exactly which upstream statuses count as done
enough to depend on.
Before you start
Some documents live under one namespace and one file: a README, a
getting-started guide, a lone campaign brief. They carry no id: field, and
they never need one. Until now, depending on one meant giving it an id it did
not need.
An entry in depends_on now resolves as exactly one of three forms. ctxgrd
picks the right one from how you write the entry – no prefix, no extra
syntax.
Step 1: Write the address that fits the target
- A document id –
ADR-7. Works as it always has. - A bare namespace name –
README. Works only when that namespace’spathsnames one literal file, with no*or**in it. - A repo-relative path, including the
.mdextension –docs/positioning/001-launch.md. Works for any claimed document, id-less or not.
Configure the two namespaces:
[README]
paths = ["README.md"]
rules = ["core.required-headings"]
[POSITIONING]
paths = ["docs/positioning/**"]
rules = ["core.required-headings"]Depend on both, each in the form that fits it:
depends_on: [README, docs/positioning/001-launch.md]README addresses the whole namespace, because [README].paths names one
file. docs/positioning/001-launch.md addresses one file directly. Its
namespace’s paths is a glob, and no bare name could pick one target from a
directory of them.
$ ctxgrd
ok: 5 documents · 12 rules · 0 diagnosticsStep 2: Recognize the three ways an address fails
Naming a glob namespace by its bare name. ctxgrd names the namespace’s
paths and points you at the forms that work:
error[core.dep-resolved]: depends_on entry 'POSITIONING' does not resolve to a document in the run
--> docs/adrs/001-example.md:6:0
|
6 | depends_on: [README, POSITIONING]
| ^
|
help: name one document instead — its path (`docs/.../001-slug.md`) or its id (`POSITIONING-<n>`)
note: `POSITIONING` addresses a namespace as a whole, which resolves only when its `paths` names a single file; [POSITIONING].paths is `docs/positioning/**`Pointing at a file no namespace claims. The path form only reaches
documents. A .md file with no id: that no [<NS>].paths glob covers is
not a document, and ctxgrd says why:
error[core.dep-resolved]: depends_on entry 'docs/notes/scratch.md' does not resolve to a document in the run
--> docs/adrs/001-example.md:6:0
|
6 | depends_on: [README, docs/positioning/001-launch.md, docs/notes/scratch.md]
| ^
|
help: add a `paths` glob covering it to a namespace in ctxgrd.toml, or an `id:` to its frontmatter
note: the file exists but no namespace claims it — an unclaimed .md file is not a document (DOC-001)Name a path that does not exist on disk at all, and the error stays. The note drops – there is no file left to explain.
error[core.dep-resolved]: depends_on entry 'docs/nowhere/does-not-exist.md' does not resolve to a document in the run
--> docs/adrs/001-example.md:6:0
|
6 | depends_on: [docs/nowhere/does-not-exist.md]
| ^
|
help: no document in the run has location `docs/nowhere/does-not-exist.md` — check the spelling, and note the path is repo-relative and includes the `.md` extensionWriting the internal placeholder id. An id-less document is tracked
internally as <NAMESPACE>-0. That value is never a real address.
CAMPAIGN-0 in depends_on behaves like naming any other id that does not
exist:
error[core.dep-resolved]: depends_on entry 'CAMPAIGN-0' does not resolve to a document in the run
--> docs/adrs/001-example.md:6:0
|
6 | depends_on: [README, docs/positioning/001-launch.md, CAMPAIGN-0]
| ^
|
help: create the missing document (`ctxgrd new CAMPAIGN "..."`) or remove `CAMPAIGN-0` from `depends_on`Use the namespace name or the path instead.
Step 3: Watch for two files claiming one namespace
The bare-namespace form only works while exactly one file claims the
namespace. paths can list several literal filenames for one document. A
CONTRIBUTING.md a repository keeps at its root or under .github/ is one
example:
[CONTRIBUTING]
paths = ["CONTRIBUTING.md", ".github/CONTRIBUTING.md"]
rules = ["core.required-headings"]If both files exist at once, ctxgrd warns on the one it does not treat as authoritative:
warning[cfg.singleton-conflict]: [CONTRIBUTING] names a single document, but this file and `CONTRIBUTING.md` both claim it
--> .github/CONTRIBUTING.md
help: delete this file, or widen [CONTRIBUTING].paths to a glob if the namespace really holds many documents
note: [CONTRIBUTING].paths is `CONTRIBUTING.md` or `.github/CONTRIBUTING.md` — a list of literal filenames names one document in whichever of those locations a repository prefers, not one per entry. `CONTRIBUTING.md` matches the earlier pattern, so it is the one `CONTRIBUTING` addressesThis is a warning, so the exit code stays 0. The file matching the earlier
entry in paths is the one README-style addresses reach. Delete the extra
file, or widen paths to a glob if the namespace really does hold many
documents.
Step 4: Control which upstream statuses count as done
core.dep-status blocks a document at a settled status from depending on one
that is not. Its terminal parameter lists the statuses your own namespace
treats as settled. By default, every upstream is checked against that same
list.
That single list breaks down once you depend on two upstreams with different
vocabularies. A checklist’s real end states are sealed and abandoned –
both mean the work stopped. Widen terminal to accept abandoned, and a
sealed checklist passes. So does an abandoned one, which was never the
intent.
satisfied-by names, per upstream namespace, which of its statuses actually
count:
[CAMPAIGN."core.dep-status"]
terminal = ["live", "killed"]
satisfied-by = { CHECKLIST = ["sealed"] }An upstream named in satisfied-by is checked only against its own list. Any
upstream left out falls back to terminal, exactly as before you added the
table. The check works the same whether or not the documents involved carry
an id. This example gives both an id, so the message below stays readable:
error[core.dep-status]: CAMPAIGN-1 is `live` but depends on CHECKLIST-1, whose status `abandoned` is not one of `sealed`
--> docs/campaigns/001-launch.md:5:0
|
5 | depends_on: [CHECKLIST-1]
| ^
|
help: move CHECKLIST-1 to a status [CAMPAIGN."core.dep-status"].satisfied-by accepts for CHECKLIST, or widen that entryMove the checklist to sealed, and the same configuration reports clean:
$ ctxgrd
ok: 2 documents · 5 rules · 0 diagnosticsterminal keeps its other job. It still decides whether your own document is
settled enough for the rule to check its dependencies at all.
Verify what a rule accepts
ctxgrd rules <code> lists every parameter a rule declares, whether you have
used it yet or not:
$ ctxgrd rules core.dep-statusAttribute terminal (config, optional)
Attribute satisfied-by (config, optional)
Attribute severity (config, optional: error|warning)Run it before writing a parameter table, not after. A key it does not list is a typo. ctxgrd now reports that at config-load time, instead of leaving the rule silently inert.
Further reading
- How ctxgrd decides what to lint – the id-claim and path-claim model these address forms build on.
- CLI reference – the full
ctxgrd rulescommand surface. - Polling a feature done-signal in an agent loop
– driving
status --exit-codefrom the same dependency graph.