docs: generalize the maintenance-routine pattern — ADR 0010, eight phase issues, slice pruned - #2686
Conversation
Generalize the maintenance-routine pattern reported by @bcherny (2026-08-13) into agnostic capability: detectors plus catalog rows plus the substrate both need. Eight research lanes and three independent verification passes reshaped the target three times, and the Brief encodes the inverted shape rather than the original framing. The apply machinery already exists and is reachable by file format alone (validated against the fix action's locator, not assumed), so the deliverable is detectors, not detect-and-fix capabilities. Merge rate cannot justify the work: three independent lines - peer-reviewed observational, large-N regression, and a randomized trial - find artifact quality weakly-to-not coupled to acceptance. The verification contract is defended on defect escape and reviewer burden instead, and the source's 388/180 figure is recorded as a single self-report with zero independent corroboration rather than as evidence of efficacy. Three lanes converged from different literatures on class-level over instance-level trust, so the Brief carries that definition and marks which of its five requirements are portable to an individual account. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XChUn86MQUxWpb4WdoN1R3
Q12 needs one empirical run and cannot be resolved from documentation: two official pages contradict each other on whether repo-declared marketplace plugins install in a cloud session, and workspace trust plus private-marketplace auth are undocumented either way. The probe cannot run unattended - browser selection, GitHub account mutation, and metered subscription usage each require the human - so it is filed as a needs-human spike rather than carried as an open assumption. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XChUn86MQUxWpb4WdoN1R3
Fills PLAN.md's empty `## Plan` with an eight-phase sequence and records the Tier B design early-exit that gates it. The spine is ordered by two constraints that are easy to miss. Fanout is findings producer #1, so the first detector of any kind makes producer #2 and the single-file consumer becomes a silent-shadowing bug -- coexistence is strictly phase 1, not merely early. And the owner doc precedes the *second* adopter, so the Pattern-C proof slice runs before the detector contract is written rather than after it. Draft only: the fresh-context stress-test has not been applied yet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two CRITICALs both landed on phase 1 and both held on re-verification. The staleness bound could not exist on the path the plan actually runs: fix-pass-mode.md:76 writes the fix-pass-record only under --yes in a non-interactive session, and the Brief's solo shape is interactive. The bound was a no-op there, so the merge set would have grown without limit and re-injected findings that :95's required post-fix re-review had already resolved -- regressing a documented loop while claiming to close a correctness gate. The record becomes unconditional, a consumption ledger, and the set is bounded by source-findings: instead. The dedup key was not mechanically available either. Stage 3 is a Sonnet semantic merge the fix action does not have, and its own rule is to minimise false merges because one silently drops a real issue. Cross-producer dedup is now presence-only, with the narrower key argued rather than assumed. Also: PLUGIN-PHILOSOPHY.md:471 sets a deadline rather than licensing a late owner doc, and the pilot is itself the second adopter -- hence a stub-then- harden split. AGENTS.md no longer carries the exec-bit guidance the draft cited. routines.md already carries two of the rows the catalog phase would have added. Two scope reductions the draft had absorbed are now stated and gated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A second fresh-context pass reviewed the revision on the axes the adversarial one did not cover. Its Critical was executed rather than argued: check-contract-slice-prune.sh --check-diff origin/main exits 1 on this branch today, naming both files this commit touches. docs/topics/<slug>/ is contract tier -- committed on a task branch only, pruned before merge -- and the grandfather baseline is read from the base revision, so a new slice cannot exempt itself. That gates the first merge, not a tail item, and it collides with per-phase PR boundaries because the tag advancement puts the slice in every diff. Phase 10 graduates the durable outcomes and prunes; the branch-shape choice it forces is an open question for the human. Five sanity checks could not pass as written and are repaired: a bare check-changelog-parity.sh exits 2 on usage; `grep -c ... returns 0` inverts its own exit code, so the success case failed and a mis-staged file passed; a forbidden-metric grep passed vacuously on zero matches, which is today's state; two checks named a state with no command. Acceptance criterion 4 says "anywhere", so its sweep now runs over the whole change set rather than two named files -- three phases shipping or governing a severity surface were uncovered. The routine-delivery open question is bound to the liveness-assertion Core contract's two limbs instead of being deferred by scope, which was not one of the permitted answers. Recorded that the ADR-0004 evidence sits in a gitignored tree two delegated phases cannot read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…romise Shape (a) deferred the prune gate rather than eliminating it: phase 8 creates a second contract slice that matches no grandfather line and is red-lined identically. That slice owes its own graduate-and-prune before its own merge -- the convention's ordinary per-slice lifecycle, not a special case -- and its PLAN must carry a terminal phase of the same shape, now asserted by a check. The gitignored-evidence finding was described in phase 10 but only promised in the phases that trip it. Phases 3, 5, and 7 now carry the obligation where the work happens: inline the path:line evidence into the issue and PR body, because two of those phases route to sub-agents that cannot open .work/. Phase 10 also names what carries the plan once the slice is deleted -- the graduated ADR, the per-phase issues, and this PR body -- so nothing load-bearing is left pointing at a path that does not survive the merge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Plan approved on branch shape (a): graduate early, prune this slice at merge, run the phases as independent PRs. The ADR is written now rather than during phase 1, because it is what carries the decision once the contract slice is deleted -- phase 1 implements it rather than deciding it. Three decisions, each with a rejected alternative that had a real case. The consumer merges the conforming set instead of picking the newest, and unions the coverage fields rather than reporting one producer's. Cross-producer dedup is presence-only: the existing plus-or-minus-three-line key sits behind a Sonnet semantic stage the fix action does not have, and adopting the bucket without the semantics would invert the pipeline's own minimise-false-merge rule and silently discard a remediation. And the applied-plan record becomes a consumption ledger written on every apply path, because it is currently written only under --yes in a non-interactive session -- so any bound anchored on it is a no-op on the path most runs take. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
docs/topics/<slug>/ is contract tier -- committed on a task branch only, pruned before merge. The durable outcomes have graduated: ADR 0010 carries the coexistence decision and its rejected alternatives, and issues #2678-#2685 carry each phase's work items, sanity checks, and inlined path:line incumbent evidence. The plan itself is in this PR's body. Nothing load-bearing is left pointing at a path that does not survive the merge. The evidence was deliberately inlined into the issues rather than cited into .work/, which is gitignored and unreadable to a delegated worker, a fresh session, or a second machine. Recover the pruned documents from the commit before this one: git show 5341117:docs/topics/boris-routines-adoption/PLAN.md git show 5341117:docs/topics/boris-routines-adoption/design/design-resolution.md Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 58s —— View job Code review —
|
|
Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5341117ca0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
ADR 0010's Consequences omitted what Decision 3 entails for a findings file whose rows were partly surfaced or narrowed at the interactive gate: the whole file is retired, and those rows do not survive inside it. The implementation states this and its recovery path; the record that decided it did not, so the decision read as if the case had not been considered. State it as an accepted consequence derived from Decision 3, and name the recovery path the implementation actually provides -- re-running the row's own producer, whose output arrives as a new findings file in the next merge set. Recoverability is phrased as an obligation this decision places on the record format rather than a property the record already had, because the attribution it depends on is a requirement the record body must meet. Decisions 1-3 are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Warning Automated review did not complete — this is an infrastructure failure, not a review verdict. Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."
Re-run the job, or workflow_dispatch this workflow with the PR number, to retry the review. A new push re-triggers this lane only if the caller's |
Correction — the recovery pointer in this PR's body does not survive the mergeRaised by an independent review of the ADR edit, verified after merge. Recording it here rather than The body says (still, unedited): git show 5341117c:docs/topics/boris-routines-adoption/PLAN.md
git show 5341117c:docs/topics/boris-routines-adoption/design/design-resolution.mdWhy that no longer works. This repository squash-merges and deletes the head branch on merge. What does work today, because GitHub still retains the unreachable commit object: gh api "repos/melodic-software/claude-code-plugins/contents/docs/topics/boris-routines-adoption/PLAN.md?ref=5341117c" \
--jq '.content' | base64 -d
gh api "repos/melodic-software/claude-code-plugins/contents/docs/topics/boris-routines-adoption/design/design-resolution.md?ref=5341117c" \
--jq '.content' | base64 -dVerified after the merge: the first returns a 67,690-byte blob. Treat this as convenience, not a The durable record was never those two files, and that was the design. The whole point of
So the correct reading of the prune is that the PLAN was a working artifact whose durable content had Convention follow-up. |
…runed Brief (#2719) ## Summary `map-corpus` shipped with references to the planning document that produced it — the `Q19` label in `SKILL.md`, `discovery/link-map-format.md`, `discovery/check_linkmap.py` and the eval set, plus "Brief criterion 2", "Brief criterion 6", "Brief acceptance criterion 4", "the rule the Brief owed step 2", and "the Brief's captured content-hash assumption" across the spoke files. That document was `docs/topics/docsite-digest/PLAN.md`, and **it exists in no git ref**: ```console $ git log --all --oneline -- docs/topics/docsite-digest (no output) ``` That fact decides the shape of the fix. The topic-docs lifecycle allows a pruned slice to be cited by pointer, but only a followable one — `docs/conventions/topic-docs/README.md:429`, "a pointer without a followable ref is not a preservation" — which is exactly the pattern #2686 used (`git show 5341117:docs/topics/boris-routines-adoption/PLAN.md`). Here there is no commit to point at, so **no pointer form the convention accepts can be constructed**. The alternative — committing the slice instead — is affirmatively blocked: `docsite-digest` appears nowhere in `scripts/contract-slice-baseline.txt`, so `check-contract-slice-prune.sh --check-diff` red-lines a new slice by design. Making each site self-contained is therefore the only conforming outcome, not a stylistic preference. So each site now states what it needs, in consumer terms: - The **deferred rung-3 decision** (`SKILL.md`, `discovery/link-map-format.md`) states its own fork — a presence-gated `/firecrawl:firecrawl map` seam versus a recorded reason to reimplement in-page extraction — its user-reserved arbiter, and its trigger (the first corpus whose seeds resolve neither `llms.txt` nor a sitemap). - The **deferred repo-tree enumeration rung** states its trigger (the first corpus whose repository half is too large to enumerate by hand). - The gate contract in `discovery/link-map-format.md` states classification coverage and the bound breach as the gate's own reasons to exist, rather than as external criterion numbers. - The **whole-snapshot hash** in `extraction/node-manifest-format.md` now points at `${CLAUDE_PLUGIN_ROOT}/reference/citation-shape.md`, which actually owns that fact (`citation-shape.md:24` defines the hash; `:41-42` ties it to map-corpus's `snapshot_sha256`). - `verification/inventory-format.md` and `verification/test_check_inventory.py` keep the substantive rationale (two prior gates in this codebase shipped fail-open and were caught by verifiers) and drop only the criterion-number citation. No behavior, schema, gate, exit code, or argument change. Only prose, one Python docstring, one Python error-message string, and the eval set's wording of an existing expectation. ## Test plan Every command run locally against this branch. | Check | Result | |---|---| | `python verification/test_check_inventory.py` | `Ran 29 tests` — `OK` | | `python discovery/test_discovery.py` | `Ran 30 tests` — `OK` (incl. `test_no_discovery_inputs_rejected`, which covers the edited branch) | | `check_linkmap.py` no-discovery path (the edited string) | exit **2**, message reworded, code unchanged | | `check_linkmap.py --help` | argument surface unchanged (`--linkmap`, repeatable `--discovery`) | | `scripts/check-changed-skills.sh origin/main` | `CHECK-SKILL map-corpus: PASS — 0 errors, 1 warning(s)` (warning is the pre-existing 204/200-line soft target) | | `npx markdownlint-cli2` over the changed markdown | `Summary: 0 error(s)` across 5 files | | evals schema (`ajv` vs the skill-quality bundled schema) | `evals.json valid` | | `check-evals-quality.sh` | `PASS (0 warning(s))` | | `scripts/check-contract-slice-prune.sh --check-diff origin/main` | exit 0 — "leaves no path under `docs/topics/`" | | `scripts/check-contract-slice-prune.sh --check` | exit 0 — 11 grandfathered, none stale | | `scripts/check-changelog-parity.sh --check` / `--check-bump` / `--check-preserved` | exit 0 each | Ghost-ref sweep across the repo (excluding `.git/` and the gitignored memory tier): no surviving `Q19` or `docsite-digest` reference in any shipped surface, and no surviving `Brief` / `criterion` back-reference anywhere under `plugins/knowledge/skills/map-corpus/`. The two remaining `Q19` hits are a different topic's own question numbering in `docs/topics/ladder-climb-roadmap/`. An independent fresh-context verifier re-derived the prune decision, read the gate script, re-swept for ghost refs, and re-ran the tests with the authoring rationale withheld. It returned PASS-WITH-FINDINGS, all findings advisory. Two are worth recording here: - **The edited `fail(2, …)` branch is covered for its exit code, not its message.** `discovery/test_discovery.py:354` (`test_no_discovery_inputs_rejected`) runs the gate with no `--discovery` and asserts exit 2; its assertion substring (`at least one --discovery`) sits *before* the reworded text, so the test pins the contract that matters and is indifferent to the wording — which is why it passes unchanged. - **The new `${CLAUDE_PLUGIN_ROOT}` pointer sits outside the skill gate's scope.** `check-skill.sh` check 5 walks `SKILL.md` only (`:529-530`), so the ref in `extraction/node-manifest-format.md` is not machine-checked. The identical form on `SKILL.md:204` *is* in scope and passes, and the target was verified by hand. Flagged as the one line here CI would not catch if it rotted. Anchor check, since renaming `## Evidence-token byte mapping (the rule the Brief owed step 2)` to `## Evidence-token byte mapping` changes its slug: repo-wide grep for `inventory-format.md#`, `the-rule-the-brief-owed`, and `evidence-token-byte-mapping` returns zero hits. No dangling anchor. The two new normative claims added to `SKILL.md` (a bare unguarded cross-plugin reference is barred; `dependencies` are reserved for hard requires) were checked against their owner, `docs/PLUGIN-PHILOSOPHY.md:25-26,33`, and are accurate. ## Related Follows the contract-slice prune pattern established by #2686, and pays down the last reference debt left by the working slice that produced `map-corpus`. No related issue: routine hygiene on a shipped skill, found while pruning the working slice that produced it. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…plicitly (#2690) Closes #2678 Phase 1 of the boris-routines-adoption plan, implementing [ADR 0010](https://github.com/melodic-software/claude-code-plugins/blob/main/docs/adr/0010-merge-findings-across-producers-and-mark-consumption-explicitly.md) rather than re-deciding it. The ADR itself lands in #2686; this PR is independent of that merge and cites the decision by URL rather than by in-tree path, so neither PR blocks the other. ## Summary `review:fanout`'s findings-file shape is the whole integration contract — nothing authenticates the writer — so any component of any shape that persists a conforming file reaches the apply relay without a fanout edit. That is the property the detector work depends on, and it made a second producer a silent data-loss bug: `fix` took the newest `*.md` declaring `type: review-findings` and merged nothing, so a detector running after a full review shadowed the entire review with no error, no warning, and a green run. This is the green-with-hidden-findings class `docs/conventions/liveness-assertion/README.md` exists to prevent, and it fails silently rather than loudly — which is why it is settled before the second producer ships. **This blocks every other phase of the plan.** ## Changes **1. `fix` consumes a merge set, not the newest file** (`context/fix-pass-mode.md` Step 1). Candidates are every conforming file whose `branch:` equals the current branch exactly; the set is then reduced by what a `fix-pass-record` already names as consumed. Coverage fields are **unioned, not picked** — `## Unparsed` concatenated, `## Surfaces` attributed per producer, every consumed file's `tier:` reported — because reporting one producer's `## Surfaces` line would hide a surface that ran and returned nothing, moving the hidden-findings failure up a layer instead of closing it. The Step 3 plan header names the consumed **set**, one line per file. **2. Dedup is presence-only, deliberately narrower than Stage 3.** Identical `Location` **and** identical `Finding` text. The tempting key — normalized path plus a ±3-line bucket — sits behind a Sonnet semantic stage the fix action does not run (`findings-normalization.md`), and adopting the bucket without the semantics inverts the pipeline's own rule: *"Minimize FALSE-MERGE over FALSE-SPLIT — a false merge silently drops a real issue."* Two distinct defects at `foo.ts:42` and `foo.ts:44` would collapse, and since Step 4 applies one `Action` per row, one producer's remediation would be discarded with no trace. Duplicate rows are possible and accepted. **3. The applied-plan record becomes the consumption ledger, written on every apply path** (Step 5). It was headless-`--yes`-only — *"Interactive and headless-stop paths write no record"* — so a bound anchored on it was a no-op on the dominant interactive path and the merge set would have grown without limit, re-injecting findings the **required** post-fix re-review had already resolved. The one path that applies nothing still writes nothing. ## Two things the issue did not specify, decided here - **`source-findings:` serialization is pinned.** Always a YAML block sequence of consumed file **names** — one entry even for a single file, never a bare scalar. The exclusion is a match against these values, so a writer emitting a scalar where the reader expects a sequence under-matches *silently* and re-consumes exactly what the record was written to retire. Names rather than repo-relative paths because the resolved `memory_dir` can differ between the session that wrote the findings and the session that applies them (a second checkout, or a `memory_dir` outside the worktree), while the branch findings directory is a single home whose `<UTC-timestamp>-<topic>.md` names are already unique within it. Comparing paths would fail exactly where comparing names holds — which is the same interaction as the shared-directory case, so the two are decided together. - **Consumption is per file, not per row, and the consequence is documented.** Rows surfaced by Step 4's low-confidence fence, or narrowed out by an operator ("only the correctness ones"), are still inside a file marked consumed. They are named in the record body and recovered by re-running the review — a fresh pass re-finds anything still present and persists it as a NEW file, which enters the next merge set as a fresh candidate. Stated explicitly because a reader implementing "exclude the consumed file" would otherwise reasonably conclude those rows vanish. Row-level ledgering was not adopted: ADR 0010 Decision 3 settles the ledger at file granularity. ## Migration safety - **A one-file set reduces to the previous behavior byte-for-byte** — merge, union, and dedup are all identities on one input. - **An empty set keeps the existing clean STOP** (message reworded to "No unconsumed findings"). - **Interactive applies now write a record**, so operators reading `.work/reviews/<branch-slug>/` will see records where previously only headless `--yes` runs produced them. ## Verification - `scripts/check-changelog-parity.sh --check-bump origin/main` → exit 0. - `scripts/check-changed-skills.sh origin/main` → `CHECK-SKILL fanout: PASS — 0 errors, 1 warning(s)` (the pre-existing no-Gotchas-surface warning). Editing `context/` makes `fanout` a changed skill, so the full static gate ran. - `markdownlint-cli2` on all four changed markdown files → 0 issues. - `evals.json` parses; 27 cases, no duplicate ids or names. - Pre-flight consumer sweep for `review-findings` / `fix-pass-record` / `source-findings` across `*.md`, `*.sh`, `*.py`, `*.json`, `*.yml`, `*.txt`: **no parse path outside `plugins/review/`** — only the ADR, two historical CHANGELOG lines, and this skill's own files and evals. **Not executed, and stated as such:** four of the issue's seven sanity checks are behavioral (two-producer merge, the interactive-ledger re-run, the foreign-branch record, the `foo.ts:42`/`foo.ts:44` guard). This skill is a prose contract with no executable surface, so those four are pinned as model-graded eval cases 24–27, not as executed fixtures. `evals.json` carries no fixture files for them. ## Independent review A fresh-context reviewer audited the diff against ADR 0010 and #2678 with the authoring rationale withheld. It confirmed all three ADR decisions present and every issue work item implemented, independently re-ran the three mechanical sanity checks, and returned **SHIP-WITH-FIXES** with four blocking findings. All four are fixed in `c00f874d`, along with every MEDIUM and LOW it raised: 1. **Recovery-by-re-review over-claimed.** Re-running `/review:fanout` regenerates only fanout's own rows, so a detector's row deferred by operator narrowing was retired permanently — a *new* silent drop inside the change that exists to close silent drops. Recovery now names the row's own producer, and the record body must attribute each deferred row to its source file. 2. **The `## Surfaces` union had no output slot** — computed in Step 2 and printed nowhere, reproducing ADR Decision 1's harm one layer along. Now in the plan template, the report, and the record body. 3. **`default-mode.md:77` still said the fix action parses only `## Findings` and `## Unparsed`** — contradicting the new Step 2 for anyone reading the producer-facing contract. Narrowed, not deleted: `## By dimension` alone stays presentation-additive. 4. **No reader rule for pre-0.20.0 records.** 0.19.0 wrote `source-findings:` as a bare scalar and those records survive the upgrade (gitignored local state), so a legacy record would have subtracted nothing and re-injected already-applied findings. The reader now tolerates a scalar as a one-entry sequence compared by base name; CHANGELOG carries the migration note; eval 28 pins it. Also fixed from the same pass: collapsed-row field rule (MAX tier, MAX confidence, all distinct `Action`s retained) and `Rank` renumbering after merge, which also fixes the apply order; "identical" defined as byte-for-byte after unescaping; an abnormally terminated apply writes no record; a minimally conforming producer missing `tier:` or `## Surfaces` is consumed with `tier: unstated` rather than skipped or guessed at; the `> DEGRADED:` blockquote collapses to its first line; the names-not-paths rationale replaced (it claimed a cross-directory property the design lacks — the true reason is that both sides are always read from one directory); the empty-set message no longer tells an operator who already ran the review to run it first; stale single-file language cleared from the SKILL.md description and the plugin README. ## Eval changes - **23 flips** — an interactive `--yes` apply now writes the record; the old expectation asserted the opposite. - **21 and 22 stand** — the headless-stop path applies nothing and still writes none; the headless `--yes` path is unchanged. - **19 updated** for the reworded empty-set message. - **24–28 added** — the two-producer merge with both files named and the `Surfaces (union)` line, the ledger subtraction proving the record is written on the interactive path, the both-sides exact-`branch:` filter, the false-merge guard at `foo.ts:42` / `foo.ts:44` with the collapsed-row field rule and `Rank` renumbering, and the legacy-scalar `source-findings:` tolerance. ## Related ADR 0010, #2686 (the ADR's PR), #2679 (detector-findings convention stub — unblocked by this) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…phase issues, slice pruned (#2727) Closes #2685 ## Summary The per-repo routine capability detection spike, executed to the contract-slice lifecycle: the topic slice carried an interviewed, adversarially validated PLAN for **routine prerequisite resolution** — how a routine identity discovers, per repository and per scheduling surface, whether it can actually run — then graduated its durable outcomes and pruned itself before merge. The net diff is ADR 0011 alone. ## Fix - **ADR 0011** (`docs/adr/0011-resolve-routine-prerequisites-per-identity-declared-over-detected.md`) records the durable decisions: per-identity-per-surface grain (`v1` identities only; fail-closed verdicts with first-class `unknown` and a provably reachable positive); declared beats detected with interactive-only proposing and per-rung ownership; composition of convention-owned consumer surfaces behind presence gates (no new prober, no cached profile, no new config family); a generated drift-gated emission bridging the authored leaves and the resolver; and the narrows-an-existing-enforcement-input-and-adds-none consumer rule. - **Five phase issues** carry the implementation with inlined `path:line` incumbent evidence: #2717 (contract document — filed blocked on the human naming ruling), #2718 (per-identity prerequisite sections in the ten `v1` leaves), #2723 (generated emission + drift gate), #2724 (deterministic resolver), #2725 (setup slice). #2726 files the reuse-or-replace follow-up found while planning (discovery's `ecosystem-discovery.md` duplicates the toolchain signal vocabulary). - **The slice is pruned** in the final commit; the full PLAN is preserved below. Interview provenance: the user was unavailable and directed that nothing gate the effort. All 16 interview branches were auto-answered and validated by two fresh-context agents with rationale withheld (12 confirmed — 7 with required amendments; 4 challenged and replaced). The finished PLAN then took independent adversarial and conformance passes (2 CRITICAL, 6 HIGH, 13 MEDIUM/LOW, 3 sanity-check defects — all adopted; the CRITICALs produced the generated-emission phase and the positive-verdict criterion). One call is deliberately routed to the human, not settled by agents: the naming ruling (verdict tokens, deferred-class marker, contract noun/filename), gating #2717. ## Verification Issue #2685's two sanity checks, run against the pre-prune commit: ```text $ ls docs/topics/routine-capability-detection/PLAN.md # exit 0 $ awk '/^## Brief/{f=1;next} f&&NF{found=1;exit} END{exit !found}' docs/topics/routine-capability-detection/PLAN.md # exit 0 (CHECK1-PASS) $ grep -qi "check-contract-slice-prune" docs/topics/routine-capability-detection/PLAN.md # exit 0 (CHECK2-PASS) ``` Prune gate, after the prune commit and after rebasing onto current `main`: ```text $ bash scripts/check-contract-slice-prune.sh --check-diff origin/main Contract-slice prune gate passed — this change set leaves no path under docs/topics/. $ bash scripts/check-contract-slice-prune.sh --check Every scripts/contract-slice-baseline.txt entry still names an existing slice under docs/topics/ (11 grandfathered). ``` Lint: `markdownlint-cli2` and `typos` clean on both the PLAN and the ADR; `check-shell-portability.sh --paths` hand-run on both per #2704 (clean). The forbidden-metric sweep over the branch diff returns only forbidding lines. ## Related - Refs #2686 (parent effort's PR; ADR 0010), #2699 (recovery-pointer hazard — this PR's prune commit gives no branch-SHA pointer), #2704 (hand-run portability check), #2717, #2718, #2723, #2724, #2725, #2726 - ADR 0011 in this diff <details> <summary>PLAN.md — full text as pruned (recover via Contents API at the pruning commit's parent)</summary> ````markdown # routine-capability-detection — PLAN Promoted from the boris-routines-adoption plan's Phase 8 by [#2685](#2685); the parent plan was carried by PR [#2686](#2686) and its slice is pruned, so #2686's body is the parent contract's followable home. Tier 0 item 3 of that effort, and the clause its originating goal names verbatim: "configurable per repo/product/app based on the repo itself, any external context provided from CLAUDE.md, AGENTS.md, repo files, MCP servers, CLI tools." ## Brief ### TLDR Define how a routine discovers, per repository, whether it can actually run there — and ship it as **routine prerequisite resolution**: a fail-closed, declared-over-detected composition of surfaces this marketplace already owns, resolved **per routine identity on its one bound scheduling surface**, never as a flat repo profile. The spike's durable outcome is the resolution contract's shape; implementation lands later as issue-filed phases extending the autonomy plugin. Four findings inverted the original "detection" framing: 1. **Nothing resolves "which routines can run here" today — but nearly every input surface already has an owner.** The catalog states prerequisite *consequences* ("routes to the advisory path … never a silent degrade") without a resolution procedure; the autonomy setup skill discovers substrates, trackers, schedulers, and observability imperatively per slice; the toolchain resolution ladder owns ecosystem inference; the tracker seam owns tracker binding. The missing piece is a **composition contract with a verdict vocabulary**, not a new prober. 2. **The grain is the routine identity on a surface, not the repo.** Every artifact a resolution touches is keyed by full routine identity (`<class-token>` or `<class-token>/<posture-token>`), postures diverge exactly where prerequisites diverge (an advisory posture and a direct-change posture of the same class need different things), and a capability present on one execution surface says nothing about another — the setup contract already states per-surface detection for isolation substrates, and the same doctrine binds here. 3. **Detection is the proposal rung, never the authority.** The fleet's own ladders (toolchain: config present → infer, then offer to persist → ask → default) and the external consensus (Renovate onboarding, Linguist overrides, buildpacks' declared order) agree: detect → propose → human ratifies → declaration governs. And proposing is interactive-only — at routine runtime the resolution reports; it never persists. 4. **Every nearby name is taken.** "Capability detection" is the guard plugins' fail-OPEN session-auth term; "capability" alone already means "a shipped contract area of this plugin" inside the autonomy plugin itself; three more capability-adjacent vocabularies live in the fleet. The provisional contract term is therefore **routine prerequisite resolution (per-repo)** — "prerequisite" is the catalog's own established noun for exactly these facts — with a five-way disambiguation note and a single human naming ruling gating the contract document (below). ### Goal Lock the resolution contract's shape (this document), graduate the durable decision to an ADR, file the implementation phases as tracker issues, and prune this slice before merge. The implementation goal those issues carry: a repository operator — or an unattended scheduled run — can ask "which routine identities can run against this repo, and why", and get a per-identity verdict with per-signal provenance, produced by deterministic probes plus tracked declarations, with agent judgment only where the question is genuinely semantic. ### Interview provenance The user was unavailable and directed that nothing gate this effort. The interview's 16 decision branches were answered with recommended answers, then adversarially validated by two fresh-context agents with the authoring rationale withheld (the `/planning:audit-answers` shape). Outcomes: 12 confirmed (7 with required amendments), 4 challenged (output grain, surface qualification, naming-collision enumeration, composition sourcing) — every challenge adopted as the validator stated it. **No answer is represented as a user decision**, and one call is explicitly routed to the human rather than settled here: the naming ruling (see "Open questions"). "Nothing gates this effort" applies to this spike's own PR; the filed P1 issue is deliberately marked blocked on that human ruling at filing time — a gate on future implementation, not on this PR. ### Stress-test summary After the interview validation, the drafted plan took two further independent fresh-context passes: an adversarial review (assumptions, evidence, failure scenarios, operational gotchas) and a conformance audit (the promoting issue's sanity checks plus every constraint, run empirically). They returned 2 CRITICAL, 6 HIGH, 13 MEDIUM/LOW-class findings plus three sanity-check defects; every finding was adopted. The CRITICALs reshaped the plan: the resolver had no machine-readable source for identities or prerequisites (leaf prose was the only home — hence the new generated, drift-gated emission phase), and the acceptance criteria were satisfiable by a resolver that hardcodes `unknown` (hence the positive-verdict criterion and fixture). The HIGHs added the fifth naming collision (autonomy's own "capability" vocabulary), real version-bump assertions in place of a vacuously-passing gate, `path:line` incumbent evidence for every phase, the `claude-config` seam for MCP enablement, the orphaned-fixtures gate and test-harness pair, and the signal-envelope constraint on the new binding section. ### The contract shape (the locked answers) **Output grain.** One resolution per **routine identity** — `<class-token>` or `<class-token>/<posture-token>` — computed for the pair (identity, its one bound scheduling surface). Class-level axes (Access class, isolation floor, per-class prerequisites) are the derivation *source*; the posture refines it; the identity is the emission key. A class-level verdict cannot express that an advisory posture is runnable while its direct-change sibling is not, and that is precisely where prerequisites diverge. The isolation floor and the `executor_class` merge cap are consumed from the existing guardrail slice, never re-derived — the latter is security-surface data and never repo-derivable. **Candidate set.** `v1` rows only — they alone have definition leaves, and posture tokens are leaf-owned. A `join:` row has no leaf and therefore no identities to resolve; it reports under a deferred-class marker, not as a verdict (that marker's token is part of the naming ruling below). A `not-a-routine` row is outside the domain entirely — no agent session exists to bind, so any verdict for it is a category error. **Verdict vocabulary.** Four verdicts: provisionally `supported / supported-with-conditions / unsupported / unknown`. `unknown` is first-class and distinct from `unsupported` — fail-closed, both route to the advisory path. All output tokens — the four verdicts and the deferred-class marker — are provisional pending the single human naming ruling (Open questions). Whatever the ruling, two constraints survive it: no token may read as a security-binding assertion (`binds` already means "has a ratified identity entry on the security binding"), and no token may read as health (`ok`, `available`, `healthy`, `pass` are all barred). **Precedence.** Declared beats detected, with detection proposing: a tracked declaration wins over any probe result; detection fills gaps and proposes declarations, never silently overrides one. Divergence between a declaration and a probe result is surfaced as a finding, not auto-resolved. Proposing is **interactive-only**: non-interactive and forked contexts are barred from the ask-and-persist rungs (the topic-docs rule, reused), so at routine runtime the resolution reports and never persists. Per-rung ownership is part of the contract: connector entitlement for `prod`/`product`/`org`/`ext` binds at the **org rung** of the binding seam, and a lower rung may never assert a prerequisite a higher rung owns; security axes accept no repo-local value at all (the autonomy plugin's ratified cascade deviation). **Who detects.** Split per portion, in the catalog's own per-portion discipline. Presence-shaped signals are probed by a **script with no agent session** — the deterministic-probe preference is this contract's own stated choice, consistent with the philosophy's deterministic-gate posture that the promoting issue names as an input. Semantic questions (does the test suite discriminate; which architecture rules apply; what a CLAUDE.md convention implies) are **judgment-only**: never encoded as file-presence heuristics — they resolve to `unknown` with a named follow-up, or to an interactive proposal pass. The catalog's `DET`/`AGT` tokens are deliberately not reused for probes: there they are judgment verdicts that carry "not a routine, zero agent tokens", and overloading them invites exactly that misread. **Probe classes.** Four, named for what they read. Every non-repo-file result is **surface-qualified**: it is evaluated on, and carries, the surface it was probed on — a CLI on the authoring laptop says nothing about the CI runner that fires the routine. Probe evidence is durable per-surface (the existing isolation-binding `probe_evidence` pattern), and the two keyspaces are distinct: isolation bindings key on execution-surface ids, while the scheduling `surfaces` map carries `execution_surface` as a field — never collapsed. 1. **repo-file probes** — build/dependency manifests, test config, CI config, tracker binding, flag-system SDK presence: deterministic glob/manifest probes. 2. **harness-context probes** — `.mcp.json` server inventory, repo-declared plugins, committed skills: deterministic reads of structured, committed surfaces. `.mcp.json` presence is not availability: enablement is settings-gated (`enableAllProjectMcpServers` / `enabledMcpjsonServers` / `disabledMcpjsonServers` — a question the `claude-config` audit already owns, composed presence-gated, never re-implemented), and servers also arrive from user scope and plugins — the probe reports presence and the enablement gate separately. **Bounded limitation, stated up front:** the enablement gate is only partly resolvable from committed surfaces (`.claude/settings.local.json` and user scope are invisible to a clone), so on a scheduled run an MCP-dependent identity can resolve at best `supported-with-conditions` — and where enablement is undeterminable, `unknown`. That is the fail-closed posture working as designed, not a bug to discover later. 3. **machine-context probes** — CLI availability, local substrates: deterministic, per-surface; a result is a claim about the probed surface only, never a repo claim. 4. **prose-context inference** — CLAUDE.md, AGENTS.md, README: judgment-only inference source for *proposing* declarations into non-security keys, interactively. The deterministic resolver never parses prose, and prose is never runtime authority. One platform fact bounds this class: Claude Code reads `CLAUDE.md`, not `AGENTS.md` — an `AGENTS.md` reaches a session only through a reference. **Composition, not re-implementation.** Resolution composes the convention-owned **consumer surfaces**, never a sibling plugin's bundled files; every cross-plugin reference is presence-gated with a documented fallback per seam-phrasing: - ecosystems: the toolchain seam (the fleet's stated SSOT for ecosystem detection and command resolution) where installed, reading the consumer's `.claude/ecosystems/<eco>.yaml` under the ecosystem-commands convention — reading **resolved** state, not bare presence: an ecosystem present but `enabled: false` is not configured, and the user-global and `.local.yaml` layers are uncommitted, so a scheduled run reports them unresolvable (the same treatment as MCP enablement); fallback is inference from the repo's own build files, never another plugin's bundled defaults; - MCP enablement conformance: the `claude-config` audit surface, presence-gated; - tracker: the work-item tracker seam — `.work-item-tracker.json` plus the bound adapter's `capabilities.json` (declared adapter verb support); - substrates, schedulers, observability: the autonomy setup skill's own discovery slices (same plugin, no gate needed); - configured-surface enumeration: by each surface's own presence in the repo — never by reading the config-cascade registry table, which is a conformance ledger that deliberately lists non-conforming rows; - signals with no owner (CI-config presence, flag-SDK presence): probes owned by this contract. **Fail-closed.** A prerequisite that cannot be established does not enable the identity; it routes to the advisory path exactly as the trigger contract already handles a missing surface or entitlement, and absence semantics cite the philosophy's silently-skipped-feature-is-a-defect rule rather than re-owning them. Fail-open is the guard plugins' session-auth posture, and precisely what this contract must not inherit. **Consumers — never admission data.** The resolution **narrows an existing enforcement input and adds none**. Its two consumers are: (1) an input to the human-landed *prepared* change to `admission.classification.temporal` on the settings-as-code home — the routine slice prepares, never writes, the security surface; and (2) a **narrowing-only** influence on the repo-local `routines.enabled` section (which the existing envelope conformance already validates claims against) — an identity may be enabled only when its verdict clears, and an identity with no protected classification entry stays unclassified and fail-closed human-gated regardless (already structural, cited not asserted). A repo-local input to a protected path is the precise agent-writable bypass the classification obligation forbids. **Configured is not working.** Presence establishes `configured`, never health. The verdict vocabulary is non-health-asserting by construction: it makes no success claim for a probe to be false about. The liveness-assertion contract's on-touch rule still binds every implementing surface — each phase that touches an engine surface (the setup `check`, the resolver) states which taxonomy row applies and how it satisfies fail-loud or agent-readable routing, rather than claiming exemption. A consumer that treats `configured` as `working` is itself the false-green defect, owned by that consumer's verification. Execution evidence (check runs, job logs) belongs to the consuming routine. **No cached profile; recompute at every consumption.** The resolution recomputes wherever it is consumed — a setup `check`, a pre-enablement gate, an advisory read; a persisted verdict is never authority. A resolution computed once at setup would silently govern a routine firing weekly while the repo gains test suites and loses connectors — the healthy-while-dead false-green shape. The only persisted artifacts are declarations (human-ratified, tracked) and surface-qualified probe evidence under the existing pattern. One existing nuance is stated, not contradicted: the binding seam persists the discovered org-binding *document path* — a pointer into the declaration layer, not a capability profile. **Scheduled runs read committed surfaces only.** A cloud run clones from the default branch and loads committed content; a capability claim sourced from one operator's machine is false in every other execution context. **Where it lands.** One new contract document in `plugins/autonomy/reference/` (the binding-seam layout rule: each shipped contract area lands exactly one document there), provisionally `prerequisite-resolution.md`, hub-linked from `routines.md` by pointer. **Per-identity prerequisite data lives in each `v1` class's own leaf** — under the single-home rule the contract doc owns vocabulary and derivation rules only, never per-class facts. Between the leaves and the resolver sits **one generated, drift-gated machine-readable emission** derived from the leaves (the generate-plus-`--check` pattern the catalog generator established): the leaves stay the authored single home, and the resolver reads structure, never prose. The emission is a generated in-plugin artifact, not consumer configuration — it creates no new config-file family. Implementation extends the existing autonomy setup skill as a slice (its own stated extension model); declarations ride `.claude/autonomy/binding.json` as an additive section that **references existing scheduling-surface ids and declares no `surfaces` map of its own** (the envelope conformance check merges every section's `surfaces` map, and duplicates are ambiguous). No new plugin, no new skill, no new catalog, no new config-file family. **Vocabulary.** Keyed to the catalog's existing axes — Access classes, join triggers, per-class prerequisites including isolation floors — plus the four probe classes above. The disambiguation note names five incumbent vocabularies this term is NOT: (i) the guard plugins' session-auth "capability detection" (fail-open, session-scoped; consumed by the work-loop, babysit-loop, and attend-queue skills); (ii) the autonomy plugin's own internal "capability" (a shipped contract area: "each capability … lands exactly one contract document", "capability slices"); (iii) verification-topology's rejected model-capability labels; (iv) loop-lane capability *tiers* (model selection); (v) the tracker adapter's `capabilities.json` (declared adapter verb support — a composed input here, not a synonym). This is why the provisional term drops "capability" altogether for the catalog's own noun, "prerequisites". ### Constraints Binding rules, each verified this session: - **Extend, don't fragment** — no new plugin, skill, catalog, or config-file family. Carried by the philosophy's design boundary (a plugin "never imports files from a sibling plugin"; `docs/PLUGIN-PHILOSOPHY.md:20-22`) and the setup skill's slice extension model (`plugins/autonomy/skills/setup/SKILL.md:456`); the parent Brief's resolved landing question and ADR 0005's question-not-population doctrine are supporting precedent, cited as such rather than as a general repository rule. - **ADR 0004 D-1** — nothing ships until an incumbent search with `path:line` evidence proves no existing surface covers it. Each phase below carries its evidence inline, and each filed issue restates it — the `.work/` memory tier is gitignored and unreachable from any other execution context, so nothing durable may point there. - **Contract-slice prune gate** — `docs/topics/routine-capability-detection/` matches no line in `scripts/contract-slice-baseline.txt`, and the baseline is read from the base revision, so it cannot be exempted. The terminal phase prunes this slice before merge. - **Version bump + matching CHANGELOG entry in the same PR** for any plugin touched. The parity gate only fires when a manifest version *changed*, so each phase asserts the bump itself (the manifest version differs from `origin/main`) **and** `--check-bump` exit 0 — never the latter alone, which passes vacuously on a bump-less diff. This spike's own PR touches no plugin. - **No acceptance/merge-rate metric anywhere, in any role** — a standing directive inherited from the parent effort's Brief (its acceptance criterion 4). Its codification into contract text is filed parent work, not yet on `main`; this plan binds itself to the directive regardless. - Only `docs/topics/` is docs-only-allowlisted; the ADR this PR graduates sits outside it, so the full CI suite runs on this PR — expected and fine on an otherwise-clean tree. ### Captured assumptions - The catalog's mapping rules stay the derivation authority for guardrail rows; prerequisite resolution decides *whether an identity can run in a repo*, never *what class it derives* — the two must not be conflated. - The autonomy setup skill's extension model ("capability slices that have not shipped yet — each lands with its own work package and extends this skill") remains the sanctioned implementation seam. - External consensus grounding (Renovate/Linguist/buildpacks/Dependabot, agents.md, Claude Code docs) was verified against primary sources 2026-08-15; the detect→propose→ratify pattern and the no-cached-profile posture both held across every surveyed tool. ### Out of scope - Any change to guardrail classes, the matrix, or the mapping rules — prerequisite resolution reads the catalog; it never re-derives rows. - Health/liveness verification of a configured surface (owned by the consuming routine's own verification; the on-touch obligation for surfaces this plan's phases touch is in scope and stated per phase). - Org-binding authoring UX for connector entitlements (the org rung owns them; declaring them is org-policy work, interviewed in the setup slice, not designed here). - The join-trigger lifecycle of catalog rows (per-catalog, not per-repo — a deferred class gains a leaf when its join trigger fires fleet-wide; this contract only answers per-repo, per-surface eligibility). - A prerequisite-resolution surface for non-routine consumers (loops, goals, one-shot sessions) — deferred with a trigger: revisit when a second consumer class asks the same per-repo question. ### Acceptance criteria (for the implementation issues) 1. Every verdict carries per-signal provenance (which probe on which surface, or which declaration at which rung). 2. The deterministic resolver runs with no agent session and is reproducible: same tree, same surface, same verdicts. 3. Fail-closed proven by fixture: an absent prerequisite yields `unknown` or `unsupported`, never a positive verdict. 4. Declared-beats-detected proven by fixture: a declaration contradicting a probe wins, and the divergence is reported. 5. Posture divergence proven by fixture: two postures of one class resolve to different verdicts from the same tree. 6. **A positive verdict is reachable, proven by fixture**: a fully-provisioned fixture tree yields the positive verdict for a named identity, with the signals that produced it in provenance. Without this, a resolver hardcoding `unknown` passes every other criterion and fail-closed ships it silently. 7. Every engine surface a phase touches states its liveness-assertion taxonomy row and how it satisfies fail-loud or agent-readable routing; no verdict token reads as health or as a security-binding assertion. 8. No acceptance/merge-rate metric anywhere. ## Plan ### Standards grounding | Surface | Sections leaned on | |---|---| | `plugins/autonomy/reference/routines.md` | axes, mapping rules, Access-to-prerequisites, routine identity, per-portion determinism, instruction provenance | | `plugins/autonomy/reference/binding-seam.md` | ladder, null semantics, layout rule (one contract doc per shipped area) | | `plugins/autonomy/reference/guardrails/admission-policy.md` | fail-closed seam; no agent-writable admission input | | `plugins/autonomy/reference/guardrails/isolation-ladder.md` | the per-class isolation floor P2 derives per leaf | | `plugins/autonomy/reference/guardrails.md` | the matrix the floors come from | | `plugins/autonomy/reference/trigger-dispatch.md` | advisory-path rule; classification obligation; executor classes | | `plugins/autonomy/skills/setup/SKILL.md` | slice extension model; per-surface detection; prepared-never-written security surface; identity-keyed sections | | `docs/conventions/config-cascade/README.md` | layer semantics; autonomy's declared deviation; provenance reporting | | `docs/conventions/topic-docs/README.md` | contract-slice lifecycle; prose-is-inference posture; non-interactive rung bar; single-home rule | | `docs/conventions/liveness-assertion/README.md` | healthy-while-dead class; the on-touch obligation | | `docs/conventions/seam-phrasing/README.md` | presence-gate-plus-fallback shape for every composed seam | | `docs/conventions/ecosystem-commands/README.md` | the consumer ecosystems surface the resolver reads | | `docs/PLUGIN-PHILOSOPHY.md` | no sibling-plugin imports; absence semantics; configuration ownership | | `plugins/toolchain/reference/resolution-ladder.md` | the canonical detect→offer-to-persist ladder; resolved-`enabled` semantics | | `plugins/work-items/tools/work-item-tracker/CONTRACT.md` | tracker binding + adapter `capabilities.json` | ### Phase spine ```text P1 contract doc ──> P2 leaf prerequisite sections ──> P3 generated emission ──> P4 resolver ──> P5 setup slice (each an issue; sequential, same plugin, shared vocabulary) P6 graduate + prune ── terminal; executed on THIS branch before THIS PR merges ``` P1→P5 are sequential by design: the leaves consume the contract's vocabulary; the emission is generated from the leaves; the resolver reads the emission; the slice consumes the resolver. All five touch `plugins/autonomy/**`, so each runs the full CI suite. The four change-set-wide gates run in **every** phase PR in addition to each phase's own checks: the bump assertion pair (the manifest version differs from `origin/main` AND `check-changelog-parity.sh --check-bump` exits 0), `check-changed-skills.sh`, `check-orphaned-fixtures.sh --check`, and `check-contract-slice-prune.sh --check-diff` (must-not-regress — trivially green on a sliceless diff, listed so a phase that reintroduces a slice is caught). The forbidden-metric sweep (`git diff origin/main | grep -inE "(merge|acceptance)[ -]rate"` yields only forbidding lines) is a declared reviewer check, not an exit-code gate. P1 is gated on the human naming ruling (Open questions). --- ### Phase 1: The prerequisite-resolution contract document [ISSUE — blocked on the naming ruling] One new document, provisionally `plugins/autonomy/reference/prerequisite-resolution.md`, owning everything under "The contract shape" above **except per-class facts**: identity-and-surface output grain, candidate-set rule, the ruled verdict vocabulary and deferred-class marker (fail-closed, `unknown` first-class), the four probe classes with surface qualification and the two bounded limitations (MCP enablement, uncommitted ecosystem layers), precedence with per-rung ownership and interactive-only proposing, composition seams (each presence-gated, including the `claude-config` MCP-enablement seam), the narrows-and-adds-no-enforcement consumer rule, configured≠working with the liveness-assertion on-touch statement, recompute-at-every- consumption, committed-surfaces-only for scheduled runs, and the five-way disambiguation note. - ADR-0004 incumbent evidence (restate inline in the issue): no contract, skill, or script answers "which routine identities can run against this repository" — `plugins/autonomy/reference/routines.md:122-133` states prerequisite consequences only (missing surface → advisory path), no procedure; `plugins/autonomy/skills/setup/SKILL.md:52-59` and `:225-232` are imperative per-slice discovery prose (substrates "PER SURFACE"), not a reusable contract; repo-wide sweeps for capability-resolver vocabulary return only the five incumbent meanings the disambiguation note names. - Authoring gotcha: the CI spell gate splits coined hyphenated compounds — prefer backticked tokens or plain words for coinages like declared-over-detected. - One pointer sentence in `routines.md` "Access to prerequisites"; one bullet in the plugin README. No other hub edit. - Version bump + matching CHANGELOG entry: assert the manifest version differs from `origin/main` AND `scripts/check-changelog-parity.sh --check-bump origin/main` exits 0. - Sanity: `ls plugins/autonomy/reference/prerequisite-resolution.md` exits 0; `grep -c "prerequisite-resolution" plugins/autonomy/reference/routines.md` ≥ 1; `grep -ci "fail-open" plugins/autonomy/reference/prerequisite-resolution.md` ≥ 1 (the disambiguation note names the opposite posture). ### Phase 2: Per-identity prerequisite sections in the v1 leaves [ISSUE] Under the single-home rule, per-class facts belong to each class's own leaf. Each of the ten `v1` leaves gains a prerequisites section deriving its identities' needs — Access class, isolation floor, connector entitlements (and which rung owns each), per-posture divergences — through the P1 vocabulary. - ADR-0004 incumbent evidence (restate inline in the issue): "prerequisite" in the leaves today means only the floor/connector consequence inherited from the catalog (`routines.md:122-133`); identities exist only as leaf prose (e.g. `plugins/autonomy/reference/routines/doc-freshness-sweep.md:75-76` names `doc-freshness-sweep/advisory` and `/docs-change` in a paragraph; `ci-health-review.md:51` likewise) — no leaf carries per-identity repo-needs data, and no other surface may (single-home). - Posture-divergent classes must show different prerequisite sets — the grain argument made concrete. - The isolation floor and `executor_class` merge cap are cited from the guardrail slice, never re-derived. - `join:` and `not-a-routine` rows gain nothing (no leaves; out of domain). - Version bump + CHANGELOG: the same bump assertion pair as P1. - Sanity: `ls plugins/autonomy/reference/routines/*.md | wc -l` prints 10 (count floor — a zero-match glob must not pass), and `grep -L '^## Prerequisites' plugins/autonomy/reference/routines/*.md` prints nothing; at least one leaf shows per-posture divergence (two postures, two different prerequisite sets). ### Phase 3: The generated identity-and-prerequisite emission [ISSUE] The bridge the resolver needs and the single-home rule forbids authoring twice: one machine-readable emission (JSON, in-plugin, generated) listing every `v1` identity with its derived prerequisite set, **generated from the leaves** with a `--check` drift gate — the same generate-plus-check pattern the catalog generator established. The leaves stay the authored single home; the emission is derived output, versioned in-plugin so the resolver and CI consume it without parsing prose; drift between leaves and emission fails CI. - ADR-0004 incumbent evidence (restate inline in the issue): no structured identity registry exists — `scripts/generate-catalog.mjs` builds `docs/CATALOG.md` from plugin manifests and never sees routine identities; the only structured posture-qualified identities in the tree are security-binding test fixtures. The generate-plus-`--check` drift-gate shape is the incumbent pattern being reused, not a new mechanism. - The emission is a generated in-plugin artifact, not consumer configuration: no new config-file family, and consumers other than the resolver and CI are out of scope. - Generator + `--check` mode ship with a co-located `*.test.sh` and manifest, per the plugin's existing conformance-script shape. - Exec bit via `git update-index --chmod=+x` for any new shebang file (the exec-bit gate is repo-wide and ungated; on Windows `core.filemode` is false and `git add` records 100644). - Version bump + CHANGELOG: the same bump assertion pair. - Sanity: the `--check` mode exits non-zero on a hand-edited emission (drift fixture) and 0 on a regenerated one; every identity named in a leaf appears in the emission and vice versa. ### Phase 4: The deterministic resolver [ISSUE] The script core (incumbent shape: Node `.mjs` beside the setup skill's existing conformance scripts): resolves the verdict set for the `v1` identities on a named surface, with no agent session, reading the P3 emission — never leaf prose. - ADR-0004 incumbent evidence (restate inline in the issue): no repo-prerequisite probe script exists — the only shipped probe surfaces are the isolation-substrate probe template (`plugins/autonomy/skills/setup/templates/isolation-probe.md`) and tool-level flag probes elsewhere in the fleet; composition targets, not rivals: the toolchain seam (`plugins/toolchain/README.md:10` — the surface others compose "instead of baking their own tables"), the tracker seam (`plugins/work-items/tools/work-item-tracker/CONTRACT.md:20` binding; `:86,:97` adapter `capabilities.json`), the `claude-config` MCP-enablement audit, and the setup skill's own slices. - Probes: repo-file and harness-context classes; composition reads of `.claude/autonomy/binding.json` (declared sections), `.claude/ecosystems/*.yaml` (**resolved** state per the ecosystem-commands convention — `enabled: false` is not configured; uncommitted layers report unresolvable), `.work-item-tracker.json` + adapter `capabilities.json`, `.mcp.json` (presence and enablement gate reported separately, enablement via the `claude-config` seam where installed). Machine-context probes only behind an explicit surface argument; results surface-qualified. - Output: per-identity verdict + per-signal provenance (probe + surface, or declaration + rung). The emission excludes wall-clock fields (timestamps, durations) so reproducibility is byte-comparable; never persists a verdict; never writes any config. - Every cross-plugin read presence-gated with the documented fallback per seam-phrasing; absence is a verdict input, not an error. - Liveness-assertion on-touch statement: the resolver is an engine surface — it names its taxonomy row and satisfies fail-loud (non-zero exit on internal failure, never a verdict-shaped fallback). - Fixtures (before the resolver), each consumed by the co-located `*.test.sh` + manifest so the orphaned-fixtures gate passes: fail-closed; declared-beats-detected; divergence-report; posture-divergence; positive-verdict (fully-provisioned tree yields the positive verdict with provenance); bare-repo (all verdicts `unknown`/`unsupported`, honest non-skip assertions — the discriminating-skip gate polices `skip_case` in `plugins/**/*.test.sh`). - Exec bit, shell-portability, ShellCheck as in P3. - Version bump + CHANGELOG: the same bump assertion pair; evals if SKILL.md changes. - Sanity: two consecutive runs on the same tree and surface emit byte-identical verdicts (wall-clock-free emission); the six fixture assertions pass via the co-located test harness; `scripts/check-changed-skills.sh origin/main` and `scripts/check-orphaned-fixtures.sh --check` exit 0. ### Phase 5: The setup slice [ISSUE] Extends the autonomy setup skill per its own extension model: `check` reports the resolved verdict set with provenance; `apply` runs the interactive propose→ratify loop — the prose-context pass reads CLAUDE.md/AGENTS.md/README to propose declarations into **non-security keys only**, the human ratifies, and the slice writes the new binding section additively (absent-section tolerance holds). The section **references existing scheduling-surface ids and declares no `surfaces` map of its own** — the envelope conformance check merges every section's `surfaces` map and duplicates are ambiguous. - ADR-0004 incumbent evidence (restate inline in the issue): the setup skill's five shipped slices (telemetry, capture, trigger/dispatch, guardrail, routine) are each discovery-first, detect-diff-reconcile (`plugins/autonomy/skills/setup/SKILL.md:233-240`); none resolves per-identity prerequisites. The routine slice's enablement section (`routines.enabled`, keyed by full routine identity, `SKILL.md:419`) is the narrowing target this slice feeds. - Detect-diff-reconcile: an existing declaration is authoritative input to reconcile against; divergence from probe results is a finding, never a silent overwrite. - Narrowing-only enablement: an identity may be enabled only when its verdict clears; `unknown`/`unsupported` routes to the advisory path. The slice **prepares** any security-binding change and never writes that surface. - Org-rung entitlements are interviewed, never auto-written: report which prerequisites await the org rung and stop. - Non-interactive contexts skip ask-and-persist rungs and report assumptions (topic-docs rule, cited). - Liveness-assertion on-touch statement for the extended setup `check`, per that contract's engine-surface obligation. - Version bump + CHANGELOG: the same bump assertion pair; evals cover the new slice paths. - Sanity: a fixture binding with a declaration contradicting a probe produces a reconcile finding, not a silent overwrite (asserted in the co-located test harness); `check` on a bare repo reports every identity `unknown` or `unsupported` with no error; `scripts/check-changed-skills.sh origin/main`, `scripts/check-orphaned-fixtures.sh --check`, and `node plugins/autonomy/skills/setup/scripts/check-signal-envelope.mjs` (against the fixture binding carrying the new section) all exit 0. ### Phase 6: Graduate the durable outcomes and prune the contract slice [TERMINAL — this PR] The prune gate fails on this branch until this phase runs, by design: this slice matches no baseline line and the baseline is read from the base revision (`check-contract-slice-prune`). - Graduate the resolution-contract decision to an ADR (per-identity-per-surface grain; declared-over-detected composition of owning surfaces; fail-closed; narrows-and-adds-no- enforcement; configured≠working; no cached profile; generated-emission bridge; the five-way naming disambiguation) — hard to reverse once implemented against, surprising without context, and the product of real trade-offs with named rejected alternatives. The ADR title and every filed issue title use "prerequisite resolution", not "capability detection" — the topic slug alone keeps the promoting issue's original name. - File Phases 1-5 as tracker issues, each restating its inlined `path:line` incumbent evidence, work items, and sanity checks; the P1 issue is explicitly marked blocked on the naming ruling at filing time. File the reuse-or-replace follow-up (discovery's `ecosystem-discovery.md` duplicates the toolchain signal vocabulary) as its own issue, outside this plan. - Paste this PLAN into the PR body inside a `<details>` block. The durable record is the graduated artifacts themselves — the ADR and the filed issues; the recovery pointer for the pruned files uses the Contents-API-by-SHA form (`ref=<pruning-commit>^`, topic-docs lifecycle step 5), never a `git show <branch-sha>:<path>` form — this repo squash-merges and deletes head branches, so branch-SHA pointers die with the branch (#2699). - A final commit deletes `docs/topics/routine-capability-detection/`. - Sanity: `bash scripts/check-contract-slice-prune.sh --check-diff origin/main` exits 0 after the prune commit (it fails today, naming exactly this slice's file — verified); `bash scripts/check-contract-slice-prune.sh --check` exits 0 (must-not-regress: it passes today too, since this slice never had a baseline entry — listed to catch an orphan line, not as proof of the prune); `ls docs/topics/routine-capability-detection/ 2>/dev/null` returns non-zero; the PR body contains the pasted plan. --- ### Blast radius **LOW for this PR** (one ADR added, one slice pruned; no plugin, contract, or CI surface changes). **MEDIUM for the filed implementation phases**: they extend one plugin's reference and setup surfaces behind additive schema tolerance, with no breaking change to any consumed format; the risk concentrated in P5's binding writes is bounded by detect-diff-reconcile, narrowing-only enablement, the no-own-`surfaces`-map rule, and the org-rung/security-axis exclusions. ### Alternatives considered | Alternative | Why rejected | |---|---| | Free-standing repo profile artifact | Re-opens interpretation at every consumer; the consuming question is per-identity | | Class-grain verdicts | Cannot express posture divergence, which is exactly where prerequisites diverge; every consuming artifact is identity-keyed | | Flat (surface-blind) signals | A capability on one surface says nothing about another; the setup contract already states per-surface detection | | Resolver parses leaf prose | Non-reproducible and violates the prose bar; the generated drift-gated emission keeps leaves the single home | | Hand-authored machine registry beside the leaves | Second authored home for leaf-owned facts; single-home violation the generated emission avoids | | Detected beats declared | Against fleet ladders and external consensus; staleness surfaces as divergence findings instead | | Self-contained prober with own signal tables | Re-implements owned probes; forks the toolchain vocabulary the way discovery's `ecosystem-discovery.md` already did | | Reading sibling-plugin bundled files | Barred: no sibling-plugin imports; the consumer surface is the seam | | Cached capability profile with TTL | No fleet precedent; the healthy-while-dead shape; external consensus recomputes per run | | Fail-open on missing signal | The guard plugins' session-auth posture — correct there, an unauthorized autonomous run here | | Resolution as admission data | A repo-local input to a protected path is the agent-writable bypass the classification obligation forbids | | New `.claude/capabilities.*` config family | Second home for admission-adjacent facts; binding.json sections are additive by design | | Parse conventions out of CLAUDE.md/AGENTS.md at run time | Prose is never runtime authority; agent-writable prose can never satisfy an admission-adjacent input | | Per-class facts in the contract doc | Single-home violation; leaf-owned data | | Keep "capability" in the contract term | Five incumbent collisions, including the autonomy plugin's own internal vocabulary | | New plugin or new skill | The philosophy's design boundary; the setup skill's slice model is the sanctioned extension seam | ### Test strategy Docs-shaped phases carry grep-shaped sanity checks (stated per phase, with count floors where a zero-match glob would pass vacuously); the emission and resolver phases are fixture-first — the drift, fail-closed, declared-beats-detected, posture-divergence, and positive-verdict fixtures exist before the code does, each consumed by a co-located `*.test.sh` + manifest so `check-orphaned-fixtures.sh` passes; the slice phase asserts reconcile-not-overwrite plus the envelope check on a fixture binding. The four change-set-wide gates and the reviewer-judged forbidden-metric sweep run in every phase PR (Phase spine). Checks that are trivially green on a given phase's diff are labeled must-not-regress, never presented as proof. ### Risks and mitigations | Risk | L | I | Mitigation | |---|---|---|---| | Output tokens drift into health or security-binding claims | Med | High | Barred-token constraints; single human naming ruling before P1 | | A repo-local declaration asserts an org-rung prerequisite | Med | High | Per-rung ownership in the contract; P5 interviews instead of writing; admission stays fail-closed regardless | | Probe false positives (configured-but-dead surfaces) | High | Med | configured≠working vocabulary; consuming routines own health; on-touch statements per engine surface | | Stale verdict governs a standing routine | Med | High | Recompute at every consumption; no persisted verdict is authority | | Leaves and emission drift apart | Med | High | Generated emission with `--check` drift gate in CI | | Resolver ships hardcoded-`unknown` | Med | High | Positive-verdict acceptance criterion + fixture | | Composition seam absent (no tracker binding, no ecosystems file) | High | Low | Presence-gate + documented fallback per seam-phrasing; absence is a verdict input, not an error | | Surface qualification collapses into a flat answer | Med | High | Per-(identity, surface) grain in the contract; the setup skill's per-surface doctrine cited | | New binding section breaks envelope conformance | Med | Med | References existing surface ids; declares no own `surfaces` map; envelope check in P5 sanity | | Term collision confuses readers | Med | Med | Five-way disambiguation note; "prerequisite" is the catalog's own noun | ### Open questions - **ROUTED TO HUMAN — the naming ruling.** One ruling covers: the four verdict tokens (provisional `supported / supported-with-conditions / unsupported / unknown`), the deferred-class marker, and the contract noun and artifact filename (provisional "routine prerequisite resolution" / `prerequisite-resolution.md`). Five incumbent collisions constrain it: `binds` (ratified security-binding entry), the guard plugins' fail-open "capability detection", autonomy's own internal "capability" vocabulary, loop-lane capability tiers, and the catalog's `DET`/`AGT` judgment verdicts. The P1 issue is filed blocked on this ruling. This is a genuine open question, not an agent-settled one. - No other question blocks this PR; the remaining human decisions are implementation-time gates, listed below. ### Handoff to implementation #### User-approval gates (decisions this spike does NOT make) - **The naming ruling** (Open questions) — before P1's contract text is written. - **P1's contract-document text** — the contract fixes vocabulary future resolvers implement against; a human reviews before it merges. - **P5's binding-write surface** — the slice writes a tracked team file; the propose→ratify loop puts a human on every declaration, and the slice ships only after that flow is reviewed. - **Org-rung entitlement declarations** — org-policy decisions, interviewed at setup time, never auto-written. - **Whether prerequisite verdicts should ever feed the security binding mechanically** — deferred with a trigger: revisit if a class's admission entry wants to key on a verdict; today the resolution narrows an existing enforcement input and adds none. #### Mechanical work - Commit per phase; surgical `git add` of named paths only; commit messages via stdin heredoc. - Each `plugins/**` phase verifies its version bump + CHANGELOG before opening its PR (the bump assertion pair, never `--check-bump` alone). - PR bodies: native closing keyword (or `No linked issue`) + non-empty `## Related`. ```` </details> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…2760) Closes #2699 ## Summary The topic-docs prune recovery pointer prescribed `?ref=<pruning-commit>^`, which is unrecoverable after this repository squash-merges and deletes the head branch. Amend the convention so the written form matches what still works, and stop presenting unreachable-object retention as a guarantee. ## Fix In `docs/conventions/topic-docs/README.md` (lifecycle steps 2 and 5): - Prescribe the Contents API form `?ref=<pre-prune-commit>` (no `^`). - State plainly that retention of unreachable objects is best-effort with no promised lifetime. - Require the PR body to name graduation targets (ADR / specs / tracker items) as the load-bearing record; the SHA pointer is convenience only. - Bump the convention changelog to 2.4.3 and align the worked-slice example. `scripts/check-contract-slice-prune.sh` is unchanged. ## Verification - `bash scripts/check-changelog-parity.sh --check-bump origin/main` — exit 0 (docs under `docs/conventions/` need no plugin bump). - `bash scripts/check-changelog-parity.sh --check` — exit 0. - `bash scripts/check-changelog-parity.sh --check-order` — exit 0. - Live instance from #2686 / PR #2686: - `gh api …/contents/…/PLAN.md?ref=5341117c` → size `67690` (prescribed form works while retention lasts). - Same path against the squash merge's parent (`c38fe716…`) → `404` (confirms `^` / parent-of-squash is not a recovery path). ## Related Refs #2686 (live instance that exposed the false pointer) --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
No linked issue
Summary
Answers "what can we do with the maintenance-routine pattern" for this marketplace, and lands the answer as durable artifacts rather than a document. Eight research lanes and three verification passes ran to completion; this PR graduates what they settled and prunes the working slice.
The research inverted the original framing three times, and the plan encodes the inverted shape:
Fix
ADR 0010 — merge findings across producers, and mark consumption explicitly. The correctness gate the whole effort turns on.
review:fanout's fix action consumes exactly one findings file and merges nothing (context/fix-pass-mode.md:3,7), which is safe only while fanout is the sole producer. The first detector of any kind makes producer #2, at which point the later timestamp silently wins and the earlier producer's findings are never applied — no error, no warning, run reports success. Three decisions, each with a rejected alternative that had a real case:--yesin a non-interactive session, so any bound anchored on it is a no-op on the path most runs take.Eight phase issues, each carrying its own ADR-0004 incumbent evidence inlined as
path:line— deliberately not a pointer into the gitignored.work/tree, which no delegated worker, fresh session, or second machine can read:feat(review): merge findings across producers (implements ADR 0010)docs(conventions): detector-findings owner doc (stub)feat(mutation-testing): persist survivors as a conforming findings filedocs(conventions): harden detector-findings with the rule-id to severity crosswalkfeat(autonomy): catalog rows for every routine class consideredfeat(autonomy): reviewer-burden promotion term, recorded as deferredfeat(testing): can't-fail test audit plus fail-closed--checkmodespike: per-repo routine capability detection, promoted to its own topicThe contract slice is pruned in this PR's final commit, per
docs/conventions/topic-docs/README.md:43. Recover the full PLAN.md and the design resolution from the commit before the prune:Verification
bash scripts/check-contract-slice-prune.sh --check-diff origin/main— exits 1 before the prune commit (it named both slice paths), exits 0 after. That transition is the point of the final commit.npx markdownlint-cli2— 0 issues across ADR 0010, PLAN.md, and design-resolution.md.awk '/^### Phase [0-9]/{p=$0;c[p]=0} /Sanity Check/{if(p!="")c[p]++} END{for (k in c) if (c[k]==0) print k}'printed nothing — all ten numbered phases carried at least one mechanically verifiable check.What the review passes caught
Both CRITICALs from the adversarial pass landed on the coexistence design and both held:
fix-pass-mode.md:76— "Interactive and headless-stop paths write no record". The solo shape is the interactive path, so the merge set would have grown without limit, re-injecting findings that:95's required post-fix re-review had already resolved. The draft would have regressed a documented loop while claiming to close a correctness gate.findings-normalization.md:77places dedup at "Stage 3 Sonnet (semantic merge)";:66orders "Minimize FALSE-MERGE over FALSE-SPLIT — a false merge silently drops a real issue". The fix action runs no LLM stage.The mechanics pass then executed rather than argued its CRITICAL:
check-contract-slice-prune.sh --check-diff origin/mainexits 1 on this branch, which is what produced the prune commit and the branch-shape decision. It also repaired five sanity checks that could not pass as written — a barecheck-changelog-parity.shexits 2 on usage; agrep -c … returns 0check inverts its own exit code, so the success case failed and a wrongly staged file passed; a forbidden-metric grep passed vacuously on zero matches; two checks named a state with no command.Other corrections worth recording:
docs/PLUGIN-PHILOSOPHY.md:471is a deadline ("before a second plugin adopts it"), not a licence to author an owner doc late — and the pilot phase is itself the second adopter, hence the stub-then-harden split.routines.md:192,195already carrydead-code-sweepandcoverage-mutation-watch, so the catalog phase gained an existing-row sweep as its first work item. The new skill takes theauditleaf and joins the registered owner set rather than pickingscanto sidestep the argument the registry asks for. And the routine-delivery gap is bound toliveness-assertion's two-limb Core contract instead of being deferred by scope, which was not one of the permitted answers.The locked Brief (the contract this plan was built to)
Brief
TLDR
Generalize the maintenance-routine pattern reported by @bcherny (2026-08-13) into tool-, org-, and
product-agnostic capability for this marketplace: detectors that emit conforming findings, plus
catalog rows governing them, plus the substrate both need. Not a port of his eleven routines,
and not a new plugin.
The research inverted the original framing three times, and the Brief encodes the inverted shape:
What is missing, class after class, is a detector to feed it.
large-N regression, randomized trial — find artifact quality weakly-to-not coupled to acceptance.
The verification contract is justified on defect escape and reviewer burden, never on
acceptance.
wins come from policy and mechanism, not from better per-instance agent judgement.
Goal
Ship, in dependency order:
reasoning is recorded rather than re-litigated.
Constraints
Binding repository rules (verified,
path:linein the research record):reference/edit plus aCHANGELOG.mdentry plus a version bump. Not a new catalog, not a new skill, not a new plugin. This closed
the original "where does it land" question; it is not reopened here.
skill covers it, with
path:lineevidence. Satisfied for all eight classes byresearch/V1-coverage-negatives.md; each issue carries its own evidence.obligation a surface should satisfy, anchored to nothing, does not become a row however well
sourced.
metadata.workflow-stagerequired; regeneratedocs/CATALOG.mdanddocs/SKILL-CHEAT-SHEET.md;SKILL.md under 500 lines; evals required for any new skill; only
docs/topics/isdocs-only-allowlisted, so anything under
plugins/**runs the full CI suite.Product constraints (verified at primary,
code.claude.com/docs/en/routines.md, 2026-08-14):account. Claude Tag (the Slack surface the source used) is Team/Enterprise-only and is out of
reach; only that delivery surface is unavailable, not the capability.
one-off runs do not, which is the pilot lever.
Containment is repo selection, environment, connector list, and the
claude/-branch push rule..claude/loads; user-scope~/.claudedoes not.pluginConfigsis ignored at projectscope by design, so any plugin taking
userConfighas no cloud-run way to receive values.logs, never statuses.
Evidentiary constraint: the source is a single self-report with zero independent corroboration
and no published methodology. Nothing in this plan may cite 388/180 as evidence of efficacy.
Acceptance criteria
Per-unit close-out loop — one class at a time: incumbent evidence recorded → row derived through the
catalog's own mapping rules → detector or deferral shipped → CI green → CHANGELOG + version bump in
the same PR. A class is closed when its row exists with a derived guardrail class and either a
shipped detector or a
join:trigger naming what would unblock it.exactly one file and merges nothing; two producers in one branch directory means the later
timestamp silently wins. Green run, hidden findings. This is a correctness gate, not a nicety.
crosswalk row exists for a deterministic surface today; that is contract work, not a detail.
org-scale and explicitly deferred at solo volume.
Captured assumptions
conforming file from a non-fanout producer passed the fix action's locator, frontmatter gate,
exact-branch check, and table parse, including the cell-escaping rule. Probe deleted afterward —
while it existed it was the newest file in that directory and would have shadowed a real review.
"wherever the judgment is mechanical", and fanout can dispatch agents only — which is why
mutation-testing:audit, the best deterministic detector in the fleet, reaches no relay today.The class-level trust path (the operative definition)
An instance-level path asks a judge to evaluate each change on its merits. A class-level path
decides once, for a category, what condition makes any member acceptable — so the per-instance
question collapses from a judgement to a check. The mechanism: per-instance persuasion is subject to
habituation; a standing class rule is not.
"Dead-code removal where the code is provably unreachable" is a class. "Code quality improvements"
is not. If deciding membership needs the judgement you were eliminating, it is an instance-level path
wearing a class-level label.
Solo shape: the gate without the statistics — narrow class, machine-checkable gate, run it
before the PR opens, human on the merge. The earned auto-merge tier is deferred with a trigger.
Scope — tiers
Tier 0 — substrate. Blocks everything.
Tier 1 — build. Formal-logic modeling (decision tables + property-based testing; strongest
evidence, and its mechanical artifacts are the verification payload) · useless-test repair
queue (genuinely uncovered; the fleet names the capability it lacks) · layering enforcement, inform-human posture (most mechanical once rules exist; propose-and-baseline, never impose-and-fail).
Tier 2 — rows now, build later. Dead code, both postures, with a 30-90 day window floor and
staged quarantine — never the source's one-day window · clone detection + trend gating (the unify
decision has no automation precedent in twenty years) · stale-flag removal (strong prior art;
consumer-facing, no local surface).
Tier 3 — rows recording why not. Logic simplification above expression level (no published
effectiveness evidence; excluded by name in
tidyings.md) · abstraction flattening (no validateddetector exists, and the fault data runs backwards — Speculative Generality and Middle Man sometimes
reduce faults) · ant-only shipper (the decision is a human product call) · GUI crash fuzzing (no
local surface; 36.6% crash-replay reproducibility).
Out of scope
outcomes with a human gate; the famous citations are within-episode and do not persist. The
existing promotion apparatus is the better-grounded shape and already avoids the merge-rate
confound by keying on completions, gate passes, and reverts.
layer above any stored prompt.
Deferred questions
#2660 (
needs-human).Two official pages contradict each other on whether repo-declared marketplace plugins install;
workspace trust for a cloud clone is undocumented, and if untrusted the declaration is ignored
silently; private-marketplace auth in cloud is undocumented. One probe settles all three, and
it cannot run unattended — browser selection, account mutation, and metered usage all require the
human. Tier 0's fourth item is blocked on it; the documented alternative (components committed
directly to
.claude/) is the fallback and needs no marketplace fetch, trust step, or credentials./planning:plan) — add a reviewer-burden term to the existing promotionpredicate, and keep any future tuner's signal set disjoint from promotion evidence. Composition
hazard if not: a tuner could raise the metric that promotes the cell that reduces scrutiny of the
tuner's own output.
join: proven recurring manual patternstays our-own-proven. Thesource's run is named-product evidence and belongs in the routine-catalog research record, not the
non-normative precedent-pointers section, whose own scope line routes it elsewhere.
claude.ai/code/routines; published figures trace to a stale April blog post. Needs anauthenticated session.
The plan's shape
Ten phases, sequential. Blast radius HIGH. Full bodies, work items, and sanity checks live in the eight issues above; the spine is:
Three ordering constraints, none obvious:
mainbefore P3 — a plugin cannot cite a repo-relativedocs/conventions/path, because it installs standalone. The established form is a raw URL tomain, which only resolves once the convention is merged.Approved reductions against the Brief
Both were surfaced as scope cuts rather than absorbed, and accepted:
Follow-up outside this PR
AGENTS.mdis 28 lines at HEAD and no longer carries the exec-bit or Windows-filemode guidance — commit e22190e's managed sync deleted the 34 lines 7c2a9b3 had added four commits earlier. It is a managed materialization, so a local patch is removed by the next sync; the fix belongs upstream inmelodic-software/standards. The exec-bit CI gate still runs and is ungated by the docs-only allowlist, so the constraint is live while its documentation is not.Related
needs-humanspike); Tier 0's fourth item is blocked on it, and the documented fallback proceeds regardlessdocs/adr/0010-merge-findings-across-producers-and-mark-consumption-explicitly.md