Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
e390bea
fix(ahoy): refuse the plugin-root link on a cold cache; own a recorde…
REPPL Sep 27, 2026
4cb8e7b
chore: resolve iss-2609100506263330 — ahoy install refuses the cold-c…
REPPL Sep 27, 2026
f49293e
chore: capture iss-2609280932480608 — an owned dangling entry ahead o…
REPPL Sep 28, 2026
c094510
fix(ahoy): adopt an owned copy on a cold cache instead of re-offering…
REPPL Sep 28, 2026
124b245
feat(lint): a banned token's extra_roots widen that token alone
REPPL Sep 28, 2026
b4df4f2
feat: abcd's own text names the product thinker or the technical faci…
REPPL Sep 28, 2026
2c80445
fix(ahoy): uninstall removes a recorded dangling entry with no plugin…
REPPL Sep 28, 2026
f87ae19
chore: name the uninstall follow-up in iss-2609100506263330's resolution
REPPL Sep 28, 2026
7711d16
fix(ahoy): remove an owned dangling entry ahead of the install; say a…
REPPL Sep 28, 2026
909e413
chore: resolve iss-2609280932480608 — install removes an owned dangli…
REPPL Sep 28, 2026
7efa5cc
docs(decisions): record the stranded-entry ruling and correct the dan…
REPPL Sep 28, 2026
819dd3c
chore: capture iss-2609281017573862 — path-entry and attestation read…
REPPL Sep 28, 2026
79923a9
fix(lint): a non-markdown file in roots is refused, not read as nothing
REPPL Sep 28, 2026
d48acdc
chore: resolve iss-2609281045487620 — a non-markdown root is refused
REPPL Sep 28, 2026
993dc7b
chore: close spc-2609212141412864, shipping itd-2609212137129937
REPPL Sep 28, 2026
f85d271
fix(implement): close the run state's same-uid hygiene gaps
REPPL Sep 28, 2026
b3f77f3
feat(implement): count declared agents and log the fields the report …
REPPL Sep 28, 2026
bd6f6ac
feat(build): a build started for a session claims its intent in the s…
REPPL Sep 28, 2026
e69640b
chore: resolve iss-2609230720193756 — run-state hygiene gaps closed
REPPL Sep 28, 2026
aadb8ac
chore: resolve iss-2609240646549900 — ceiling_overrun event and repor…
REPPL Sep 28, 2026
e04df6e
chore: resolve iss-2609240646544930 — a join just before a window cou…
REPPL Sep 28, 2026
3715582
chore: resolve iss-2609240646555891 — required log fields and report …
REPPL Sep 28, 2026
e93dd8d
chore: resolve iss-2609240646542516 — the verb counts declared agents…
REPPL Sep 28, 2026
557c64c
chore: resolve iss-2609252050506863 — build --session claims its inte…
REPPL Sep 28, 2026
ee9e01d
fix: every stop in the plugin pages names whose answer it waits on
REPPL Sep 28, 2026
88830a3
docs: drop an unrecorded hat and gloss the product thinker for contri…
REPPL Sep 28, 2026
a34d644
chore: capture the history registry writing through a symlinked ~/.abcd
REPPL Sep 28, 2026
2f7e2a3
fix(ahoy): report a stranded entry's removal as information, not a re…
REPPL Sep 28, 2026
734b26a
fix: refuse a symlinked ~/.abcd in every reader and writer abcd trust…
REPPL Sep 28, 2026
e6df1ca
chore: resolve iss-2609281017573862 and iss-2609260958587561 — ~/.abc…
REPPL Sep 28, 2026
7cf24a4
fix(guard): read a parameter expansion as an unknown word, and three …
REPPL Sep 28, 2026
7dcb5cf
chore: capture the carried-variable remainder of the parameter-expans…
REPPL Sep 28, 2026
389149b
chore: resolve iss-2609251824244354 and iss-2609270036253187 — the gu…
REPPL Sep 28, 2026
a0c42d1
chore: capture the append primitive's symlink race past an empty Lstat
REPPL Sep 28, 2026
1c87837
fix(guard): a string's quotes apply to a variable's value, not its name
REPPL Sep 28, 2026
35b1835
fix(fsutil): refuse a leaf linked between the append's Lstat and its …
REPPL Sep 28, 2026
11aba60
chore: resolve iss-2609281229109140 — the append refuses a leaf linke…
REPPL Sep 28, 2026
772515b
fix(guard): a here-document body reaches the commands its owner pipes…
REPPL Sep 28, 2026
7d96abd
chore: name the everyday cost of carried-value classes 1 and 3
REPPL Sep 28, 2026
2bf816d
fix(build): the JSON says a session-less build holds no claim
REPPL Sep 28, 2026
9b39a6b
style(fsutil): give openAppendIn its doc comment back from the test seam
REPPL Sep 28, 2026
10b3c66
fix(fsutil): refuse a home-scoped rel that is not a clean relative path
REPPL Sep 28, 2026
7dbd624
fix(ahoy): keep the history registry out of a symlinked ~/.abcd
REPPL Sep 28, 2026
043e0d2
chore: resolve iss-2609281129171021 — the history registry behind a s…
REPPL Sep 28, 2026
b47a60f
chore: capture the by-path check behind the symlinked ~/.abcd rule
REPPL Sep 28, 2026
f8e1438
Merge fix/drain-symlinked-home (drainHome, containing drainH) into in…
REPPL Sep 28, 2026
b5b25b1
Merge fix/drain-implement (drainI) into integ/land-12
REPPL Sep 28, 2026
c7a4dd6
Merge fix/drain-guard-3 (drainG3, containing drainG) into integ/land-12
REPPL Sep 28, 2026
9024d4b
Merge feat/roles-not-maintainer (roles) into integ/land-12
REPPL Sep 28, 2026
83596e7
fix: the citation confirmation names whose word it waits on
REPPL Sep 28, 2026
3d5055c
merge: bring main (#736) into the integration branch
REPPL Sep 28, 2026
53728ee
chore: re-measure the reading windows after bringing main in
REPPL Sep 28, 2026
c72b665
merge: bring main (#737) into the integration branch
REPPL Sep 28, 2026
2144bcd
chore: re-measure the reading windows after bringing main in
REPPL Sep 28, 2026
cafe93e
Merge branch 'main' into integ/land-12
REPPL Sep 28, 2026
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
24 changes: 12 additions & 12 deletions .abcd/config/reading-presets.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@
"test"
],
"window": {
"tokens_est": 1340000,
"measured_tokens_est": 1325292,
"measured_bytes": 5102376,
"measured_at": "0dd22bdc043952a36800be0d41d72ac65b23055d"
"tokens_est": 1350000,
"measured_tokens_est": 1332620,
"measured_bytes": 5130589,
"measured_at": "c72b6658f76425e3a5265054190527f5a411c17d"
}
},
"entailment": {
Expand Down Expand Up @@ -132,10 +132,10 @@
"intent-projection"
],
"window": {
"tokens_est": 390000,
"measured_tokens_est": 383234,
"measured_bytes": 1475453,
"measured_at": "0dd22bdc043952a36800be0d41d72ac65b23055d"
"tokens_est": 400000,
"measured_tokens_est": 387345,
"measured_bytes": 1491280,
"measured_at": "c72b6658f76425e3a5265054190527f5a411c17d"
}
},
"comparative": {
Expand Down Expand Up @@ -216,10 +216,10 @@
"test"
],
"window": {
"tokens_est": 1350000,
"measured_tokens_est": 1334328,
"measured_bytes": 5137164,
"measured_at": "0dd22bdc043952a36800be0d41d72ac65b23055d"
"tokens_est": 1360000,
"measured_tokens_est": 1341656,
"measured_bytes": 5165377,
"measured_at": "c72b6658f76425e3a5265054190527f5a411c17d"
}
}
}
Expand Down
56 changes: 41 additions & 15 deletions .abcd/development/brief/04-surfaces/01-ahoy.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ the table above is the sub-verb set, and the modes are the bare verb's flags.
the repository this checkout's own origin names, and the changes an apply
would make. A toggle it could not read reports `unknown`, never `disabled`.
The same request also reads the repository's merge hygiene, which abcd mirrors
and never sets: those settings encode a maintainer's workflow rather than a
and never sets: those settings encode the technical facilitator's workflow rather than a
security posture, and each is reported only when the API answered for it,
because `false` and "the API did not say" are different facts
(iss-2608270512210664).
Expand Down Expand Up @@ -216,16 +216,21 @@ are caller-controlled and line-oriented. `trusted-roots` and
`local-transcript-roots` are the two that widen what a session will trust, so
each is honoured only when it is a regular file this uid owns that no one else
can write, and a file failing either test is ignored with one line saying which
test it failed. `path-entry` is read through the shared guarded read instead:
a symlinked, non-regular or oversized file is refused, but its ownership and its
permissions are not checked, and the hook shims that consult it check neither.
test it failed. `path-entry` and `cache-attestation` are held to the same test,
by the install verb and by the hook shims that consult `path-entry`, and a record
failing it vouches for nothing.
`load-limits` is a setting, not a declaration, but it is read through the same
guard as the two that widen trust, and a file failing it, or holding a line that
does not parse, is reported loudly and both of its limits take their defaults.
`rules.json` is read through that guard too, because it injects text into every
session on the machine, but a file failing it — or failing to parse — fails the
rules load outright: nothing injects until it is fixed, and the file is named on
stderr.
stderr. None of these files is honoured behind a `~/.abcd` that is itself a
symlink, and nothing ahoy or the bootstrap writes there goes through one: each
refuses the link and names it, as the rules loader does for `rules.json`, while a
symlinked `~/.abcd` holding none of them reads as absent (the rule is stated once,
under *The two `.abcd/` scopes* in
[`05-internals/03-configuration.md`](../05-internals/03-configuration.md#the-two-abcd-scopes)).

There is **no workspace, host, or development-environment layer.** A folder a
user keeps their repos in groups nothing, and abcd does not privilege it. abcd
Expand Down Expand Up @@ -293,19 +298,31 @@ which is false (iss-95).
**The `PATH` entry is classified, not assumed.** Detection scans `PATH`,
resolving symlinks, and classifies each hit as abcd's own entry, the dev shim,
or a foreign binary. An abcd-owned entry anywhere on `PATH` is the install; with
none, the default location answers the same question. Three states are named
none, the default location answers the same question. A symlink whose target
has gone is abcd's own when it is the one a plugin update stranded or when the
home-scoped `path-entry` record names it, read exactly as the hook shims read
it; any other dangling link asserts no provenance (iss-2609100506263330).
Three states are named
rather than lumped together: an owned entry whose target has gone is dangling; an
install directory absent from `PATH` is required but not resolvable, for which
abcd prints a one-line export fix and never edits a shell profile; and any
`abcd` that comes *before* abcd's own entry is shadowed, because an entry that is
correct and never reached is not an install (iss-171). Install carries the two
correct and never reached is not an install (iss-171). A link whose target has
gone is the exception to "never reached" in wording, not in the gap: it runs
nothing, because the shell skips it, and what it still threatens is to answer
whatever reappears at its target, so neither the gap nor the note says it is what
runs. An owned one that is not the entry install acts on — typically a link a
plugin update stranded ahead of the one-liner's copy — is removed with its record
by an install that leaves a working entry of abcd's own behind it
(iss-2609280932480608), the same danglingness rule that clears one at the target
(iss-2609100506256636); an unowned one is named and left. Install carries the two
non-resolvable ones on its own result as notes, since a fresh user cannot run
the doctor by name on a machine where abcd is not yet on `PATH`.

**The name-guard scaffolding is reported at the granularity a maintainer can
act on.** Each absent artefact is a gap abcd will create; every other state is a
diagnostic, because abcd writes what is missing and never replaces what a
maintainer put there. A pre-commit guard present without abcd's own marker line is
**The name-guard scaffolding is reported at the granularity the technical
facilitator can act on.** Each absent artefact is a gap abcd will create; every other state is a
diagnostic, because abcd writes what is missing and never replaces what the
technical facilitator put there. A pre-commit guard present without abcd's own marker line is
foreign, and is reported rather than claimed as installed. A lint config with no
usable banned-names array, one that cannot be read, and one git ignores — so CI
never sees it, the state a public repo is in by default — are three distinct
Expand Down Expand Up @@ -353,8 +370,14 @@ produces (iss-2609012039117381) — and only when the home-scoped `path-entry`
record names that exact path as this machine's installed binary. The record is a
string comparison and no hashing, because adr-46 keeps the fast path at one file
test. Both install routes write it, and the ahoy installer writes it for **every**
entry shape it leaves on `PATH`: the owned copy, the pinned symlink it degrades
to when there is no verified artefact to copy from, and the dev shim. An entry
entry shape it leaves on `PATH`: the owned copy, the dev shim, and a working
pinned symlink into the plugin root that an earlier release wrote. The installer
never writes that symlink itself: with no verified artefact to copy from it
writes no entry, because a link into the plugin root dangles at the next plugin
update, and it names the install one-liner as the command to run first — the
one route that fetches and verifies the release binary on an explicit ask
(adr-38) — after which a re-run adopts the copy in place. A pin into the plugin
root is a required `symlink.legacy` gap whatever the cache holds (iss-2609100506263330). An entry
the record does not name is an install this rung refuses, and it is the one
state where a filesystem test alone would call the install healthy while every
hook quietly degrades, so the board raises it as a gap in its own right and
Expand Down Expand Up @@ -602,8 +625,11 @@ the same three-shape predicate detection classifies with, and only one of the
three is a pointer at all: the dev shim; the owned copy the `path-entry` record
names and whose bytes still hash to the recorded value, which is the default
install and a regular file pointing at nothing; and lastly a legacy symlink
whose target is this plugin's binary. Anything else is foreign and is left where
it stands. It leaves the entire `.abcd/` namespace and the history store intact.
whose target is this plugin's binary, or whose target has gone when it is the
link a plugin update stranded or the one the `path-entry` record names. The
recorded dangling link needs no plugin root to be recognised, so uninstall finds
it on `PATH` and removes it with its record on a machine where abcd itself is
gone. Anything else is foreign and is left where it stands. It leaves the entire `.abcd/` namespace and the history store intact.

**Uninstall then install is a tested round-trip invariant**: afterwards the
detection pass must report zero actionable gaps, and the resulting state must be
Expand Down
4 changes: 2 additions & 2 deletions .abcd/development/brief/04-surfaces/09-reflect.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ they are rendered into link text.

The receipt shape this surface consumes is the predecessor store's phase-audit
report, and the predecessor wrote it under `.abcd/logbook/`. **That location is
retired here.** A 2026-07-12 maintainer adjudication, made on iss-56, placed
runtime artefacts in the gitignored `.abcd/.work.local/logs/` tier instead;
retired here.** A 2026-07-12 adjudication on iss-56 placed runtime artefacts
in the gitignored `.abcd/.work.local/logs/` tier instead;
iss-73 carried out the relocation, and a detector holds it:
`TestNoRetiredLogbookLocationInSource` fails the build if any non-test Go source
under `internal/` so much as names `logbook`. A delivered `reflect` therefore
Expand Down
18 changes: 16 additions & 2 deletions .abcd/development/brief/04-surfaces/10-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ checked in six months. The currency lint answers that in one read-only pass, so
it can run on every commit at no cost and gate a release without a network.

The citation sub-tree is the writing half, and it is the only place abcd reaches
the network on behalf of documentation. It runs when a maintainer asks, never
the network on behalf of documentation. It runs when the technical facilitator asks, never
in a gate, which is what keeps the lint itself deterministic and offline.

## Sub-verbs
Expand Down Expand Up @@ -53,7 +53,7 @@ in a gate, which is what keeps the lint itself deterministic and offline.
rather than recorded as broken.
- **The citation confirmation** records that a human verified a citation the fetcher
could not read, either from named URLs or from a receipt file. Today the
maintainer clears the printed checklist and names the URLs on the command line;
technical facilitator clears the printed checklist and names the URLs on the command line;
the receipt form ships against a producer that does not exist yet, a generated
checklist page that would hand the file back (a later rung of the same intent).
Both forms write the same dated entry, so when the page arrives it is a second
Expand Down Expand Up @@ -116,6 +116,20 @@ promotion is reachable only by a human typing the flag.
banned tokens, each a blocker, so the published surface stays host-agnostic;
the `<!-- docs-lint: allow -->` escape covers the sanctioned exception,
attribution.
- **The two roles, named.** abcd's own text says which person it means: the
product thinker, who decides what to build, or the technical facilitator,
who decides how. The `roles/retired-role-word` banned token refuses the one
word that blurred them, as a blocker, and it reaches past the documentation:
its `extra_roots` add the plugin command pages, this repository's rules
overrides and the bundled rules source (itd-2609212137129937). An entry's
`extra_roots` widen that entry alone, reading every text file there and not
only markdown (a rules file is JSON), with `exempt_paths`, the escape and the
fence default applying as they do under `roots`; the rest of the family is
not armed there, and a missing extra root is a configuration error, as a
missing root is. `roots` itself holds markdown: a non-markdown file named
there would be read by nothing, so it is refused and pointed at
`extra_roots` (iss-2609281045487620). The escape covers an acknowledgement and a persona's outside
job title, nothing else.
- **Harness leak**, a separate rule from those tokens and armed here as a
blocker, refusing the two shapes a harness stamps onto text the repository did
not ask it to stamp: a live agent-session URL, and a tool's own "generated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ target's, is ahoy's shape, so it lives in the generated appendix of
[`01-ahoy.md`](01-ahoy.md#appendix-the-shipped-surface) and is not repeated here.

The identity verb's render is the follow-on surface and writes nothing: it proposes
a correction as a diff, and adopting it is always the maintainer's move.
a correction as a diff, and adopting it is always the product thinker's move.

## Flow

Expand Down
38 changes: 25 additions & 13 deletions .abcd/development/brief/04-surfaces/17-guard.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,18 @@ the package to it. Text beside one in the same word is also read as bash leaves
it when the output is empty. One nested past the depth the guard reads, one
holding a case command, or more of them where the program name could be than
the guard follows, is refused rather than left unread. A parameter expansion
holding a substitution prints its output, so its word is unknown from the `${`
on, and inside double quotes one ends at its own `}`, where a nested `"` opens a
string of its own. A here-document body is data, but the substitutions the shell
(`$VAR`, `$1`, `$@`, `${VAR:-git}`) prints a value the line does not hold, so it
is an unknown word by the same rule: `--$VAR` is every flag it could become, and
`$GIT` as the program is any program its known text allows. A `${…}` ends at its
own `}`, where a nested `"` opens a string of its own, and a substitution in its
text runs and is read. A variable that is the whole word is read as an operand,
as a wholly-substituted word is (`git push origin "$branch"`), and a string
handed to a shell carries its variables for that shell to expand, where they
are read by the same rule, the string's own quotes applying to the value. What a variable carries in from an earlier command
is not read: as a script it is not a stream, and as the program's name it is not
a `pkill` or `killall`, whose entries name only the program and an operand, nor a
bare interpreter inside a string, because a variable is how ordinary commands
carry a program or a path between commands. A here-document body is data, but the substitutions the shell
runs in a body whose delimiter is unquoted are read as commands, and such a body
is read by the lines bash compares with its delimiter, joined across a trailing
odd run of backslashes. A backtick's text is read after bash's own pass over
Expand Down Expand Up @@ -265,7 +274,10 @@ piped into `xargs kill`, is read as the kill by name it is — through a group
and into one, whose every command is read as handed what is piped into it,
through a shell string that runs the search, and into a shell string `xargs`
runs or a pipe or redirect feeds, whose every command is read as handed its
input — and a `pkill` or
input, or whose positional parameters or own text hold the search's output; out
of an unquoted here-document's substitutions into the command that reads the
document and every command its output is piped on to; and into a substitution in a command that reads a pipe, which runs
with that pipe as its input — and a `pkill` or
`killall` selecting by user, group or terminal, its value written apart or
attached, as selecting every session under the account; `pkill`'s signal name
is read as a signal first, in any case. In a repository with more
Expand All @@ -274,9 +286,9 @@ because the stash stack is shared across worktrees. Where the reading is a
guess, over-blocking is the direction the guard takes.

What an allow still does not see is a hazard that never reaches command position
at all: a word that is wholly a command substitution standing where a flag
would be, which is read as an operand because that is how a commit message or a
branch name is spelled every day; one behind a wrapper flag the per-wrapper
at all: a word that is wholly a command substitution or a variable standing
where a flag would be, which is read as an operand because that is how a commit
message or a branch name is spelled every day; one behind a wrapper flag the per-wrapper
table does not name; a REST
path an entry names by its root segment when the host serves that API under a
prefix; an IFS the shell already holds when the line starts, or gains during the line
Expand All @@ -285,12 +297,12 @@ since every line is read from the default IFS; a pid list a kill reads through a
grep` chain;
a payload inside a non-shell interpreter such as `python -c`, which is
one opaque token and today a silent allow; and any dangerous form no entry
describes. Nor does an allow see through a parameter expansion that carries no
substitution (`$VAR`, `${VAR:-git}`), wherever it stands — as the command's
program name, as a flag, or inside a payload the guard does read — because the
guard sees the variable and not what the shell will expand it to, and warning on
every variable would bury the warnings that matter; so the obvious evasions above
do not include a hazard spelled through a variable. Nor does an allow see what a
describes. Nor does an allow see what a variable carries in from an earlier
command: a pid list (`p=$(pgrep make); kill $p`), a stream path handed to a
shell, shell text run through `eval "$X"` or placed in a string a shell runs,
or `pkill` or `killall` as a variable's value standing as the program with an
operand (`$P make`), because reading each would refuse the ordinary commands a
variable carries a value for; whether to is an open ruling. Nor does an allow see what a
lone substitution prints when it stands as the whole command (`$(cat msg.txt)`,
`$(date)`): such a name can be any program, but with no operand after it no
entry matches, so it allows by the posture above, where an allow means no entry
Expand Down
4 changes: 2 additions & 2 deletions .abcd/development/brief/04-surfaces/19-identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
A project's tagline gets written once and copied four times: the README
strapline, the plugin manifest, the conventions file, a string baked into the
binary's banner. Then one of them is improved. `/abcd:identity` records the
canonical wording in one place, tells the maintainer which surfaces have drifted
canonical wording in one place, tells the product thinker which surfaces have drifted
away from it, and prints the exact diff that would bring each back.

The report and the rendered diff are **strictly read-only**. Initialisation
Expand Down Expand Up @@ -104,7 +104,7 @@ skipped rather than reported, because a file that does not exist carries no
drift.

**Autonomous rewriting is permanently out of scope.** The render proposes; the
maintainer adopts. Changing the positioning deliberately is an edit to the block,
product thinker adopts. Changing the positioning deliberately is an edit to the block,
after which the same proposal flow chases the surfaces.

Initialisation never re-interviews a repo that already has a block — it adopts it. Run
Expand Down
Loading
Loading