Skip to content

docs: generalize the maintenance-routine pattern — ADR 0010, eight phase issues, slice pruned - #2686

Merged
kyle-sexton merged 9 commits into
mainfrom
feat/boris-routines-adoption
Aug 15, 2026
Merged

docs: generalize the maintenance-routine pattern — ADR 0010, eight phase issues, slice pruned#2686
kyle-sexton merged 9 commits into
mainfrom
feat/boris-routines-adoption

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

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:

  1. The apply machinery already exists and is reachable by file format alone. What is missing, class after class, is a detector to feed it. Validated empirically, not inferred: a hand-written conforming file from a non-fanout producer passed the fix action's locator, frontmatter gate, exact-branch check, and table parse.
  2. Merge rate cannot justify any of this. Peer-reviewed observational work, a large-N regression, and a randomized trial all find artifact quality weakly-to-not coupled to acceptance. The verification contract is justified on defect escape and reviewer burden, never acceptance. Nothing here cites the source's 388/180 figures as evidence of efficacy — a single self-report with no independent corroboration and no published methodology.
  3. Class-level beats instance-level. Three lanes converged from different literatures: durable wins come from policy and mechanism, not from better per-instance agent judgement.

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:

  • 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 ±3-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.
  • The applied-plan record becomes a consumption ledger written on every apply path. 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.

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:

Issue Phase
#2678 feat(review): merge findings across producers (implements ADR 0010)
#2679 docs(conventions): detector-findings owner doc (stub)
#2680 feat(mutation-testing): persist survivors as a conforming findings file
#2681 docs(conventions): harden detector-findings with the rule-id to severity crosswalk
#2682 feat(autonomy): catalog rows for every routine class considered
#2683 feat(autonomy): reviewer-burden promotion term, recorded as deferred
#2684 feat(testing): can't-fail test audit plus fail-closed --check mode
#2685 spike: per-repo routine capability detection, promoted to its own topic

The 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:

git show 5341117c:docs/topics/boris-routines-adoption/PLAN.md
git show 5341117c:docs/topics/boris-routines-adoption/design/design-resolution.md

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.
  • Per-phase sanity-check invariant, measured rather than asserted: 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.
  • Two independent fresh-context review passes ran against the plan with the authoring rationale withheld: an adversarial pass (2 CRITICAL, 8 HIGH) and a mechanics pass (1 CRITICAL, 7 IMPORTANT). Every finding acted on was re-verified against the file before the plan was changed. Details in the collapsed section below.
What the review passes caught

Both CRITICALs from the adversarial pass landed on the coexistence design and both held:

  • The staleness bound could not exist on the path the plan runs. 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.
  • The dedup key was not mechanically available. findings-normalization.md:77 places dedup at "Stage 3 Sonnet (semantic merge)"; :66 orders "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/main exits 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 bare check-changelog-parity.sh exits 2 on usage; a grep -c … returns 0 check 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:471 is 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,195 already carry dead-code-sweep and coverage-mutation-watch, so the catalog phase gained an existing-row sweep as its first work item. The new skill takes the audit leaf and joins the registered owner set rather than picking scan to sidestep the argument the registry asks for. And the routine-delivery gap is bound to liveness-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:

  1. The apply machinery already exists and is reachable by file format alone (validated, below).
    What is missing, class after class, is a detector to feed it.
  2. Merge rate cannot justify any of this. Three independent lines — peer-reviewed observational,
    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.
  3. Class-level beats instance-level. Three lanes converged from different literatures: durable
    wins come from policy and mechanism, not from better per-instance agent judgement.

Goal

Ship, in dependency order:

  • Tier 0 substrate — the four items below, which every candidate class depends on.
  • Tier 1 detectors — three classes with the strongest evidence and a real local surface.
  • Catalog rows for every class considered, including the ones deliberately not built, so the
    reasoning is recorded rather than re-litigated.

Constraints

Binding repository rules (verified, path:line in the research record):

  • ADR 0005 — a new class extends the existing catalog: a reference/ edit plus a CHANGELOG.md
    entry 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.
  • ADR 0004 incumbent-first gate is binding — no remediation ships until it proves no existing
    skill covers it, with path:line evidence. Satisfied for all eight classes by
    research/V1-coverage-negatives.md; each issue carries its own evidence.
  • ADR 0008 — a row is admitted only when its observable is anchored to text that is present. An
    obligation a surface should satisfy, anchored to nothing, does not become a row however well
    sourced.
  • Version bump and matching CHANGELOG entry in the same PR (CI-enforced, zero exemptions);
    metadata.workflow-stage required; regenerate docs/CATALOG.md and docs/SKILL-CHEAT-SHEET.md;
    SKILL.md under 500 lines; evals required for any new skill; only docs/topics/ is
    docs-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):

  • Routines are available on Pro, Max, Team, and Enterprise — the mechanism is reachable on this
    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.
  • Minimum schedule interval is one hour. Runs count against a per-account daily allowance;
    one-off runs do not, which is the pilot lever.
  • No permission containment during a run — "no permission-mode picker and no approval prompts".
    Containment is repo selection, environment, connector list, and the claude/-branch push rule.
  • Repo .claude/ loads; user-scope ~/.claude does not. pluginConfigs is ignored at project
    scope by design, so any plugin taking userConfig has no cloud-run way to receive values.
  • Green status ≠ success — "It does not mean the task in your prompt succeeded." Efficacy reads
    logs, never statuses.
  • Workflows do not travel into scheduled runs; custom slash commands do.

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.

  1. Findings-file coexistence is settled before a second producer ships. The fix action consumes
    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.
  2. Every detector emits machine-computed severity, not prose routed through an LLM crosswalk. No
    crosswalk row exists for a deterministic surface today; that is contract work, not a detail.
  3. Every class-level gate satisfies items 1-2 of the trust-path definition (below). Items 3-5 are
    org-scale and explicitly deferred at solo volume.
  4. No acceptance-rate metric anywhere — not as a promotion input, not as an efficacy signal.
  5. Each shipped detector carries evals, per the CI gate.

Captured assumptions

  • The format-only path stays supported. Validated 2026-08-14, not assumed: a hand-written
    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.
  • Detectors are scripts unless a named agent is earned. The repo's own philosophy prefers one script
    "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.
  • Catalog rows derive their guardrail class through the existing mapping rules, never by hand.

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.

# Requirement Portable?
1 Class definition narrow enough that membership is decidable without judgement yes
2 Machine-checkable gate that fails closed yes
3 A denominator — enough instances to compute a rate org-scale
4 An outcome signal that is not the merge decision org-scale
5 A lookback window and a demotion rule org-scale

"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.

Item Why
Findings-file coexistence Silent-shadowing correctness bug the moment a second producer exists
Detector contract Machine-computed severity, rule/threshold vocabulary, suppression; owner doc must precede the second adopter
Per-repo capability detection The agnostic core: which classes bind, resolved from repo state (build files, language, test framework, flag system, architecture config, MCP servers, CLI tools)
Repo-scope plugin declaration User-scope does not load in cloud; gated on the cloud probe (below)

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 validated
detector 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

  • A new plugin, a new catalog, or a parallel governance surface (ADR 0005).
  • A self-tuning routine class. Across 22 verified papers, none tunes from deployed production
    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.
  • Auto-merge without human review at solo volume — requirements 3-5 above are unmeetable here.
  • Porting the source's prompts. They are a meta-prompt (instructions to create routines), one
    layer above any stored prompt.

Deferred questions

  • Q12 (arbiter: USER-RESERVED) — repo-scope plugin declaration in cloud. Filed as
    #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.
  • Q4 follow-on (arbiter: /planning:plan) — add a reviewer-burden term to the existing promotion
    predicate, 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.
  • Q2 (resolved, recorded)join: proven recurring manual pattern stays our-own-proven. The
    source'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.
  • Live daily run-cap numbers (arbiter: USER-RESERVED) — the docs direct readers to
    claude.ai/code/routines; published figures trace to a stale April blog post. Needs an
    authenticated 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:

P1 coexistence ──> P2 convention stub ──> P3 Pattern-C pilot ──> P4 harden ──> P7 detector
                        (must merge to main)                                      ^
P5 catalog rows ──> P6 predicate term ────────────────────────────────────────────┘

P8 capability detection ── promoted to its own topic
P9 repo-scope declaration ── blocked on #2660
P10 graduate + prune ── this PR

Three ordering constraints, none obvious:

  • P1 before any detector — fanout is producer chore: initialize marketplace scaffold #1, so the first detector of any kind makes producer fix: correct plugin-system inaccuracies and harden references #2.
  • P2 before P3 — a new cross-plugin convention lands in an owner doc before a second plugin adopts it, and P3 is that second adopter.
  • P2 merged to main before P3 — a plugin cannot cite a repo-relative docs/conventions/ path, because it installs standalone. The established form is a raw URL to main, which only resolves once the convention is merged.

Approved reductions against the Brief

Both were surfaced as scope cuts rather than absorbed, and accepted:

  • Tier 1: three detectors to one. Ships can't-fail tests (feat(testing): can't-fail test audit — detector plus fail-closed --check mode #2684); formal-logic modeling and layering enforcement become catalog rows with named triggers.
  • Detection, not repair. The Brief names a repair queue; a judgment-shaped finding is surfaced rather than auto-applied, so the queue-and-apply half needs its own decision.
  • One deviation: the Brief's per-unit close-out loop is batched stage-at-a-time, because the substrate phases are shared by every class and a per-class loop would re-pay them N times.

Follow-up outside this PR

AGENTS.md is 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 in melodic-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

kyle-sexton and others added 7 commits August 14, 2026 22:20
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>
@cursor

cursor Bot commented Aug 15, 2026

Copy link
Copy Markdown

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

claude Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 58s —— View job


Code review — /review:code-review

  • Gather context (PR body, gh pr diff, skill instructions)
  • Read changed file(s)
  • Apply code-review criteria (correctness, CLAUDE.md alignment, maintainability)
  • Post findings

Scope: this PR's only change is docs/adr/0010-merge-findings-across-producers-and-mark-consumption-explicitly.md (+99/-0, new file). No code paths are touched in this PR — the ADR is a design record; its implementation is deferred to issue #2678.

What I checked

Since the diff is pure prose with no logic to exercise, "correctness" here means: do the ~10 path:line evidence citations the ADR's argument depends on actually say what it claims, and is the decision internally consistent. I independently re-read every cited source line rather than trusting the PR body's claimed verification:

I also confirmed ADR numbering has no gap (0001–0010, all present) and the frontmatter/heading shape matches the existing ADRs (e.g. 0009) exactly.

Findings

None at the code-review high-signal bar. The document's citations are accurate, the three decisions each carry a rejected alternative with a real reason (not a strawman), and the "Consequences" section's claims (byte-for-byte-unchanged single-producer case vs. "interactive applies now write a record") are two distinct, non-contradictory properties rather than an inconsistency once read against Decision 3's actual scope.

I did not independently run markdownlint-cli2 (no shell access to npx in this sandbox) — the PR's markdownlint claim is author-reported and unverified by me.

@github-actions

Copy link
Copy Markdown

Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread docs/topics/boris-routines-adoption/PLAN.md Outdated
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>
@github-actions github-actions Bot deleted a comment from claude Bot Aug 15, 2026
@claude

claude Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error after 0s —— View job


I'll analyze this and get back to you.

@github-actions

Copy link
Copy Markdown

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 pull_request triggers include synchronize (the canonical caller omits it).
An automatic retry may already have run — it is skipped when a partial review could duplicate comments, or when the failure class needs an operator (auth).

@kyle-sexton
kyle-sexton merged commit aefbf88 into main Aug 15, 2026
40 checks passed
@kyle-sexton
kyle-sexton deleted the feat/boris-routines-adoption branch August 15, 2026 07:36
@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Correction — the recovery pointer in this PR's body does not survive the merge

Raised by an independent review of the ADR edit, verified after merge. Recording it here rather than
silently editing the body, because the body is the artifact that was wrong.

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.md

Why that no longer works. This repository squash-merges and deletes the head branch on merge.
5341117c was a commit on feat/boris-routines-adoption, which no longer exists —
gh api repos/melodic-software/claude-code-plugins/branches/feat/boris-routines-adoption returns
404 Branch not found. A squash merge does not carry branch commits into main's history, so a
fresh clone has never fetched that object and git show fails against it. The command only works in
a checkout that fetched the branch before it was deleted — which is to say, on the machine that
wrote it.

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 -d

Verified after the merge: the first returns a 67,690-byte blob. Treat this as convenience, not a
guarantee
— retention of an unreachable object is a GitHub implementation detail with no promised
lifetime, and nothing in this repository keeps it alive.

The durable record was never those two files, and that was the design. The whole point of
graduating before pruning is that everything load-bearing left the slice before it was deleted:

So the correct reading of the prune is that the PLAN was a working artifact whose durable content had
already been moved. The pointer was offered as a convenience and was written in a form that outlives
neither the branch nor a fresh clone.

Convention follow-up. docs/conventions/topic-docs/README.md prescribes naming the pruning
commit and reading its parent (?ref=<pruning-commit>^). That form has the same defect under
squash-merge for the same reason: the pruning commit is a branch commit too, and the squash commit on
main has no parent containing the slice. A slice pruned on a squash-merging repository is
recoverable only through the API-by-SHA form above, or not at all once the object is collected.
Worth fixing in the convention rather than repeating per slice — filed separately rather than
patched into this merged PR.

kyle-sexton added a commit that referenced this pull request Aug 15, 2026
…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>
kyle-sexton added a commit that referenced this pull request Aug 15, 2026
…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>
kyle-sexton added a commit that referenced this pull request Aug 15, 2026
…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>
kyle-sexton added a commit that referenced this pull request Aug 15, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant