diff --git a/README.md b/README.md index d406c5f..f0852dd 100644 --- a/README.md +++ b/README.md @@ -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 have hit anyway. Re-dispatching all five lenses every round is the 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 that names the signed capability and invites a veto, and continues to `planner` without stopping. A later veto is a forward correction, never diff --git a/boss/SKILL.md b/boss/SKILL.md index c3eb96d..e2ebeef 100644 --- a/boss/SKILL.md +++ b/boss/SKILL.md @@ -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 stops an editorial repair from laundering one in. The gate owns this 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 commit subject carries `(boss-signed)` so the act is auditable in history), fire the auto-sign notify, and continue to `planner` in diff --git a/docs/design.md b/docs/design.md index 0744398..d6cc98a 100644 --- a/docs/design.md +++ b/docs/design.md @@ -113,8 +113,10 @@ The relationship: ## What's out of scope for this plugin - **Issue-tracker integration**: the plugin can read the - profile's `issue_tracker.kind` and `issue_tracker.list_cmd` - and invoke the configured listing command, but does not - directly call Gitea / GitHub / Linear APIs. The `boss` skill - uses the configured shell command for queue reads, so the - plugin remains transport-agnostic. + profile's `issue_tracker.kind`, `issue_tracker.list_cmd`, and + `issue_tracker.show_cmd` and invoke the configured listing / + single-issue commands, and `specify` may post a reconciliation + comment via the tracker's comment command — but the plugin does + not directly call Gitea / GitHub / Linear APIs. Every read and + write goes through the configured shell command, so the plugin + remains transport-agnostic. diff --git a/docs/pipeline.md b/docs/pipeline.md index 7ad0ac2..2fe151d 100644 --- a/docs/pipeline.md +++ b/docs/pipeline.md @@ -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 that re-runs the objective gates and re-dispatches all five lenses each 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". ### planner diff --git a/docs/profile-schema.md b/docs/profile-schema.md index d15479c..eb00919 100644 --- a/docs/profile-schema.md +++ b/docs/profile-schema.md @@ -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.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.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. | ## `notifications` diff --git a/issue/SKILL.md b/issue/SKILL.md index e88a954..c212fa1 100644 --- a/issue/SKILL.md +++ b/issue/SKILL.md @@ -94,7 +94,7 @@ when running outside the repo (the slug lives in | Operation | Command | |---|---| | List open | `tea issues ls --state open` | -| View one | `tea issues ` | +| View one | `tea issues ` (body only) — add `--comments` to include the discussion thread: `tea issues --comments ` | | Create | `tea issues create -t "" -d "<body>" -L "<labels>"` | | Edit body/title | `tea issues edit <idx> -t "<title>" -d "<body>"` | | Re-label | `tea issues edit <idx> --add-labels a --remove-labels b` | @@ -111,7 +111,10 @@ Notes: `in-progress`). - Before editing, view the issue first (`tea issues <idx>`) — do not 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 push (per `~/dev/CLAUDE.md`); `tea issues close` is for closing without an accompanying commit. diff --git a/specify/SKILL.md b/specify/SKILL.md index d78384f..cdb9c19 100644 --- a/specify/SKILL.md +++ b/specify/SKILL.md @@ -121,6 +121,54 @@ the reactive-deference failure the gate exists to prevent. Genuine doubt about whether the sources resolve a decision *is itself* 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 The project's `CLAUDE.md` declares the criterion a feature must diff --git a/specify/agents/spec-skeptic.md b/specify/agents/spec-skeptic.md index 1466656..2999687 100644 --- a/specify/agents/spec-skeptic.md +++ b/specify/agents/spec-skeptic.md @@ -72,10 +72,22 @@ In addition, every dispatch: `paths.code_roots` and the project's examples / fixtures directory — you grep these to confirm or refute ratification. - For the `scope-fork` lens additionally: the sources the spec was - built from, when the carrier names them (an issue body is reachable - via the issue-tracker show command; an in-context design narrative is - quoted in the dispatch). You judge "resolved vs picked" against what - the sources actually say, not against what reads plausibly. + built from. The seeding issue is reachable via the profile's + `issue_tracker.show_cmd` (issue index appended last) — invoke it so + you read the issue **with its comment thread**, not the body alone. + 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 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 pin; your own recall does not pin). For `scope-fork`, this means 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 it concretely (section ref, the offending text, why it is a defect). 5. If, after an honest search, you cannot find one → `SOUND`. Saying diff --git a/templates/project-profile.yml b/templates/project-profile.yml index cf3d4d0..908d07f 100644 --- a/templates/project-profile.yml +++ b/templates/project-profile.yml @@ -62,6 +62,8 @@ git: close_marker: "closes #N" # url: "" # human-browsable issue list URL # 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: # command: "" # e.g. "~/.claude/notify.sh"; boss falls back to chat if empty