Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8c09f03
feat(capture): count skipped records on the board and name the refusi…
REPPL Sep 25, 2026
98d8d70
test(capture): pin that a derived slug always satisfies its own valid…
REPPL Sep 25, 2026
6073ca1
chore: resolve iss-2609120452071388 — skipped records counted and lay…
REPPL Sep 25, 2026
166d35a
fix(capture,intent): encode hidden runes at the record-write boundary
REPPL Sep 25, 2026
089c861
chore: resolve iss-2608301206073609 — hidden runes encoded on record …
REPPL Sep 25, 2026
7239764
chore: resolve iss-2608301244450106 — derived wontfix grounds validated
REPPL Sep 25, 2026
dd61abc
fix(capture): resolve every ledger write and the lock inside an os.Root
REPPL Sep 25, 2026
20e3f39
fix(capture): contain the reading and disposition record writes too
REPPL Sep 25, 2026
997111d
chore: resolve iss-2609012037143368 — ledger writes contained in os.Root
REPPL Sep 25, 2026
93bea0d
fix(capture): delete the ledger-root marker walk; discovery is git's …
REPPL Sep 25, 2026
7e562bd
chore: resolve iss-2609090947359464 — ledger-root walk deleted
REPPL Sep 25, 2026
1a3f386
chore: capture the promote remedy's nil-grounds panic
REPPL Sep 25, 2026
14ab2e0
fix(capture): promote's orphan remedy fits why the stamp failed
REPPL Sep 25, 2026
d733216
chore: resolve iss-258 — lost promote race names the duplicate draft
REPPL Sep 25, 2026
72f28fe
chore: resolve iss-2609020154474224 — remedy delimiter cannot be closed
REPPL Sep 25, 2026
69ce990
chore: resolve iss-2609251208394294 — no panic on a grounds-less orphan
REPPL Sep 25, 2026
243414a
fix(capture): judge a quoted impact on its raw scalar, as the gate does
REPPL Sep 25, 2026
ad6f81b
chore: resolve iss-2608261133218490 — one impact verdict per spelling
REPPL Sep 25, 2026
df17a11
feat(capture): say when a record is uncommitted, at the write and on …
REPPL Sep 25, 2026
e336956
chore: resolve iss-2609100508570527 — uncommitted records named
REPPL Sep 25, 2026
d09f45e
feat(capture): a defer verb writes the release cut's waiver pair
REPPL Sep 25, 2026
7d9d14f
chore: resolve iss-2609181223260994 — capture defer writes the waiver
REPPL Sep 25, 2026
8004221
test(capture): a deferral the verb writes is one the release cut honours
REPPL Sep 25, 2026
a5a2b7f
feat(capture): every ledger verb names the checkout and branch it add…
REPPL Sep 25, 2026
bdbe55f
chore: resolve iss-2609202053570475 — ledger verbs name their checkout
REPPL Sep 25, 2026
a60cc4c
feat(capture): say when a capture names no location in this checkout
REPPL Sep 25, 2026
ef461db
chore: resolve iss-2609231156260287 — location-less capture says so
REPPL Sep 25, 2026
e2abfdf
fix(capture): an unreadable record bucket is reported, not read as empty
REPPL Sep 25, 2026
7b85e8a
chore: resolve iss-260 — unreadable record bucket reported as itself
REPPL Sep 25, 2026
5d085cb
fix(capture): a closed-set refusal names the flag and its accepted set
REPPL Sep 25, 2026
8813ccf
chore: resolve iss-2608290810037524 — enum refusal names flag and set
REPPL Sep 25, 2026
48e9b4f
fix(capture): the restamp gate parses the origin rather than finding it
REPPL Sep 25, 2026
116bd7c
chore: resolve iss-2608300941548519 — restamp gate parses the origin
REPPL Sep 25, 2026
39998cc
chore: capture four follow-ups from the capture review
REPPL Sep 25, 2026
67257f1
Merge remote-tracking branch 'origin/main' into fix/capture-cluster
REPPL Sep 26, 2026
68deae0
feat(surface): capture defer carries its sentence from the manifest
REPPL Sep 26, 2026
9b77433
chore: recalibrate the reading windows at the merged tip
REPPL Sep 26, 2026
523d784
Merge branch 'main' into fix/capture-cluster
REPPL Sep 26, 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
22 changes: 11 additions & 11 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": 1040000,
"measured_tokens_est": 1021262,
"measured_bytes": 3931862,
"measured_at": "1dff258e9ea7ae29841faaefc47966913fbbd954"
"tokens_est": 1060000,
"measured_tokens_est": 1040187,
"measured_bytes": 4004721,
"measured_at": "68deae03f210499a62d356dc67101b268c15caed"
}
},
"entailment": {
Expand Down Expand Up @@ -133,9 +133,9 @@
],
"window": {
"tokens_est": 350000,
"measured_tokens_est": 343062,
"measured_bytes": 1320792,
"measured_at": "1dff258e9ea7ae29841faaefc47966913fbbd954"
"measured_tokens_est": 344124,
"measured_bytes": 1324879,
"measured_at": "68deae03f210499a62d356dc67101b268c15caed"
}
},
"comparative": {
Expand Down Expand Up @@ -216,10 +216,10 @@
"test"
],
"window": {
"tokens_est": 1050000,
"measured_tokens_est": 1030298,
"measured_bytes": 3966650,
"measured_at": "1dff258e9ea7ae29841faaefc47966913fbbd954"
"tokens_est": 1060000,
"measured_tokens_est": 1049223,
"measured_bytes": 4039509,
"measured_at": "68deae03f210499a62d356dc67101b268c15caed"
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion .abcd/development/brief/04-surfaces/04-launch.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,8 @@ than a design target.
that is absent, misspelled, or outside the ledger's enum. An unreadable grade
has not been judged, and "not judged" must not read as "not serious".
- **The waiver** is the frontmatter pair `deferred_after` plus
`deferral_reason`, both schema-accepted keys. `deferred_after` names the cut's
`deferral_reason`, both schema-accepted keys, written by the ledger's deferral
verb ([`06-capture.md`](06-capture.md)). `deferred_after` names the cut's
**anchor** tag, not the version being derived, which is what makes a waiver
single-use: the anchor moves at the next release and every waiver written
against the old one lapses, so a deferred finding is re-asked rather than
Expand Down
69 changes: 61 additions & 8 deletions .abcd/development/brief/04-surfaces/06-capture.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ binary.

| Verb | Bucket | Status |
|---|---|---|
| `defer` | — | shipped |
| `disposition` | — | shipped |
| `link` | — | shipped |
| `list` | — | shipped |
Expand All @@ -40,10 +41,19 @@ binary.
wontfix counts, the most recent open issues, and a three-way routing hint that
closes on the next move (capture it, shape it as an intent, or, for a big
unproven idea, run the optional `abcd ideate` admission gauntlet). It creates,
moves and mutates nothing.
moves and mutates nothing. A file that claims to be a record and that the reader
refuses is counted in none of the three totals, so the board counts it beside
them and names, for each one, the reader layer that refused it: the filename,
the guarded read, the frontmatter parse, the schema or the folder and filename
invariants. The layer is what tells a reader whether the record or the reader is
the side to fix (iss-2609120452071388). The board also counts the records git
reports as untracked or changed and marks each such row: folder membership is a
status only once the file is committed, so an uncommitted record is in no state to
any other branch, worktree or gate (iss-2609100508570527).

**`/abcd:capture "<text>"`** is the fast path: it appends a structured entry
with an auto-assigned `iss-N` and writes it to `open/`. Provenance and taxonomy
with an auto-assigned `iss-N` and writes it to `open/`, and says that the record
is not committed yet whenever git reports it so, which for a new record is always. Provenance and taxonomy
are caller-supplied flags. Severity, category, source and the found-during
context each carry a default, so the fast path stays fast; the location, slug
and dependency flags have none. The `origin` field is derived from the verb that
Expand All @@ -63,6 +73,10 @@ hold is the mechanical sign of a finding filed in the wrong place
(iss-2609120511058115). A conceptual location, meaning anything that is not a
lone path token, and an absent value are written as given. The check is made
at capture only, so a record keeps the path it named when the tree later moves.
An absent location is written as given and is not refused, but the verb says the
record names no location in this checkout, so nothing ties it to the repository
it is filed into: that is a nudge, not a gate, and it is the shape every
misfiled record behind iss-2609120511058115 had (iss-2609231156260287).

One flag belongs to one category: the lapse-instant flag carries the RFC 3339
instant a recorded discipline gave way, for the `lapse` category, and it has no
Expand Down Expand Up @@ -159,6 +173,18 @@ it: an intent, a spec, or a commit sha. A fourth, the shipped-in release, is mig
use only: it names the release that already carried the work, so the record
stays out of the current cut.

**Deferring** writes the release cut's waiver onto an open record
(iss-2609181223260994): `deferred_after` naming the anchor tag, `deferral_reason`
stating why, and a dated `## Deferral` section appended to the body, which is the
part of the record a reader sees. The record stays in `open/`, because a deferral
carries a finding past one cut and neither fixes nor declines it. Everything the
cut's reader would not honour is refused at the write, with nothing written: a
tag that is not the checkout's newest release tag, an empty reason, a record that
is not open, and a record whose grade is neither `major` nor `critical`, which
the guard never blocks on. The grade is judged before the tag. A record deferred
past an earlier anchor is deferred again: the pair is replaced and a new section
appended, so each cycle's deferral stays readable in the record.

**Marking an issue wontfix** records an explicit non-action decision and moves
the issue to `wontfix/`. Grounds are optional here and override the recorded
text only: the token stays `declined`, because a wontfix **is** that non-action.
Expand Down Expand Up @@ -195,6 +221,14 @@ Two consequences follow, and both are stated to the caller rather than guessed.
deliberate fixture or the residue of the defect above, and only the caller can
tell those apart. Moving it would destroy the evidence of which it was.

Every verb also says which checkout's ledger it addressed, and the record
dispatcher says it for an issue id (iss-2609202053570475): one stderr line naming
the checkout and its branch in the plain render, and a `ledger` member with
`checkout` and `branch` in the machine-readable one. The checkout is written home-relative where
it can be. A record filed in another worktree is invisible here, and a refusal
that says "not found" without naming where it looked sends the reader to the
wrong conclusion.

## 3. Ledger structure

Frontmatter, per the issue-ledger schema in `internal/core/issueschema`, which
Expand Down Expand Up @@ -233,10 +267,10 @@ resolved_by: # optional structured pointer to what resolved it
---
```

`deferred_after` and `deferral_reason` are the release cut's waiver pair, and no
capture verb writes them: they are added by hand when a `major` or `critical`
finding is to be carried past a cut open, and the changelog guard reads them.
The waiver is granted for one cycle and lapses when the next release re-anchors.
`deferred_after` and `deferral_reason` are the release cut's waiver pair. The
deferral verb writes them when a `major` or `critical` finding is to be carried
past a cut open, and the changelog guard reads them. The waiver is granted for
one cycle and lapses when the next release re-anchors.
[`04-launch.md`](04-launch.md) owns the rule they answer to.

`lapsed_at` is transcribed from what the source states, never derived from the
Expand All @@ -259,6 +293,14 @@ above is correct under both. Where a merge produces two reachable candidates,
prefer the commit that carries the change over the merge commit, whose diff is
the whole pull request rather than the fix.

Free text is written losslessly but never invisibly. A bidi override, a
zero-width rune, a C1 control, DEL or any other character a terminal would hide
is percent-encoded as its UTF-8 bytes wherever a verb writes caller text into a
record: the capture body and its location and context fields, a resolution or
wontfix note, and every grounds entry, on this surface and in the intent drafts
promotion and `abcd intent` mint. A line break and a tab in the body are left as
they are, because they are its structure (iss-2608301206073609).

The record body is free-form. One part of it is not, and it is where grounds
land.

Expand Down Expand Up @@ -310,7 +352,9 @@ for ad-hoc scribbles.
in its `related_issues`, appends the intent to the issue's `related_intents`,
and leaves the issue in its folder; an issue already
promoted is refused with the existing intent id, and a post-mint stamp failure
names the orphan draft and the repair flag.
names the orphan draft and the repair flag — or, when a concurrent promotion
of the same issue won the race, names the winner and says to delete the
duplicate draft (iss-258).
- **Given** a reading item with no disposition, **when** the user records one,
**then** a disposition record is written under
`.abcd/work/issues/dispositions/`; a second answer to the same item is refused
Expand Down Expand Up @@ -371,7 +415,7 @@ _Generated from the command tree; a drift test fails `go test` when this appendi

### `abcd capture`

Sub-verbs: `abcd capture disposition`, `abcd capture link`, `abcd capture list`, `abcd capture mentions`, `abcd capture migrate`, `abcd capture promote`, `abcd capture resolve`, `abcd capture wontfix`.
Sub-verbs: `abcd capture defer`, `abcd capture disposition`, `abcd capture link`, `abcd capture list`, `abcd capture mentions`, `abcd capture migrate`, `abcd capture promote`, `abcd capture resolve`, `abcd capture wontfix`.

| Flag | Type |
|---|---|
Expand All @@ -385,6 +429,15 @@ Sub-verbs: `abcd capture disposition`, `abcd capture link`, `abcd capture list`,
| `--slug` | string |
| `--source` | string |

### `abcd capture defer`

Sub-verbs: none.

| Flag | Type |
|---|---|
| `--after` | string |
| `--reason` | string |

### `abcd capture disposition`

Sub-verbs: none.
Expand Down
5 changes: 4 additions & 1 deletion .abcd/development/brief/04-surfaces/08-abcd.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ form.
and the concrete next move for its lifecycle state. Bare answers *what can I
do*; the id form answers *what is this, and what is my next move* (spc-26,
itd-121). A positional on the namespace root is not a `show` sub-verb, so the
form stays inside the naming discipline.
form stays inside the naming discipline. For an issue id it also names the
checkout and branch whose ledger it read, as every ledger verb does: a stderr
line in the plain render and a `ledger` member in the machine-readable one
(iss-2609202053570475).

Any other positional is refused: the CLI exits **2** with `abcd: unknown command
…` on stderr, which is the framework's usage-error convention. `abcd status` is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ the timestamp inside a record id, so the gate's verdict is not decided by a
field the record it is judging can edit.

The waiver is the `deferred_after` / `deferral_reason` frontmatter pair on the
record. `deferred_after` names the anchor tag the deferral was granted against,
record, written by `abcd capture defer`. `deferred_after` names the anchor tag the deferral was granted against,
which makes it single-use: when the next release re-anchors, the waiver lapses
and the finding is re-asked. A waiver that names the wrong anchor, or states no
reason, leaves the record blocking and says why, in the same fail-safe direction
Expand Down
21 changes: 21 additions & 0 deletions .abcd/development/release/surface.json
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,27 @@
}
]
},
{
"path": "abcd capture defer",
"hidden": false,
"sentence": "Carry an open major or critical issue past one release cut: Writes deferred_after and deferral_reason; refuses a minor or nitpick issue, or an empty reason.",
"flags": [
{
"name": "after",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
},
{
"name": "reason",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
}
]
},
{
"path": "abcd capture disposition",
"hidden": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ remedy: re-run without `--production-mode`. The refusal is about the RESTAMP and
never about the transition — an unstamped record stays resolvable, because
forward-only population must not strand a record nobody can close.

The gate reads the `origin` through the vocabulary's parser, not as a
presence test (iss-2608300941548519): a record carrying an `origin` outside
the closed set refuses the restamp on the same terms, because writing a mode
beside it produces a pair no command writes. The converse is a deliberate
write inside the lint's declared residual: a record carrying a valid `origin`
and no `production_mode`, itself a `record_provenance` blocker, is repaired
into a clean pair by a restamp, since that pair is exactly what a command
writes.

**The attribution seam is extended, not duplicated.** `identity.Pin` gains an
optional `ProductionMode` member; `LoadPin` validates it against the vocabulary
and returns an error on an unknown value, exactly as it already errors on a
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
schema_version: 1
id: "iss-2609251151293535"
slug: "the-source-vocabulary-has-no-member-for-a-finding-an"
severity: "minor"
category: "process"
source: "agent-finding"
found_during: "autonomous run A resumed 2026-09-25"
origin: researcher-authored
production_mode: hand-written
found_at: "internal/core/issueschema/issueschema.go"
---

The source vocabulary has no member for a finding an autonomous bug-hunt loop files, and a downstream loop wrote source autonomous-hunt, which the reader refuses and skips. Decomposed out of iss-2609120452071388 when its code halves were fixed, because this half is a closed-vocabulary ruling for the product thinker, not an implementer's fix: either such a loop uses an existing member (agent-finding is the nearest) or autonomous-hunt joins issueschema.Sources, with record-lint and the help text following from the one list. Until it is ruled, a record carrying it is skipped with the schema layer named on the board.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
schema_version: 1
id: "iss-2609251235119402"
slug: "abcd-intent-audit-reads-the-issue-ledger-its-issue-drift"
severity: "nitpick"
category: "ux"
source: "agent-finding"
found_during: "autonomous run A resumed 2026-09-25"
origin: researcher-authored
production_mode: hand-written
found_at: "internal/surface/cli"
---

abcd intent audit reads the issue ledger (its issue-drift form checks the promote join) without naming which checkout's ledger and branch it read. Decomposed out of iss-2609202053570475, which made every capture verb and the record dispatcher name the ledger they addressed: the audit's output is the intent-auditor verdict envelope, owned by the intent surface, so adding the member there is that surface's change. The helpers to reuse are ledgerIdentityOf and renderLedger in internal/surface/cli.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
schema_version: 1
id: "iss-2609251823551349"
slug: "capture-disposition-writes-disposition-grounds-and-exit"
severity: "minor"
category: "security"
source: "user-observation"
found_during: "autonomous run A resumed 2026-09-25"
origin: researcher-authored
production_mode: hand-written
---

capture disposition writes disposition_grounds and exit_condition redacted but never through termsafe.EncodeHiddenRunes (internal/core/capture/reading.go:471-479), so a bidi or zero-width rune in --exit-condition lands in the committed record verbatim, the class iss-2608301206073609 closed for other free-text writes (review-capture 2).
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
schema_version: 1
id: "iss-2609251823555125"
slug: "capture-defer-past-the-same-anchor-appends-a-second-deferral"
severity: "minor"
category: "bug"
source: "user-observation"
found_during: "autonomous run A resumed 2026-09-25"
origin: researcher-authored
production_mode: hand-written
---

capture defer past the SAME anchor appends a second ## Deferral section (internal/core/capture/deferral.go:114), where the doc says one per cycle (review-capture 3).
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
schema_version: 1
id: "iss-2609251823559111"
slug: "the-capture-ledger-s-os-root-escape-is-classified-by"
severity: "minor"
category: "tech-debt"
source: "user-observation"
found_during: "autonomous run A resumed 2026-09-25"
origin: researcher-authored
production_mode: hand-written
---

The capture ledger's os.Root escape is classified by matching the string 'path escapes from parent' (internal/core/capture/ledgerroot.go:59-64), and nothing pins the match: ledgerroot_test.go:62 and :97 assert only err != nil, so a Go release that rewords the message would silently degrade ErrPathUnsafe to a generic error (review-capture 1). Assert errors.Is(err, ErrPathUnsafe) in both race tests.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
schema_version: 1
id: "iss-2609251823560369"
slug: "the-capture-verbs-json-and-stderr-print-the-checkout-path"
severity: "minor"
category: "security"
source: "user-observation"
found_during: "autonomous run A resumed 2026-09-25"
origin: researcher-authored
production_mode: hand-written
---

The capture verbs' --json and stderr print the checkout path through RedactHome only, so a checkout outside HOME is printed in full (internal/surface/cli/cli.go renderLedger), an absolute local path in output that may be pasted elsewhere; renderLedger also splices the ledger member by byte surgery on the trailing brace (review-capture 4).
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ category: "ux"
source: "impl-review"
found_during: "spc-24 build, ruthless-reviewer note"
found_at: "internal/core/capture/promote.go"
resolution: "The already-promoted refusal wraps ErrAlreadyPromoted, and a promotion whose stamp is refused that way (it lost the race to a concurrent promotion) is told which intent won and to delete its duplicate draft, instead of the link remedy that would be refused."
impact: fix
resolved_by:
commit: "14ab2e07"
---

capture promote's post-mint stamp-failure remedy is attached unconditionally, so a promote that loses a race against a concurrent promote of the same issue emits advice that will refuse: B mints itd-Y, fails the under-lock re-check because A stamped itd-X, and B's error still says 'complete the link with capture promote iss-N --intent itd-Y' — running that hits the already-promoted refusal, and the duplicate draft itd-Y must be deleted by hand. The wrapped error does carry the true itd-X fact. Accepted residue class in spc-24 (no cross-store lock); a smarter remedy would special-case the already-promoted stamp error and say 'delete the duplicate draft' instead.
capture promote's post-mint stamp-failure remedy is attached unconditionally, so a promote that loses a race against a concurrent promote of the same issue emits advice that will refuse: B mints itd-Y, fails the under-lock re-check because A stamped itd-X, and B's error still says 'complete the link with capture promote iss-N --intent itd-Y' — running that hits the already-promoted refusal, and the duplicate draft itd-Y must be deleted by hand. The wrapped error does carry the true itd-X fact. Accepted residue class in spc-24 (no cross-store lock); a smarter remedy would special-case the already-promoted stamp error and say 'delete the duplicate draft' instead.

## Grounds

- pursued: the losing side of a promote race gets advice that works; a lost-race report still naming the --intent link would show it wrong
Loading
Loading