feat(specify): record in-context fork resolutions as auditable issue comments

The `spec-skeptic` `scope-fork` juror reads only the seeding issue plus
the spec. On the legitimate `specify` direct-entry path — a fork settled
in a long in-context design discussion — that resolution lives only in
ephemeral chat the juror cannot replay. When the issue body lags the
discussion (still lists the fork open), the juror correctly blocks, and
a design BLOCK escalates without self-correction. The result: auto-sign
was structurally almost unreachable for the in-context entry path.

Close the blind spot by giving the juror an auditable source instead of
weakening the gate. When `specify` enters in-context and a tracker issue
still lists a now-resolved fork as open, the orchestrator posts a
reconciliation comment recording each fork's resolution WITH provenance
(a record of the user's decision, never a fresh orchestrator one) before
writing the spec. The comment is persistent and audit-able — unlike a
carrier digest — so it, not the orchestrator's confidence, is what the
juror checks.

Separation of powers keeps it honest: the orchestrator writes the
comment, the adversarial juror enforces the provenance requirement. A
bare `decision: X` with no provenance does not resolve the fork — the
re-dispatched juror blocks on it. The escalation rule and the
three-field carrier are untouched; only the juror's information changes.

Mechanics:
- specify Step 1.5: reconciliation-comment sub-step, provenance format,
  issue-less fallback (auto-sign -> human sign-off, no weak spec-note).
- spec-skeptic: replace the "quoted in the dispatch" drift; juror reads
  the issue WITH comments via `issue_tracker.show_cmd`; provenance check.
- new profile slot `issue_tracker.show_cmd` (must render comments);
  documented in schema + template.
- issue skill: `tea issues <idx>` is body-only; `--comments` required
  (verified against tea 0.14.1 and Aura #55 — 180 vs 144 lines).
- consistency: design.md out-of-scope, README, pipeline.md, boss skill.
This commit is contained in:
2026-06-12 15:54:47 +02:00
parent a87916e578
commit 268ee705f4
9 changed files with 101 additions and 14 deletions
+6 -1
View File
@@ -158,7 +158,12 @@ not settled), an `INFRA_ERROR`, any objective gate not green, or an
exhausted budget falls back to the human sign-off pause the spec would exhausted budget falls back to the human sign-off pause the spec would
have hit anyway. Re-dispatching all five lenses every round is the have hit anyway. Re-dispatching all five lenses every round is the
backstop that stops an editorial repair from quietly settling a design backstop that stops an editorial repair from quietly settling a design
question. On a clean sign the orchestrator commits the spec question. When a fork was settled in-context but the seeding issue still
lists it open, `specify` records the resolution as a provenance-bearing
reconciliation comment on the issue (Step 1.5), which the `scope-fork`
juror reads via `issue_tracker.show_cmd` — closing the blind spot where
the in-context entry path could otherwise never clear that lens. On a
clean sign the orchestrator commits the spec
(`(boss-signed)` in the subject), sends a mandatory informational notify (`(boss-signed)` in the subject), sends a mandatory informational notify
that names the signed capability and invites a veto, and continues to that names the signed capability and invites a veto, and continues to
`planner` without stopping. A later veto is a forward correction, never `planner` without stopping. A later veto is a forward correction, never
+6
View File
@@ -369,6 +369,12 @@ and defined there. The boss-side contract is just this:
question in the user's place; the full re-panel each round is what question in the user's place; the full re-panel each round is what
stops an editorial repair from laundering one in. The gate owns this stops an editorial repair from laundering one in. The gate owns this
loop (`../specify/SKILL.md` Step 6) — boss only enforces the notify. loop (`../specify/SKILL.md` Step 6) — boss only enforces the notify.
One upstream detail matters here: when a fork was settled in-context
but the seeding issue still lists it open, `specify` Step 1.5 posts a
provenance-bearing reconciliation comment on the issue so the
`scope-fork` juror ratifies the resolution from an auditable source
rather than false-blocking on the stale body. That comment records a
*user* decision; it never lets the orchestrator certify its own.
- **On a clean sign, do not stop.** Commit the spec (the auto-sign - **On a clean sign, do not stop.** Commit the spec (the auto-sign
commit subject carries `(boss-signed)` so the act is auditable in commit subject carries `(boss-signed)` so the act is auditable in
history), fire the auto-sign notify, and continue to `planner` in history), fire the auto-sign notify, and continue to `planner` in
+7 -5
View File
@@ -113,8 +113,10 @@ The relationship:
## What's out of scope for this plugin ## What's out of scope for this plugin
- **Issue-tracker integration**: the plugin can read the - **Issue-tracker integration**: the plugin can read the
profile's `issue_tracker.kind` and `issue_tracker.list_cmd` profile's `issue_tracker.kind`, `issue_tracker.list_cmd`, and
and invoke the configured listing command, but does not `issue_tracker.show_cmd` and invoke the configured listing /
directly call Gitea / GitHub / Linear APIs. The `boss` skill single-issue commands, and `specify` may post a reconciliation
uses the configured shell command for queue reads, so the comment via the tracker's comment command — but the plugin does
plugin remains transport-agnostic. not directly call Gitea / GitHub / Linear APIs. Every read and
write goes through the configured shell command, so the plugin
remains transport-agnostic.
+5 -1
View File
@@ -113,7 +113,11 @@ signs. A `BLOCK` is never signed over: an editorial one (`criterion` /
`ambiguity` / `plan-readiness`) is repaired in a bounded ≤ 2-round loop `ambiguity` / `plan-readiness`) is repaired in a bounded ≤ 2-round loop
that re-runs the objective gates and re-dispatches all five lenses each that re-runs the objective gates and re-dispatches all five lenses each
round; a design one (`scope-fork` / `grounding`), an `INFRA_ERROR`, or round; a design one (`scope-fork` / `grounding`), an `INFRA_ERROR`, or
an exhausted budget falls back to the human sign-off pause. See an exhausted budget falls back to the human sign-off pause. When the
entry is in-context and the seeding issue still lists a now-resolved
fork as open, `specify` posts a provenance-bearing reconciliation
comment on the issue (Step 1.5) so the `scope-fork` juror can ratify the
resolution instead of blocking on the stale body. See
`../specify/SKILL.md` Step 6 and `../boss/SKILL.md` §"Spec auto-sign". `../specify/SKILL.md` Step 6 and `../boss/SKILL.md` §"Spec auto-sign".
### planner ### planner
+1
View File
@@ -173,6 +173,7 @@ authoritative semantics live at the `paths` row above.
| `issue_tracker.close_marker` | string | `"closes #N"` | Marker the orchestrator includes in commit bodies to auto-close issues. | | `issue_tracker.close_marker` | string | `"closes #N"` | Marker the orchestrator includes in commit bodies to auto-close issues. |
| `issue_tracker.url` | string | (empty) | Human-browsable URL of the issue list — surfaced in notifications and cross-references. | | `issue_tracker.url` | string | (empty) | Human-browsable URL of the issue list — surfaced in notifications and cross-references. |
| `issue_tracker.list_cmd` | string | (empty) | Shell command that lists open issues. Used by the `boss` skill to read the forward queue. Examples: `tea issues ls --repo X/Y --state open`, `gh issue list --repo X/Y --state open`. | | `issue_tracker.list_cmd` | string | (empty) | Shell command that lists open issues. Used by the `boss` skill to read the forward queue. Examples: `tea issues ls --repo X/Y --state open`, `gh issue list --repo X/Y --state open`. |
| `issue_tracker.show_cmd` | string | (empty) | Shell command that renders **one** issue **with its comment thread**; the orchestrator and the `spec-skeptic` `scope-fork` juror append the issue index as the final argument (e.g. `tea issues --comments``tea issues --comments 55`). MUST include comments — a `specify` reconciliation comment (see `specify/SKILL.md` Step 1.5) is invisible to the juror otherwise. Examples: `tea issues --comments`, `gh issue view --comments`. If empty, the juror reads only the issue body and a fork resolved in-context but not echoed into the body cannot be ratified — auto-sign falls back to the human sign-off. |
| `protected_branches` | list | `[main]` | Branches that are sacrosanct in the same sense as main. | | `protected_branches` | list | `[main]` | Branches that are sacrosanct in the same sense as main. |
## `notifications` ## `notifications`
+5 -2
View File
@@ -94,7 +94,7 @@ when running outside the repo (the slug lives in
| Operation | Command | | Operation | Command |
|---|---| |---|---|
| List open | `tea issues ls --state open` | | List open | `tea issues ls --state open` |
| View one | `tea issues <idx>` | | View one | `tea issues <idx>` (body only) — add `--comments` to include the discussion thread: `tea issues --comments <idx>` |
| Create | `tea issues create -t "<title>" -d "<body>" -L "<labels>"` | | Create | `tea issues create -t "<title>" -d "<body>" -L "<labels>"` |
| Edit body/title | `tea issues edit <idx> -t "<title>" -d "<body>"` | | Edit body/title | `tea issues edit <idx> -t "<title>" -d "<body>"` |
| Re-label | `tea issues edit <idx> --add-labels a --remove-labels b` | | Re-label | `tea issues edit <idx> --add-labels a --remove-labels b` |
@@ -111,7 +111,10 @@ Notes:
`in-progress`). `in-progress`).
- Before editing, view the issue first (`tea issues <idx>`) — do not - Before editing, view the issue first (`tea issues <idx>`) — do not
edit blind. Status/discussion updates go in a `tea comment`, not by edit blind. Status/discussion updates go in a `tea comment`, not by
overwriting the body. overwriting the body. Note `tea issues <idx>` prints the **body
only**; pass `--comments` to read the existing thread first (the bare
form would otherwise prompt for it interactively, which hangs a
non-interactive agent).
- Routine closing is done by a `closes #N` line in the commit body on - Routine closing is done by a `closes #N` line in the commit body on
push (per `~/dev/CLAUDE.md`); `tea issues close` is for closing push (per `~/dev/CLAUDE.md`); `tea issues close` is for closing
without an accompanying commit. without an accompanying commit.
+48
View File
@@ -121,6 +121,54 @@ the reactive-deference failure the gate exists to prevent.
Genuine doubt about whether the sources resolve a decision *is itself* Genuine doubt about whether the sources resolve a decision *is itself*
the fork signal — it resolves to a bounce, not to a guess. the fork signal — it resolves to a bounce, not to a guess.
**Reconciliation comment (lagging-issue case).** A load-bearing fork can
be resolved by the in-context design discussion yet still be listed
*open* by the tracker issue that seeded the cycle — the issue lags the
conversation. When that holds (entry is in-context **and** a tracker
issue still carries a now-resolved fork as open), record the resolution
as an auditable issue comment **before** writing the spec, via the
project's issue-tracker comment command (`tea comment <idx>` for Gitea;
a heredoc body per the `issue` skill's conventions). One comment, this
exact shape:
```
## Design reconciliation (specify, in-context entry)
Spec: <spec-name>. The fork(s) below are still listed open on this
issue; they were resolved in the in-context design discussion.
- **Fork: <name>** → <decision>.
Provenance: <verbatim user statement, or explicit attribution + date>.
```
The comment must carry **provenance**, and the distinction is
load-bearing: it is the *record of a user decision* (legitimate — the
user settled the fork in discussion, you are minuting it), never a *new
orchestrator decision* dressed as settled (illegitimate). A bare
`decision: B` with no provenance is the latter. If you cannot point to a
real user statement, that is the signal that *you* picked the fork, not
the user — which is a Step-1.5 bounce, not a comment. The comment is
*persistent and auditable* (author + timestamp, in the forward queue) —
which is exactly why it, and not your ephemeral confidence, is what a
downstream `scope-fork` juror reads to confirm the fork is settled (see
Step 6). Recording it keeps the forward queue honest in either run mode;
under auto-sign it is *load-bearing* — without it the `scope-fork` juror
sees only the stale issue body and (correctly) blocks.
Provenance enforcement is not yours to self-certify: you *write* the
comment, the adversarial `scope-fork` juror *checks* it carries
provenance (Step 6). Writing a provenance-less comment does not buy a
pass — the re-dispatched juror blocks on it.
Gitea writes are pre-authorized (per `~/.claude/CLAUDE.md`); post the
comment without a confirmation prompt.
**Issue-less in-context entry.** If the cycle has *no* tracker issue —
the design was settled in pure chat — there is no independent artefact to
comment on, and a provenance note *inside the spec* would be
self-referential (the spec asserting its own sources settle it). Do not
manufacture a weak spec-note path: under auto-sign this case falls back
to the human sign-off (the safe default), because no auditable source
exists for the juror to check against.
### Step 2 — Apply the project's feature-acceptance criterion ### Step 2 — Apply the project's feature-acceptance criterion
The project's `CLAUDE.md` declares the criterion a feature must The project's `CLAUDE.md` declares the criterion a feature must
+21 -5
View File
@@ -72,10 +72,22 @@ In addition, every dispatch:
`paths.code_roots` and the project's examples / fixtures directory — `paths.code_roots` and the project's examples / fixtures directory —
you grep these to confirm or refute ratification. you grep these to confirm or refute ratification.
- For the `scope-fork` lens additionally: the sources the spec was - For the `scope-fork` lens additionally: the sources the spec was
built from, when the carrier names them (an issue body is reachable built from. The seeding issue is reachable via the profile's
via the issue-tracker show command; an in-context design narrative is `issue_tracker.show_cmd` (issue index appended last) — invoke it so
quoted in the dispatch). You judge "resolved vs picked" against what you read the issue **with its comment thread**, not the body alone.
the sources actually say, not against what reads plausibly. You judge "resolved vs picked" against what the sources actually say,
not against what reads plausibly. A fork the issue *body* still lists
open may have been resolved in an in-context discussion the dispatch
cannot replay to you; the legitimate channel for that resolution is a
**reconciliation comment** on the issue (`specify` Step 1.5). A
reconciliation comment counts as the sources resolving the fork **only
if it carries provenance** — a record of the user's decision (a
verbatim statement, or explicit attribution + date). A bare
`decision: X` with no provenance is an orchestrator self-assertion
dressed as settled, not a source: it does **not** resolve the fork, and
a load-bearing decision whose only support is such a comment is a
refutation. If `show_cmd` is unset, you have only the body — a fork the
body lists open is unresolved to you.
You do NOT read files under `paths.plan_dir` (the plan does not yet You do NOT read files under `paths.plan_dir` (the plan does not yet
exist). You do NOT read other specs unless the spec under review exist). You do NOT read other specs unless the spec under review
@@ -117,7 +129,11 @@ YOU DO NOT RUN THE FULL TEST SUITE. (TEST LIST, TYPE-CHECK, PER-BLOCK PARSER RUN
`#[ignore]`/`xfail`/disabled does not pin; code presence does not `#[ignore]`/`xfail`/disabled does not pin; code presence does not
pin; your own recall does not pin). For `scope-fork`, this means pin; your own recall does not pin). For `scope-fork`, this means
listing the load-bearing decisions and checking each against the listing the load-bearing decisions and checking each against the
sources. For the others, read for the specific failure. sources read via `show_cmd` (issue **with comments**); a fork the
issue body lists open is resolved only by a reconciliation comment
that carries provenance (see the standing reading list) — a
provenance-less one does not count. For the others, read for the
specific failure.
4. If you find a genuine defect along your lens → `BLOCK`, and state 4. If you find a genuine defect along your lens → `BLOCK`, and state
it concretely (section ref, the offending text, why it is a defect). it concretely (section ref, the offending text, why it is a defect).
5. If, after an honest search, you cannot find one → `SOUND`. Saying 5. If, after an honest search, you cannot find one → `SOUND`. Saying
+2
View File
@@ -62,6 +62,8 @@ git:
close_marker: "closes #N" close_marker: "closes #N"
# url: "" # human-browsable issue list URL # url: "" # human-browsable issue list URL
# list_cmd: "" # e.g. "tea issues ls --repo X/Y --state open" # list_cmd: "" # e.g. "tea issues ls --repo X/Y --state open"
# show_cmd: "" # renders one issue WITH comments; index appended last.
# e.g. "tea issues --comments" -> "tea issues --comments 55"
notifications: notifications:
# command: "" # e.g. "~/.claude/notify.sh"; boss falls back to chat if empty # command: "" # e.g. "~/.claude/notify.sh"; boss falls back to chat if empty