Prompt quality of the nine plugins — what a calibrated review measured
Run 2026-09-08T17:57:42Z · rubric 6a92f9a52857 · generated by what is now plugins/nacharbeit/scripts/build_report.py from analysis/nacharbeit/ (do not edit by hand).
Every judgement finding below was produced by a sonnet finder that first had to pass a planted-defect calibration, then survived an adversarial sonnet refuter that re-opened the file and searched for the quote. Mechanical findings come from what is now plugins/nacharbeit/scripts/nacharbeit_lint.py (25 regex/YAML rules at the time of this run, each sabotage-tested). The rule ids resolve in the nacharbeit rubric, plugins/nacharbeit/references/rubric.md.
How to read this report
Start with section 1. It says how much to trust everything below it: the finder's recall on planted defects, the sealed hold-out it never trained on, and how accurately the router proxy reproduced the known-answer prompts.
Section 2 is the verdict per plugin, one row each, written by opus from the verified findings. Read the summary cell before the numbers.
Section 3 is cannibalization. Every pair there was picked together by a haiku router for the same user prompt, then judged. A pair marked intended-handoff needs no change.
Section 4 lists every verified judgement finding by severity, with the quote the finder cited and the fix the impact lens approved or revised. A warning sign on a fix means applying it as written would make something else worse.
Section 5 is the mechanical lint, grouped by rule. These are the findings a script can prove and a script can fix.
Sections 6 and 7 are the backlog by model tier and the cross-plugin synthesis. The ten actions at the end of section 7 are the ordered starting list; the appendix shows which batch and angle each finding came from, so silence in a cell can be read for what it is.
1. The instrument, before the verdict
| measure | value | meaning |
|---|---|---|
| calibration round 1 | recall 0.76, weakest angle 0.40, 0 false positive(s) on clean files | target recall ≥ 0.8, every angle ≥ 0.6 |
| calibration round 2 | recall 0.80, weakest angle 0.60, 1 false positive(s) on clean files | target recall ≥ 0.8, every angle ≥ 0.6 |
| sealed hold-out recall | 0.79 | measured once on fixtures the rewriter never saw; floor 0.7 |
| raw → verified findings | 464 → 216 (+28 from the critic round) | the refuter's kill rate |
| router proxy accuracy | 0.97 top-1 on 70 known-answer prompts (top-3 68) | router proxy accurate; collisions are measured |
The finder prompt was rewritten 1 time(s) during calibration:
- after round 1: Restructured the prompt from one flat paragraph of angle hints into an explicit order of operations, the eight (sharpened) rules of engagement, per-angle reading strategies, and a closing self-check, because every missed class traced to a check the reviewer never performed rather than a judgement it got wrong. The single largest addition is a mandatory frontmatter-key pass: the old prompt implicitly treated
descriptionas the only frontmatter of interest, so obligations carried by load/scope/execution keys were invisible — the new step 2 makes every key in scope and pairs it with a procedure-angle rule mapping a forking execution context to the requirement that the body contain an actionable task. Four further generalisations target the other misses without inviting noise, each phrased as a discriminating test rather than a keyword list: step-logic now defines steps and loops semantically (prose "then … then" chains count; "keep going / after every X / until nothing new" are loops) and states that convergence-only stop conditions still fail absent a bound and an escalation path; clarity gets a delete-test for paragraphs that define things a competent model already knows, plus a sharpened example criterion that keeps the rubric's template mis-flag intact by making the finding turn on whether a placeholder names a vocabulary the file never enumerates and whether a filled instance and the empty branch are shown; procedure now states that a description built from quoted user phrasings satisfies only the "when" half and overfits literal queries (with "report it once" to prevent it double-firing across rules), and adds a progressive-disclosure test asking whether body-resident bulk is really the standing instruction, especially when a reference file already covers the same material. Since the calibration set produced no false positives, no rule needed a new negative criterion, so precision is protected instead by generic guards — quote-before-claim ordering, one defect one rule, the standing known-mis-flag exclusion, and an explicit "never manufacture a finding to fill an angle" — and by phrasing each new cue as a condition that clean files fail to meet. Length is 799 words.
Sealed defects the frozen finder still missed (the report's blind spots): test/plugins/fixtures/nacharbeit/sealed-2/skills/quill-lint/SKILL.md Q-OTHER-RIGIDITY, test/plugins/fixtures/nacharbeit/sealed-2/skills/quill-lint/SKILL.md Q-OTHER-SCRIPT-INTENT, test/plugins/fixtures/nacharbeit/sealed-2/skills/prose-check/SKILL.md Q-CANN-INTERNAL.
2. Per-plugin verdicts
| plugin | verdict | verified | lint | summary |
|---|---|---|---|---|
| andon | needs-work | 21 | 10 | Six majors, all local fixes; nothing needs a component redesigned or merged. The three most consequential: (1) plugins/andon/references/okf-ledger-schema.md carries two false contracts — it places ANDON_BOARD.md in output_dir when andon-status only ever writes <ledger_dir>/ANDON_BOARD.html, and it claims tags derive from first-class fields while lane:fast|slow has no documented source (it is evidence-doc-only in andon_core.py). (2) plugins/andon/skills/andon-preflight/SKILL.md promises no writes beyond a ledger-writability test, then Step 3 writes PREFLIGHT.md and preflight_summary.json — and only the markdown write is gated by validate-write-path, with no stated behaviour on failure. (3) plugins/andon/skills/andon-loop/SKILL.md advertises a stage-or-gap filter argument no phase reads, and Phase 6's check-convergence call consumes a gaps-closed tally and a wire-status array that no earlier phase is told to build. Also major: andon-adjudicator.md's only non-overridable stop depends on Tier 1 structural evidence none of its three named feeders produce. Done well: routing is clean — every measured collision is an intended handoff, an unresolvable bare-status/preflight prompt, or (andon-preflight vs befund-preflight) a defect on the other side. |
| cli-scaffold | needs-work | 19 | 3 | Three problems dominate. (1) agents/cli-scaffold-verifier.md is the weakest component: its description is 1361 chars against the 1024 cap and embeds <example> XML tags, and its body leaves three control-flow holes — exit code 2 is listed but never handled, the MAX_FIX_ITERATIONS HALT is never reported to the caller, and step 3 reads a report JSON whose path is only documented for the exit-1 branch. (2) All three paradigm skills (skills/cli-scaffold-{compiled,interpreted,shell}/SKILL.md) treat "the resolved language/dialect" as given while their own descriptions invite direct invocation, with no stated precondition or refusal path. (3) skills/scaffold-cli/SKILL.md step 1 loops AMBIGUOUS back into itself with no retry cap or escalation. Correction to one finding: skills/cli-architecture/SKILL.md's "12" is not an off-by-one — scripts/constants.py asserts LANGUAGE_COUNT = 12 and comments that POSIX sh "does not add to LANGUAGE_COUNT"; renumbering to 13 would contradict enforced code, so the fix is wording, not arithmetic. Done well: language resolution is delegated to scripts/lang_router.py rather than guessed, and doctrine lives in one skill with an explicit no-duplication rule. |
| passung | needs-work | 33 | 1 | Fifteen majors, all local fixes; no component needs redesign, and one item needs a human decision rather than a rewrite. Most consequential: (1) the injection-reporting contract is dead in all three workflows — scan.js's VERDICT_SCHEMA, verify.js's CHECK_SCHEMA/RECHECK_SCHEMA and canonize.js's PANEL_SCHEMA/SITES_SCHEMA carry no field for the injectionSuspects the shared UNTRUSTED block tells agents to fill, and verify.js:145 still holds a dead for (const s of []) loop — the exact silent-no-op shape this repo has been burned by. (2) commands/passung-align.md is the weakest command file: its no-approved-brief gate names no remedial command, the pilot has no branch for failing tests before PLAYBOOK.md is written and the fan-out begins, the required per-unit deps has no stated source, and [dimension] is never bound to $2. (3) commands/passung-verify.md passes no module list to a workflow described as fanning out per aligned module, and never says to read ALIGN_NOTES.md. Done well: where gates exist they are specific and enforceable — align-executor.md's missing-PLAYBOOK STOP, align.md's 2/3 circuit breaker, verify.md's two-cause blocker — and passung-scan.md's description is already well-scoped with explicit exclusions. |
| zirkel | needs-work | 43 | 2 | Three problems dominate. (1) plugins/zirkel/workflows/solve.js reads priorClarify, priorExplore, requestedBranches and maxBranchCount off the raw args instead of the parsedArgs normalization the same file defines — when args arrives as a JSON string (the case normalizeArgs exists for) prior-run reuse and branch-count overrides silently vanish; the same file also pushes 'Revise' into phases_run before the criteria>=3 gate, so the returned contract claims a phase that never ran. (2) solve.js's Execute stages self-report a mode named 'reason-verify' while the script makes one generic agent() call per stage, never dispatching reason-verify.js's 3-attempt majority vote — the mode names are undefined operationally and promise rigor that is not performed. (3) plugins/zirkel/skills/zirkel-summarize-trace/SKILL.md has no Input section naming where the zirkel-solve run data comes from, over-promises "exactly 7 sections" its own body makes conditional, and shows its report and score table only as ellipses. Missing gates on parallel() results (explore-branches.js, optimize-instruction.js) and a contradictory risk polarity in explore-branches.js follow. Done well: nearly every phase validates its JSON through a named guard, and sibling agents mostly justify their tool grants in-body. |
| zeugnis | needs-work | 13 | 0 | needs-work: four majors, all local edits, no component needs redesign. (1) plugins/zeugnis/skills/zeugnis-cycle/SKILL.md:46 treats every non-zero exit of cycle_engine.py plan-next-pass as "pass cap reached", folding bad repo_root, corrupted ledger and invalid --mode into the same benign wrap-up path. (2) The same file at line 98 has no outcome value for a propose-mode finding that was only described, so an all-propose pass can read as converged on its first pass regardless of how many findings were raised. (3) plugins/zeugnis/skills/zeugnis-code-change/SKILL.md:35 promises a uniformly single-check, un-verified pass but invokes dependency_audit.py with its mandatory verification on, so one of the four domains has rigor the framing denies. Two measured routing collisions are description defects on zeugnis's side: zeugnis-contract-drift's "docstrings" reads as prose docs against befund-docs-drift, and zeugnis-assertion-audit's trailing "Verification is mandatory" clause pulls adversarial verify-this-change prompts away from andon-verify. Done well: four of five agent files ship concrete fenced output examples, most descriptions are already plugin-scoped, and mechanical lint is clean. Caution: three of the four majors shipped a factually wrong first suggestion (fixMakesWorse) — apply the revised fix, never the original. |
| cupertino | needs-work | 40 | 0 | Every major here is a local fix — added sentences, description clauses, workflow guards — so nothing needs redesign, but there are many of them. The three most consequential: (1) plugins/cupertino/workflows/handbook-fix.js accepts its findings arg unvalidated — a missing arg looks identical to "zero mechanical findings", .rule is interpolated but enforced by no schema (yielding RULE: undefined and file::undefined clusters), and findings that never passed handbook-check's re-verification are remediated as if confirmed. (2) State flags that gate real behaviour can stick: plugins/cupertino/skills/cupertino-handbook-check/SKILL.md and cupertino-handbook-fix/SKILL.md only clear handbook-check-active / handbook-fix-active on the success path, and cupertino-review/SKILL.md clears flags after a render step with no failure gate, discarding the only copy of stage content. (3) plugins/cupertino/skills/cupertino-council/SKILL.md never discloses the PreToolUse backwards gate that cupertino-backwards says blocks it, and its description carries no negative trigger — the measured collision with befund-ui-audit lands there too. Done well: the handbook agents' fenced output schemas and cupertino-review's explicit skip-and-persist handling for stages 5-6; mechanical lint is clean. |
| lehre | needs-work | 24 | 1 | Ten majors, all local edits — no component needs redesign or merging, and the four measured routing collisions all resolve by appending negative triggers rather than rescoping. Most consequential. (1) The human-approval safety model has a hole at both ends: the field recording partial phase approval is never defined (plugins/lehre/skills/lehre-brief/SKILL.md:90 shows only status: awaiting-approval, while lehre-conform reads an unnamed "phase is approved" field), and plugins/lehre/agents/conformance-remediator.md:3 declares two dispatch preconditions but its Rules only ever re-check verification, never brief approval. (2) plugins/lehre/agents/pattern-investigator.md:22 tells the agent to prefer serena's find_symbol/find_referencing_symbols while frontmatter grants only Read, Glob, Grep, Bash — silent grep fallback in a portable agent. (3) Four gates name no failure path or destination: lehre-codify:41 and :63, lehre-decompose:34, lehre-pin:91. Done well: every skill and agent ships a fenced worked output example, and lehre-brief's "Refuse without inputs" step plus lehre-validate's step-6 fail-gate are the in-repo models the weaker gates should copy. |
| befund | needs-work | 51 | 16 | Three problems dominate. (1) Artifact contracts that no step defines: skills/befund-extract-rules/SKILL.md resolves output paths for DATA_OBJECTS.md and business_rules.json that nothing in the pipeline produces or validates (human-tier), and skills/befund-stage-map/SKILL.md calls stage_map.json optional while skills/befund-transform-brief/SKILL.md Step 7 assumes it exists, with stage_map_summary.json never described at all. (2) Missing absent-input branches in skills/befund-transform-brief/SKILL.md: arch_health_summary.json and business_rules_summary.json are read as decision sources with no stated behaviour when the producing skill never ran, unlike the explicit stage_graph.json gate. (3) Tool grants that contradict stated roles: five read-only agents (convention-auditor, business-rules-miner, arch-health-auditor, idiom-auditor, ui-auditor) hold Bash they never use, while agents/transform-executor.md is told to delete and move files with no tool that can. Separately, all 16 skill descriptions are when-only and carry no negative scope, which is what the measured collisions with cupertino, zeugnis, lehre and andon all trace back to. Done well: gates fail closed with named CLI preconditions, and most agents already show fenced output instances. |
3. Cannibalization — measured by routing, judged by opus
router proxy accurate; collisions are measured. Pairs below were picked together by a haiku router for the ambiguous prompts in what is now plugins/nacharbeit/scripts/ambiguous-prompts.json.
| pair | prompts | verdict | winner | rationale | tier |
|---|---|---|---|---|---|
cupertino-handbook-check ↔ befund-lint-audit | 2 | add-negative-trigger | befund-lint-audit | No handoff edge connects these two — the graph shows two closed, disjoint loops (cupertino-handbook-draft/check/fix/apply + handbook-drift-auditor; befund-autopilot/lint-audit/convention-auditor/idiom-fix), so this is not an intended handoff. Both are legitimate and structurally distinct: reading the SKILL.md bodies confirms the only real discriminator is which written artifact holds the rules. cupertino-handbook-check requires .cupertino/<domain>-handbook.md and its Step 1 bails to cupertino-handbook-draft when that file is absent; befund-lint-audit reads .claude/house-rules.md with a labelled CLAUDE.md fallback, caps dispatch at lint_max_rules, and writes LINT_AUDIT.md for autopilot's CHECK phase. Different inputs, different outputs, different pipelines — so not a merge, and not a rescope either, since each description already names its own source document. The collision is purely in the generic quoted triggers: cupertino literally claims "check this against our standards" while befund claims "check our conventions", and neither disclaims the other, so an artifact-blind prompt hits both. Fix is to disambiguate by artifact rather than by phrasing. befund-lint-audit wins these two prompts: "our standards" and "the conventions we wrote down" name no handbook, and CLAUDE.md/house-rules.md is the artifact that exists in essentially every repo, whereas a cupertino handbook only exists after a deliberate draft pass. Tier is sonnet rather than haiku because cupertino's trigger list is edited (the ambiguous "check this against our standards" phrase is removed, not just supplemented), not merely appended to. No pipeline order changes. | sonnet |
cupertino-handbook-draft ↔ lehre-codify | 2 | add-negative-trigger | No handoff edge connects the two (cupertino-handbook-draft's neighbours are all cupertino: handbook-apply/check and handbook-dimension-analyst; lehre-codify's are all lehre: preflight/decompose/gauge/conform plus its three researcher agents), so this is not an intended handoff. Nor is it a merge: the two do genuinely different jobs on different axes. cupertino-handbook-draft is descriptive and domain-scoped — pick exactly one of code/design/testing/documentation, fan out over a fixed 6-dimension catalog, and write a human-readable .cupertino/<domain>-handbook.md recording what the project already does (or honestly labelling a dimension scaffolded when it has no convention). lehre-codify is prescriptive and pipeline-bound — it runs after lehre-preflight/lehre-decompose, dispatches doctrine-researcher per rule-domain, requires every rule to cite an external authority, assigns severity by whether a deterministic predicate exists, and emits .lehre/ruleset.json for lehre-gauge/violation-auditor to enforce. Neither should absorb the other, and neither needs narrowing: each already owns its territory in its body text. The collision is purely lexical in the descriptions. Both surface as "project conventions written down as rules", and neither says which side of descriptive-vs-prescriptive it sits on, nor that one produces a prose handbook while the other produces a machine-enforced JSON ruleset inside a multi-step pipeline. The two colliding prompts actually have different correct answers, which is why a single winner is not named: "capture how this project actually does things, one rule per dimension" is cupertino-handbook-draft verbatim (actual conventions, per-dimension), while "what conventions should this project follow? write them down as rules" is lehre-codify — "should follow" is the authority-research framing, and lehre-codify's description already lists the near-identical trigger "what rules should this project follow". A router that reads both descriptions today has nothing to separate them on; adding a mutual "not for X — use Y" clause supplies exactly that discriminator without touching either pipeline. Pure clause insertion, existing text unchanged, hence haiku. | haiku | |
zeugnis-contract-drift ↔ befund-docs-drift | 2 | add-negative-trigger | befund-docs-drift | The two skills have genuinely disjoint territory and no handoff edge between them: zeugnis-contract-drift's body states "Never extract or verify prose documentation — only structural, machine-checkable declarations", while befund-docs-drift reads prose files (CLAUDE.md/README/ARCHITECTURE/DECISIONS/ADR) and verifies falsifiable claims against cited code. So this is neither a merge nor a real scope overlap — the jobs differ, the pipelines differ (contract-auditor + cycle_engine vs docs-drift-auditor + autopilot CHECK), and neither names the other. The collision is purely lexical, and it lives in the descriptions, not the prompts, so "leave" is wrong. Two words do all the damage. (1) "docstrings" in zeugnis's description reads as "docs" to a router; the disambiguating rule that zeugnis means docstring parameter/return declarations and not prose lives only in the SKILL.md body, which the router never sees. (2) "after a refactor" is zeugnis's own trigger phrase and appears verbatim in the colliding prompt, while "verify our docs match the code" is befund's near-verbatim trigger — so the second prompt hands each skill half of its own description. For these prompts the winner is befund-docs-drift: "the docs still match the code" in ordinary usage means documentation files, and the refactor clause is circumstance, not scope. "check for drift" alone is genuinely underdetermined — but the fix is still to make each description say what it is not, so that a bare "drift" ask surfaces the choice rather than silently picking the wrong auditor. Both proposed rewrites are pure negative-trigger insertions that preserve every existing trigger phrase and change no pipeline ordering; befund-docs-drift's also promotes the ci-topology exclusion that its Step 2 already enforces in code, so the description matches the guard. fix_tier haiku. | haiku |
lehre-codify ↔ befund-extract-rules | 1 | add-negative-trigger | No handoff edge connects these two, and the source files confirm they mine different things. lehre-codify is prescriptive: it researches a code style / layering / error-handling / dependency-direction doctrine from external authority (dispatching doctrine-researcher, pattern-investigator, rule-critic) and writes .lehre/ruleset.json with per-rule provenance and severity — rules about HOW code is written. befund-extract-rules is descriptive: it dispatches business-rules-miner over executable calculations, validations and state transitions, verifies each file:line citation, and requires a two-judge panel for P0 — rules about WHAT the code decides. Neither is a subset of the other, so merge and rescope are both wrong, and neither pipeline should change (lehre-codify sits between lehre-preflight/decompose and lehre-gauge; extract-rules sits inside autopilot's CHECK phase). The prompt "extract the rules this codebase follows into something checkable" hits every shared token — extract, rules, this codebase, checkable — and names neither domain. It is under-specified rather than mis-routed, so I name no winner: without knowing whether the user means conventions or domain logic, both are defensible, and a router picking both is arguably the correct behaviour. But the descriptions can still resolve it, because neither currently says what it is NOT. lehre-codify's triggers ("establish our code standards", "codify our architecture") and extract-rules' triggers ("extract business logic", "turn tribal knowledge into a spec") are individually clear yet purely positive; a prompt that matches neither trigger phrase falls back on the generic word "rules", which both own. Adding one explicit exclusion sentence to each — pointing at the other by name, with the how-it-is-written vs what-it-decides distinction spelled out — is enough for a router to either pick correctly on a disambiguated prompt or surface the fork on this one. Worth noting a third neighbour: undocumented-but-observed conventions are passung's territory, not lehre-codify's, since lehre-codify refuses any rule without an external authority; that boundary is intact and untouched by this fix. | haiku | |
passung-map ↔ passung-scan | 1 | add-negative-trigger | passung-scan | These are consecutive stages of one pipeline, not rivals: /passung-map's body opens by rendering analysis/$1/consistency.json "(from /passung-scan)" and writes matrix.json from it, so map is strictly downstream and produces nothing without a prior scan (the bidirectional edges in the graph are just "map cites its input" plus "scan names its successor"). The router collision is therefore not a genuine territory dispute — it is caused by map's description omitting the one fact that disqualifies it here. "Render the divergence inventory as a module × convention consistency matrix" names the same object scan produces ("divergence inventory") with no statement that the inventory must already exist, so a discovery prompt like "where does this repo do the same thing two different ways?" reads as an equally good match for both. scan's own description is already well-scoped (it carries two explicit exclusions and needs no change). The minimal fix is one precondition clause plus a negative trigger on map only, pointing discovery prompts at scan; no scoping, merging, or pipeline reordering is warranted, since the current order (scan → map → canonize) is correct and load-bearing. fix_tier haiku: a single-field insertion into one command's frontmatter description, no body edits, no wiring changes. | haiku |
zirkel-map-relationships ↔ befund-stage-map | 1 | add-negative-trigger | befund-stage-map | These are not the same job. befund-stage-map is a repo-specific artifact producer: it parses the real import/use graph per detected language, clusters files into stages by shallowest package boundary, and writes a stage_graph.json that six other befund components read (edges to befund-arch-health, -complexity-score, -transform-brief, -autopilot, stage-mapper). zirkel-map-relationships is a domain-agnostic reasoning technique: it extracts relationship triples and traverses them hop by hop, citing a triple index, and produces no artifact anyone downstream consumes (its only edge is back into zirkel-solve). No handoff edge exists between the two, so this is not an intended handoff, and merging would either destroy the reasoning technique's generality or destroy the stage-graph artifact contract. The collision is real and is the descriptions' fault, not the prompt's: "map the architecture of this repo" is almost verbatim the first trigger phrase befund-stage-map claims, and it wins outright — the user wants module boundaries derived from source, not a hop-by-hop causal traversal. But zirkel-map-relationships invites the miss with two unqualified graph-shaped phrases, "entity/dependency/causal structure" and "trace the dependency path", neither of which excludes code dependencies. Nothing in its text tells a router that repository import graphs belong elsewhere. Rescoping is unnecessary — zirkel's territory is legitimate and already narrow in kind (traversal over an established triple index), just unbounded in domain. The minimal correct fix is a negative trigger on the zirkel side only, naming befund-stage-map and the repo-architecture phrasings. befund-stage-map needs no change: it already owns the colliding phrase explicitly and adding a mirror exclusion would only lengthen a description that is currently winning on the merits. fix_tier is haiku: this is a pure negative-trigger insertion appended to one description, with the existing positive triggers left word-for-word intact and no pipeline order touched. | haiku |
befund-arch-health ↔ befund-stage-map | 1 | intended-handoff | befund-stage-map | The two skills are the two halves of one declared pipeline, and the colliding prompt is literally the two halves joined by "and": "show me the real module boundaries" is a verbatim trigger phrase in befund-stage-map's description, and "where they're violated" is befund-arch-health's "layering violations" / cycles / god-modules territory. The dependency is explicit in both directions and in-description: arch-health's description states it "Reads the full stage_graph.json from befund-stage-map", and the SKILL.md body (plugins/befund/skills/befund-arch-health/SKILL.md, Step 0) hard-gates on that file — if stage_graph.json is absent it degrades to "Ready-with-gaps", writes an empty ARCH_HEALTH.md noting that befund-stage-map has not run, and stops. Stage-map, symmetrically, "writes the full stage graph other befund skills depend on". So a router returning both for this prompt is not confusion — it is the correct pair, in the correct order, and befund-autopilot's CHECK phase sequences them exactly this way (stage-map first, arch-health second). Neither description claims the other's territory: stage-map never mentions violations, deficiencies, cycles, or god-modules; arch-health never offers to build or extract the graph. Merging them would fuse a mechanical extraction step that several other consumers (befund-complexity-score, befund-transform-brief, befund-status) read from into a judgement step, and rescoping would only make stage-map refuse a phrase it legitimately owns. The winner for these prompts is befund-stage-map — it must fire first because arch-health cannot produce a real answer without its artifact — with arch-health firing immediately after as its declared successor to answer the "and where they're violated" half. No description change is warranted; adding a negative trigger to either would suppress a handoff that is by design. | |
andon-verify ↔ befund-transform-execute | 1 | intended-handoff | befund-transform-execute | The collision is the wired pipeline, not overlapping territory. befund-transform-execute's own description ends with "then hands off to andon-verify without self-verifying", and the SKILL.md backs that with a hard rule (verify-dispatch-handoff, Step 6 "Hand off to verification -- never self-verify": it must state its edit is unverified and dispatch andon:andon-verify, or andon-loop for a ledger-recorded proof, then stop). The handoff graph confirms it with an in-description edge transform-execute -> andon-verify. The prompt "apply the approved fixes, one at a time, and prove each one" contains both halves of that pipeline verbatim: "apply ... one at a time" is exactly transform-execute's one-authorized-phase-per-invocation contract, and "prove each one" is exactly andon-verify's job (it never applies anything, and its own text forbids writing to the ledger). A router that returns both is returning the correct sequence, in order. No territory is double-claimed: transform-execute applies and never verifies; andon-verify proves and never edits. Neither description needs narrowing or a negative trigger — adding "not for proving" to the applier would contradict the successor it is required to name. The only real ambiguity is upstream ("the approved fixes" could mean an andon ledger gap or a MODERNIZATION_BRIEF.md phase), which is a prompt-side underspecification between transform-execute and andon-loop, not a defect in this pair. Winner: transform-execute fires first and dispatches andon-verify itself, so ranking both is harmless and ranking andon-verify alone would leave nothing to apply. | |
andon-loop ↔ andon-verify | 1 | intended-handoff | andon-loop | The overlap is the plugin's designed division of labour, and both descriptions already say so. andon-loop's body declares itself "the sole writer to the ledger" and states that "andon-propose and andon-verify only ever return structured results for you (the orchestrator) to persist"; andon-verify's body opens with "Never write to the ledger... andon-loop persists it", and its description leads its usage clause with "Use when andon-loop dispatches it to prove a wire." The handoff graph carries the edge in both directions (andon-loop -> andon-verify; andon-verify -> andon-loop, inDescription true), and five other components (andon-preflight, andon-propose, befund-idiom-fix, befund-transform-execute, plus the four tribunal agents) route to andon-verify as a leaf. For the colliding prompt, andon-loop wins outright: "apply the approved fixes" is a write to source plus a ledger update, which andon-verify is explicitly forbidden from doing, and "one at a time" is the loop's one-gap-per-stage cadence. "prove each one" is the only clause andon-verify legitimately answers, and it answers it as the loop's dispatch target -- exactly the sequence a correct run produces. A router surfacing both is therefore not cannibalization; it has surfaced the orchestrator and the leaf it will call. No description change is warranted. Narrowing andon-verify's standalone clause ("or when the user directly asks to prove a wire, run the tribunal...") would be a regression: docs/orchestration/README.md calls out andon-verify by name as one of two leaves "usually assumed to be pipeline-bound and are not", available as a verification beat inside any other workflow, and befund-idiom-fix and befund-transform-execute both depend on that standalone entry point in their own descriptions. The single distinguishing signal -- plural iterative fixes plus ledger writes vs. one named wire -- is already present in both descriptions ("closing one gap per stage", "iterate a multi-stage codebase" vs. "one wire", "<wire-id>" argument-hint). | |
andon-loop ↔ befund-transform-execute | 1 | add-negative-trigger | andon-loop | Read both SKILL.md files. They are genuinely different jobs that happen to share the phrase "one at a time, and prove it". andon-loop is an iterative orchestrator over the OKF ledger: it closes one gap per stage across the whole value stream, refuses to advance past an unproven wire, and is the sole ledger writer — the plural, repeating, self-proving shape the prompt describes, and the exact target CLAUDE.md documents befund-autopilot handing FIX+VALIDATE to once its approval gate passes. befund-transform-execute applies exactly ONE structural phase from MODERNIZATION_BRIEF.md, behind four hard CLI gates (transform.mode="execute", the phase in authorized_phases, clean tree, all Open Questions resolved), and explicitly does not verify — it hands off to andon-verify. So for "apply the approved fixes, one at a time, and prove each one" (plural fixes, self-proving, no brief or phase number named), andon-loop is the winner. Not intended-handoff: the graph edge transform-execute -> andon-loop is real but points the other way in time (proof after a phase is applied), is not in either description, and does not explain why both fire on this prompt. Not merge or rescope either — transform-execute is already the narrower skill, with quoted literal triggers and an artifact precondition andon-loop has no notion of; narrowing it further would gain nothing, and andon-loop's breadth is its purpose. The collision comes from two attractive phrases sitting in descriptions with no mutual exclusion: transform-execute's "Applies exactly one human-authorized phase" reads as "the approved fixes, one at a time", and andon-loop's "scan for gaps and fix them in order" reads as a generic fix-applier. One sentence appended to each, naming the other and the artifact that distinguishes them (MODERNIZATION_BRIEF.md phase N vs. a repo-wide ledger pass), separates them without touching either pipeline. Pure insertion, no rewrite of the existing text, no ordering change — haiku tier. | haiku |
cupertino-handbook-fix ↔ befund-idiom-fix | 1 | add-negative-trigger | cupertino-handbook-fix | These are not the same job and not a handoff: cupertino-handbook-fix applies design-handbook conformance findings (mechanical: true rows in .cupertino/handbook_check_<domain>_summary.json, gated on .claude/cupertino.local.md mode: fix, dispatches handbook-remediator/handbook-verifier), while befund-idiom-fix applies code-modernization idiom rewrites (category: "modernization" rows in code_idiom_summary.json, gated on idiom_fix.mode="fix", dispatches idiom-remediator then hands off to andon-verify). No edge connects them; their pipelines, state files, mode gates and remediator agents are disjoint. So neither merge nor rescope applies — the territories are already narrow, they just aren't fenced against each other in words. The collision is a vocabulary one, and it is real rather than the prompt's fault. "mechanical findings from the last audit" carries no domain, and both skills self-describe as the mechanical-subset applier that refuses the judgment-requiring rest ("Never touches mechanical:false" vs "Never touches smell-category findings -- those require design judgment"). The repo's own orchestration doc reinforces the leak by calling befund-idiom-fix the skill for "mechanical rewrites" / "only the mechanical fixes", even though its own description never uses the word. Each description states which artifact it consumes but neither states which artifact it does not consume, so a router with no artifact visibility has nothing to split on. Winner for this prompt is cupertino-handbook-fix, on vocabulary alone: "mechanical findings" is its literal trigger phrase, and befund's is "modernization findings"/"idiom findings". That is a thin margin, which is exactly why the fix is to make the split explicit rather than leave it. Both skills fail closed at runtime anyway (missing summary JSON or wrong mode halts), so a misroute costs a turn, not a bad edit — the cost of the collision is router noise, not damage, which further argues for the cheapest fix. Proposed change: insert reciprocal negative triggers naming the other skill and the artifact whose absence disqualifies each. Both rewrites preserve the existing wording verbatim and only append; no pipeline order, gate, or dispatch shape changes, hence fix_tier haiku. Frontmatter note for whoever applies it: cupertino's description is a double-quoted YAML scalar (keep inner quotes single) and befund's is a plain scalar (keep any added text free of ": " sequences) — per this repo's lint-frontmatter.py, a parse failure loads the skill with an empty description and no trigger at all. Files: /Users/hahn/LocalDocuments/GitHub_Forks/werkstoff/.claude/worktrees/friendly-tesla-9cfacc/plugins/cupertino/skills/cupertino-handbook-fix/SKILL.md and /Users/hahn/LocalDocuments/GitHub_Forks/werkstoff/.claude/worktrees/friendly-tesla-9cfacc/plugins/befund/skills/befund-idiom-fix/SKILL.md. Worth also correcting docs/orchestration/README.md's "mechanical rewrites" phrasing for befund-idiom-fix, which is the upstream source of the shared word. | haiku |
andon-verify ↔ zeugnis-assertion-audit | 1 | add-negative-trigger | andon-verify | These are genuinely different jobs and neither is a handoff of the other (no edge between them; the graph shows two disjoint stars — andon-verify sits under andon-loop/preflight/propose plus befund handoffs, zeugnis-assertion-audit under zeugnis-cycle/preflight/contract-drift and lehre-pin). andon-verify proves or refutes one named wire/claim/fix, with adversarial tribunal as one of seven strategies. zeugnis-assertion-audit judges whether an existing test suite would catch mutations — a property of the tests, not of a change. For "verify this change is actually correct, adversarially" the owner is unambiguously andon-verify: it literally lists "run the tribunal" and "verify a fix is actually verified" as triggers, and the prompt names no test suite at all. The collision is a description defect, not a prompt defect, and it is one-sided. zeugnis-assertion-audit's description ends with enforcement prose about its own internals — "Verification is mandatory and cannot be skipped by any setting" — which injects the bare word "verification" with no object into a trigger description. That is the only place in that description where a generic verify-this-is-correct prompt can land, and it is not a trigger condition at all (it is a note to the executing model, redundant with the body's own "This skill's Verify phase is NOT optional" paragraph). Both descriptions are otherwise correctly scoped, so narrowing or merging would destroy real territory; the cheap, correct fix is an explicit negative trigger on each side pointing at the other, which also inoculates the reverse case ("are my tests any good, adversarially") from pulling andon-verify. fix_tier haiku: pure insertion of a "not for X — use Y" clause on each description. No pipeline order changes, no re-scoping, and the existing trigger clauses stay verbatim. | haiku |
cupertino-council ↔ befund-ui-audit | 1 | add-negative-trigger | befund-ui-audit | These are two genuinely different jobs at two different lifecycle moments, so neither merge nor rescope applies: cupertino-council is generative and pre-code (five design lenses convened before a line of UI is written), befund-ui-audit is a static, read-only pass over existing JSX/TSX/Vue/Svelte/HTML/CSS for a11y, semantic-markup and design-token defects. No handoff edge connects them (cupertino-council's edges are all intra-cupertino; befund-ui-audit's are all intra-befund), so this is not an intended handoff either. It is also not the prompt's fault alone — "review this UI before we ship it" is a perfectly ordinary phrasing whose "before we ship" clearly places it after code exists, and the descriptions do not disambiguate it: cupertino-council's "never after — retrofitting the council onto finished code defeats the purpose" is stated as a rationale about the council, not as a routing instruction naming where such a request should go, and "any request to design or implement a user-facing surface" is broad enough to catch a shipped-UI review. On the other side, befund-ui-audit lists only literal a11y/token phrasings and says nothing about not owning design work, so a router sees a UI-shaped review request and has no explicit exclusion on either side. befund-ui-audit should win for this prompt, because the UI already exists. Worth noting for the cupertino side: the correct cupertino answer to a whole-project review is cupertino-review (which already carries its own negative trigger and sequences council as one stage), not cupertino-council directly — so the negative trigger should route to both alternatives. The fix is a pure insertion of "not for X — use Y" clauses on both descriptions; no pipeline order, gate, or artifact dependency changes, hence haiku tier. | haiku |
zeugnis-status ↔ befund-status | 1 | leave | These are not cannibals — they are two members of a sibling family of per-pipeline status reporters (zeugnis-status, befund-status, andon-status, lehre-status, passung-status, modernize-status, manuscript-forge-status). Each reads a disjoint artifact set: zeugnis-status opens analysis/zeugnis/*_summary.json + analysis/zeugnis/ledger.json and writes the zeugnis findings dashboard/burndown; befund-status runs befund_cli.py status-present-artifacts over befund sidecars and reports its present/structural split. Neither can produce the other's answer, so there is no territory to narrow and nothing to merge. Both descriptions already lead with their own plugin name ("where zeugnis's quality analysis stands", "where does befund stand"), which is the strongest disambiguator available. The colliding prompt — "what's the status of the last run?" — names no pipeline, no artifact, and no domain. There is no winner because the prompt does not contain the information needed to pick one; the correct behavior is to ask which pipeline the user means (or, if only one pipeline has artifacts on disk, to say so). The handoff edges confirm the separation: zeugnis-status points only at zeugnis-cycle, befund-status only at befund-* artifacts. No cross-plugin edge exists. Adding negative triggers would not fix this and would scale badly: with 7+ status siblings, mutual "not for X — use Y" clauses become an N² maintenance burden in every description, and even a fully cross-referenced pair still loses on a prompt that names none of them. The over-broad secondary clauses ("what's stale in our analysis", "what should we run next" / "which reports are stale, what to run next") are mild, but qualifying them would only help against each other, not against the five other status skills, so it buys nothing real. This is a router-side/prompt-side ambiguity, not a description defect. | haiku | |
andon-status ↔ befund-status | 1 | leave | These are two pipeline-local dashboards for two unrelated pipelines, and their descriptions already say so in non-overlapping vocabulary: andon-status is entirely about the andon ledger (stream table, wire statuses, cycle/pass counters, active constraint, evidence-strategy mix) and befund-status is entirely about befund artifact presence and staleness relative to the latest commit. Reading the SKILL.md bodies confirms the disjointness: andon-status renders from andon_core.py render-board over the ledger; befund-status reads befund_cli.py status-present-artifacts over sidecar JSON. Neither reads or writes the other's artifacts, and the handoff graph shows each one only pointing back into its own plugin (andon-status -> andon-loop/andon-preflight; befund-status -> befund-complexity-score/stage-map/transform-brief). Nothing is claiming the other's territory. The collision is entirely the prompt's: "what's the status of the last run?" names no pipeline, no artifact and no ledger, so there is no correct winner to pick — the same prompt would equally pull zeugnis-status and /passung-status, which is a four-way tie no description can break. Per this repo's own rule against inferring a missing gating value, the right behavior is to ask which pipeline the user means (or to note which ones have actually run), not to guess. Adding negative triggers would mean each of the four status skills listing the other three, which grows quadratically and still would not disambiguate a prompt that mentions none of them. The one arguably loose clause — andon-status's trailing "or what to do next" versus befund-status's "what should we run next" — is not what fired here, and tightening it would trade a real trigger phrase for no gain against this prompt. | ||
andon-status ↔ zeugnis-status | 1 | leave | Both descriptions are already fully plugin-scoped and mutually exclusive on their face: andon-status names the andon board, ledger, wire statuses, cycle/pass counters and active constraint; zeugnis-status names zeugnis's quality analysis, stale reports and the findings dashboard. Neither claims the other's territory, and the handoff graph shows two disjoint local loops (andon-preflight/andon-loop vs zeugnis-cycle) with no edge between plugins — so this is not an intended handoff and not a merge candidate either (they read different artifacts: andon's log.md ledger under ledger_dir, zeugnis's analysis/zeugnis/*_summary.json + ledger.json). The collision is caused entirely by the prompt. "what's the status of the last run?" names no pipeline, no artifact, and no plugin. It is equally a hit for befund-status and lehre-status (and, outside werkstoff, modernize-status and passung-status) — this repo ships a whole family of read-only per-pipeline status skills, and a bare "status" prompt is under-determined against every one of them by construction. Narrowing either description cannot fix that, because there is nothing in the prompt to narrow against; and adding negative triggers would require each status skill to enumerate every sibling status skill in the session (a combinatorial, ever-stale list) while still leaving the bare prompt ambiguous. The correct resolution is at dispatch time, not in the descriptions: for an unqualified status ask, decide by which pipeline's artifacts actually exist on disk (ledger_dir/log.md vs analysis/zeugnis/), or ask the user which pipeline they mean. Both skills already fail safely if picked wrongly — each is strictly read-only and each is explicitly required to say "never run" rather than fabricate a board or dashboard, so a mis-route costs a sentence, not a false report. No description edit is warranted, and there is no winner for these prompts. | ||
zeugnis-preflight ↔ befund-preflight | 1 | leave | Both descriptions are already correctly and narrowly scoped to their own plugin's pipeline, and neither claims the other's territory. zeugnis-preflight names zeugnis and its four downstream audit skills explicitly; befund-preflight names befund, its befund-* downstream skills, and befund-autopilot's CHECK phase. Verified against the on-disk frontmatter at plugins/zeugnis/skills/zeugnis-preflight/SKILL.md and plugins/befund/skills/befund-preflight/SKILL.md — the routing descriptions match the files exactly, so there is no drift to fix. The collision is entirely the prompt's doing: "is this repo ready for the pipeline? do a preflight" names no pipeline. "The pipeline" is a bare definite reference with no antecedent, and this repo ships at least four sibling readiness gates that legitimately match it — andon-preflight, zeugnis-preflight, lehre-preflight, befund-preflight (plus passung-preflight and manuscript-forge-preflight from adjacent plugins). Surfacing more than one is the correct router behavior for that prompt; the right session response is to ask which pipeline, not to pick. No description edit helps here. A negative trigger ("not for befund readiness — use befund-preflight") only fires when the prompt already names the other pipeline — which is exactly the case that already routes correctly — and does nothing for an unnamed one. Worse, it is combinatorial: each of the four-plus preflights would need an exclusion naming every sibling, and the list would silently rot the next time a plugin gains a preflight (the same four-list drift failure mode CLAUDE.md records for release wiring). Rescoping is also wrong — neither is over-broad; each is already the minimum scope its plugin needs. Merging is wrong on the facts: the two run entirely different checks (zeugnis's five audit-domain checks vs. befund's language/tool/parseability/house-rules/git-CI/doc detection), gate disjoint downstream skill sets, and belong to independently versioned plugins. No winner: for this prompt neither should fire unilaterally. | ||
andon-preflight ↔ befund-preflight | 1 | rescope | These are not competing skills: each is the read-only readiness gate for its own plugin's pipeline (andon-preflight checks ledger-directory writability and the .claude/andon.local.md gate before andon-loop/propose/verify; befund-preflight checks language detection, tool availability, smoke-parseability and doc/CI presence and emits a per-downstream-skill Ready/Ready-with-gaps/Not-ready verdict). No handoff edge runs between them, so this is not an intended handoff, and merging them would be wrong — they run different checks against different prerequisites. The colliding prompt names no pipeline ("the pipeline"), and this repo ships four sibling preflights (andon, zeugnis, lehre, befund) plus passung-preflight, modernize-preflight and manuscript-forge-preflight, so no single skill can legitimately win an unqualified "do a preflight" — the correct behavior is to ask which pipeline is meant. That much is the prompt's fault, not the descriptions'. What is a description defect is asymmetric: andon-preflight scopes every trigger to andon by name ("if a repo is ready for andon", "before the first run of any andon skill"), while befund-preflight claims the bare literal "run preflight" as a quoted trigger — generic territory that four plugins have equal claim to, and the single strongest lexical match for this prompt. That is the one description overreaching, so narrow it: drop the unqualified literal, keep the befund-qualified triggers and the autopilot CHECK-phase entry point, and add an explicit negative pointer naming the sibling preflights. andon-preflight needs no change. No winner is named for these prompts: with "the pipeline" unspecified, both firing (or a clarifying question) is the right outcome, and the rescope only stops befund from silently owning the generic phrase. Tiered opus because it is a scope-narrowing rewrite of a trigger set, not a bolted-on negative clause; it does not change any plugin's pipeline order, so it is not human-tier. | opus | |
andon-preflight ↔ zeugnis-preflight | 1 | leave | Both descriptions are already plugin-scoped and mutually exclusive in territory: andon-preflight's first sentence names andon-loop/andon-propose/andon-verify and its trigger is "ready for andon"; zeugnis-preflight names zeugnis-dependency-audit/assertion-audit/contract-drift/agentic-reliability and "is zeugnis set up correctly here". Neither claims generic repo readiness, neither names the other, and there is no handoff edge between them — the only edges either has run downward into its own plugin's skills. They also do different work (andon tests ledger-directory writability and a settings gate; zeugnis runs five independent checks reporting one verdict per domain skill), so merge and rescope are both wrong. The collision is the prompt's fault. "is this repo ready for the pipeline? do a preflight" names no plugin at all, and "the pipeline" is unresolvable — this repo ships a whole preflight family on the same naming convention (andon-preflight, zeugnis-preflight, lehre-preflight, befund-preflight, plus passung-preflight, modernize-preflight and manuscript-forge-preflight from installed plugins). Any router will multi-fire on a plugin-less preflight request, and there is no winner to name: firing andon over zeugnis for this prompt would be arbitrary. The correct runtime behavior is to ask which pipeline, or list the installed preflights. add-negative-trigger is specifically the wrong repair here. Because the ambiguity is family-wide rather than pairwise, a "not for zeugnis — use zeugnis-preflight" clause in andon-preflight fixes only 1 of ~20 pairs, and doing it properly would need every preflight to enumerate every other — description bloat that pushes out the real trigger text, for a prompt that still names none of them. | ||
cli-scaffold-compiled ↔ scaffold-cli | 1 | intended-handoff | scaffold-cli | These are the two ends of one declared dispatch edge, not two claimants on the same job. scaffold-cli is the plugin's stated entry point ("You route a scaffold request to the correct paradigm skill. You never generate code yourself") and its description names cli-scaffold-compiled as one of exactly three dispatch targets; cli-scaffold-compiled names scaffold-cli back, parenthetically, as the thing that dispatches to it. The handoff graph shows the edge in both directions and inDescription: true on both, plus the same shape for the two sibling paradigm skills (cli-scaffold-interpreted, cli-scaffold-shell) — three leaves under one router. The overlap is by design and the pipeline order must not change. For the colliding prompt ("scaffold a new command-line tool in Rust") the winner is scaffold-cli: language resolution is its job and is explicitly done in code, never guessed, and it also owns the ambiguous-language clarification and the unsupported-language refusal over 12 options. Firing cli-scaffold-compiled directly on a raw user prompt skips that resolution step — it would work for an unambiguous "Rust", but would silently mis-handle "make me a CLI in C#-ish" or a dialect the router guessed at. The only residual defect is that cli-scaffold-compiled's description buries its dependency in a parenthetical ("dispatched here by scaffold-cli") while opening with a bare imperative ("Generate a production-grade CLI scaffold in a compiled language — Rust, Go, or .NET"), which reads as a standalone capability and is why a router scored it against a raw "scaffold a CLI in Rust". That is a wording fix inside an intended handoff, not a rescope: the optional proposed description below promotes the dependency to a sentence of its own ("Not the entry point ... a raw user request goes to scaffold-cli first") and changes nothing about scope, doctrine loading, verifier handoff, or order. scaffold-cli needs no change. fix_tier is haiku because the edit is a negative-trigger insertion into one description. | haiku |
andon-challenger ↔ passung-critic | 1 | leave | These are two adversarial read-only reviewers over entirely different objects, and both descriptions already say so in their first clause. andon-challenger argues that a proposed fix fails a named wire's contract, and is explicitly framed as one blind half of andon-verify's tribunal (paired with andon-defender, adjudicated afterward) — the file confirms it requires a wire contract plus the fix's diff and refuses to hold the fix to any standard the contract never set. passung-critic reviews a proposed canonical Pattern Card or an applied /passung-align diff, and its distinctive stance is skepticism of forced uniformity (was the "losing" variant justified? did the diff ride along extra cleanup? was an equivalence-verifier PASS re-derived?) — none of which is expressible in andon's wire-contract vocabulary. Neither claims the other's territory, and there is no edge between them; the two clusters are internally wired (andon-adjudicator/defender/verify; passung-verify/align/equivalence-verifier) and disjoint from each other. The collision comes entirely from the probe prompt, which is domain-free: "a read-only critic that tries to refute this proposed change" names no wire, no contract, no Pattern Card, no convention variant — it describes the shared genus (adversarial read-only review) and supplies nothing to discriminate the species. Any real invocation carries the object being critiqued, and that object routes unambiguously. Adding "not for X — use Y" cross-references would be noise: the two plugins are otherwise unrelated, the negative trigger would only pay off for prompts as underspecified as this one, and a router that cannot pick between them on this prompt also cannot be expected to act on a pointer, since it has nothing on which to decide the pointer applies. No winner: with the object unstated, dispatching andon-challenger without a wire contract would make it refuse or invent a standard, and dispatching passung-critic without a Pattern Card or alignment diff leaves it nothing to review. | haiku | |
handbook-remediator ↔ idiom-remediator | 1 | add-negative-trigger | These are two separate pipeline leaves, not duplicates and not a handoff pair: the handoff graph shows no edge in either direction between them, and each is owned by its own orchestrator (cupertino-handbook-fix -> handbook-remediator -> handbook-verifier; befund-idiom-fix -> idiom-remediator -> andon-verify). Their bodies confirm genuinely different jobs on different finding streams: handbook-remediator applies mechanical: true design-handbook findings produced by cupertino-handbook-check and is explicitly forbidden to verify itself (a separate handbook-verifier does), while idiom-remediator applies modernization-category findings from befund-code-idiom, clustered by (file, kind), refusing smell findings and anything carrying a severityNote. Merging or rescoping either would break a pipeline order, so neither is on the table. The collision is real but shallow. The prompt ("apply exactly this one already-approved mechanical fix at the cited line and nothing else") is the shared shape of both agents' contract — one verified finding, exact cited location, nothing widened, no self-verification — with zero domain signal about whether the finding came from a design-handbook rule or a language-idiom rule. Neither description contains anything that would push a router away when the domain is absent, and neither says out loud that it is dispatch-only. So this is not purely the prompt's fault ("leave"): a cheap negative trigger in each description fixes every domain-bearing variant of this prompt ("apply this handbook fix", "apply this Optional[X] rewrite") and makes the dispatch-only nature explicit, which is what stops a bare user prompt from selecting either agent directly. No winner for these exact prompts: with no domain named and both agents invocable only by their parent skill, the correct outcome is that neither fires from the bare prompt — the orchestrator (cupertino-handbook-fix or befund-idiom-fix) is what should be selected, and it dispatches the right remediator. The proposed descriptions add (a) a cross-reference naming the other agent as the wrong-domain alternative and (b) an explicit "dispatch comes from X, not from a bare fix request" clause. Wording changes only; no pipeline order, tool grant, or body text is touched, hence the haiku tier. | haiku | |
conformance-remediator ↔ handbook-remediator | 1 | add-negative-trigger | Not a real cannibalization: these are two dispatch-only leaves in two different plugins, each bound to its own orchestrator (lehre-conform -> conformance-remediator -> lehre-validate; cupertino-handbook-fix -> handbook-remediator -> handbook-verifier). The handoff edges confirm each is a step in a pipeline the other has no access to, so they cannot merge and neither can be rescoped away — lehre applies doctrine-rule violations approved in LEHRE_BRIEF.md, cupertino applies mechanical:true findings emitted by cupertino-handbook-check, and the finding objects have different shapes (rule/location cluster vs. file:line + suggestedFix JSON). What they do share is the abstract job description, verbatim in both: "apply one already-verified mechanical fix at the cited location and nothing else." Neither description names a domain — only a dispatcher — so a domain-free prompt like the colliding one has literally nothing to discriminate on, and the router picking both is the honest outcome. There is no winner for this prompt as written: correct routing depends entirely on which upstream produced the finding, and a bare user prompt should reach neither agent directly (both refuse a dispatch that does not state the finding was verified and approved). The cheap fix is a mutual negative trigger naming the sibling and its source artifact, so the router has a discriminator when the prompt does carry a hint ("handbook", "design", "doctrine", "brief"), and both stay unattractive for an unqualified prompt. No pipeline order changes; this is a pure appended sentence on each description, hence haiku tier. | haiku | |
conformance-remediator ↔ idiom-remediator | 1 | add-negative-trigger | These are two genuinely distinct leaves, not duplicates: conformance-remediator applies a lehre rule-card fix after lehre-conform's approval gate and hands verification to lehre-validate; idiom-remediator applies a befund-code-idiom "modernization" rewrite dispatched by befund-idiom-fix and hands verification to andon-verify. There is no handoff edge between them — each only names its own pipeline — so this is not an intended handoff, and merging would fuse two different upstream contracts (rule cards + brief approval vs. modernization findings with severityNote/smell exclusions), two different verifiers, and two plugins' pipeline orders. Rescoping is not needed either: the territories already differ; what collides is the generic surface phrasing both share ("apply exactly this one already-verified mechanical fix at the cited location and nothing else"), which is the whole job description of any per-cluster remediator. The colliding prompt carries no pipeline signal at all — no rule card, no brief, no idiom/modernization vocabulary — so no winner exists for it as written; both agents are dispatch-only leaves that a bare user request should never fire directly. The cheap durable fix is a mutual negative trigger plus an explicit "only ever dispatched by <its orchestrator>" clause, so a prompt with even partial context routes correctly and a context-free one visibly matches neither exclusively. Pure insertion into the descriptions, no body or pipeline change: haiku tier. | haiku |
Known-answer prompts the router got wrong (2):
| prompt | expected | router picked |
|---|---|---|
| scaffold a CLI in some scripting language, whatever's easiest | passung-preflight | scaffold-cli |
| where do we violate our own layering, and which of those are real | befund-stage-map | befund-arch-health |
4. Verified findings
Severity: blocker 0, major 111, minor 131, nit 2. Angle: procedure 64, step-logic 53, meaning 47, clarity 44, contract 27, cannibalization 7, other 2.
major (111)
| where | rule | claim | fix | tier |
|---|---|---|---|---|
plugins/andon/agents/andon-adjudicator.md:25 | Q-STEPS-INPUTS | The adjudicator's one non-overridable stop condition depends on Tier 1 structural-evidence (a Kythe/SCIP/LSIF index query from andon-verify 'strategy e') that is never named among 'the three inputs (Defender/Challenger/Verifier)' the opening line and 'What to produce' section say it reads, and none of andon-challenger.md, andon-defender.md, or andon-verifier.md mention producing any structural-index output -- so it is unstated whether the adjudicator must query the index itself (it has no Bash, only Read/Grep/Glob) or wait on an input none of its three named feeders supply. | Resolve the ambiguity in one direction rather than leaving both options open: state in andon-verifier.md's 'What to produce' that when a criterion under review is a structural claim, the Verifier is responsible for running the Tier 1 index query (Kythe/SCIP/LSIF, e.g. via an LSP-backed tool) as part of 'reproduce, or fail to reproduce, specific factual claims,' and reports the tier and any contradiction as one of its objectively-true findings. Do NOT resolve it as 'queried directly by the adjudicator itself' -- andon-adjudicator.md grants only Read/Grep/Glob (no Bash, no LSP), and its own refusals section exists precisely to keep it a pure judge separated from fact-gathering ("dispatched as a separate party specifically so your judgment isn't anchored"); giving it direct index-query capability would require expanding that tool grant and blur the Verifier/Adjudicator separation of duties the tribunal protocol depends on (Q-PROC-TOOLS-ROLE: tool grant should match role). Then update andon-adjudicator.md's refusal bullet and 'What to produce' to say the Tier 1 tier/non_overridable flag arrives as part of the Verifier's reported facts, i.e. a fourth field the Verifier supplies, not a fourth agent or a capability the Adjudicator exercises itself. | sonnet |
plugins/andon/references/okf-ledger-schema.md:17 | Q-MEANING-CONTRACT | This settings table claims ANDON_BOARD.md lives in output_dir, but andon-status/SKILL.md never writes a markdown board file at all -- only an HTML board written to <ledger_dir>/ANDON_BOARD.html, a different directory and a different extension -- so a reader trusting this stated contract looks for the wrong file in the wrong place. | Confirmed against skills/andon-status/SKILL.md (line 76) and scripts/build_board_html.py: the board is written only as <ledger_dir>/ANDON_BOARD.html, never as ANDON_BOARD.md, and never under output_dir. Change the output_dir row to 'Where PREFLIGHT.md, etc. live.' and append to the ledger_dir row: '...lives, plus the rendered ANDON_BOARD.html.' | haiku |
plugins/andon/references/okf-ledger-schema.md:68 | Q-MEANING-CONTRACT | The Tags section states tags 'are derived automatically from the first-class fields above,' but no first-class field anywhere in the stage/gap/evidence doc-type definitions determines a fast/slow lane, leaving lane:fast|slow an orphaned tag with no stated source in the one place a reader would look for the schema's output contract. | ⚠ andon_core.py's build_tags_for_doc() shows lane is a first-class field read only for evidence docs (if doc_type == "evidence": ... if fields.get("lane"): tags.append(f"lane:{fields['lane']}")), with allowed values in LANES = ["fast","slow"] (unvalidated by validate_doc). It has no meaning for gap docs. The suggested fix's second option ('add the first-class field ... to the gap or evidence doc-type sections') is wrong for 'gap' -- applying it there would document a field the code never reads for gap docs, adding a new inaccuracy while fixing the original one. Narrow the fix to the evidence section only: add 'Optional: lane -- one of fast, slow. Not currently validated; used only to emit the lane: tag.' under the evidence doc-type list (around line 58), and drop the 'gap or' framing entirely. (conflicts with Q-MEANING-CONTRACT) | sonnet |
plugins/andon/skills/andon-loop/SKILL.md:5 | Q-MEANING-PROMISE | The frontmatter promises an optional stage-or-gap filter argument, but no phase in the body (topology detection, cursor scanning, or cursor advance) ever reads, checks, or scopes execution to it -- the only related mechanism is Phase 0's unrelated 'explicit stream' override for topology detection, not a runtime filter on which gap/stage gets worked -- so a user who supplies the hinted argument gets a full unfiltered loop with no indication their filter was ignored. | The two branches are not equally scoped: deleting the argument-hint line is a one-line haiku-tier edit, while wiring a real stage-or-gap filter through Phase 2's scan and Phase 5's cursor advance changes the skill's actual contract (what inputs it accepts, how the cursor state machine behaves) -- per the rubric's own fix-tier table that's a 'human' decision (changes a plugin's contract), not something a sonnet rewrite should pick unilaterally. Keep the finding and the two options, but flag that a human must first decide whether andon-loop is meant to support scoped/filtered runs at all before either branch is executed; only route the chosen branch to sonnet (or haiku for the delete-only path) afterward. | human |
plugins/andon/skills/andon-loop/SKILL.md:222 | Q-STEPS-INPUTS | Unlike every other scripted call in this file, Phase 6's check-convergence invocation consumes two inputs -- a gaps-closed tally and a JSON array of wire statuses -- with no preceding step in Phases 2-5 telling the orchestrator to accumulate a per-pass closure count or to assemble a wire-status array from the stages' evidence docs, so the call's arguments have no named source. | Confirmed against plugins/andon/skills/andon-status/SKILL.md line 35 ('each wire's status is derived from its latest evidence doc'), so pointing there instead of restating the definition is a pointer, not a duplication (keeps M-DUP-CONTENT clean). The suggested fix is sound as written: add one sentence at the top of Phase 5 or Phase 6 instructing the orchestrator to (a) tally gaps closed since the last pass boundary and (b) build the wire-status array from each inter-stage wire's latest evidence-doc verdict, cross-referencing andon-status's existing derivation rule rather than re-deriving it. | sonnet |
plugins/andon/skills/andon-preflight/SKILL.md:3 | Q-MEANING-PROMISE | The description promises no file writes beyond a ledger-writability test, but Step 3 of the same file ('write the two outputs') has the skill write <output_dir>/PREFLIGHT.md and <output_dir>/preflight_summary.json to disk -- real writes the description's carve-out does not cover. | Extend the description's carve-out to name the report/summary files this skill itself writes (or move that writing responsibility elsewhere) so the promise matches Step 3's actual behavior. | sonnet |
plugins/andon/skills/andon-propose/SKILL.md:19 | Q-MEANING-ASSUMED | Reading house-rules.md is gated on 'andon-preflight reported it present,' a session-carried fact this file never says how to obtain when absent -- yet this skill's own description says it can be triggered directly ('when the user directly asks what to fix for a named gap'), a path where andon-preflight was never run, so a real house-rules.md would be silently skipped instead of read. | Replace the andon-preflight-report gate in step 2 with a direct existence check: read .claude/house-rules.md (or house_rules_path) if it exists on disk, regardless of whether andon-preflight ran this session; only fall back to codebase-only defaults when the file is actually absent. This also satisfies Q-STEPS-PREDICATE (observable predicate instead of session-state inference). | sonnet |
plugins/cli-scaffold/agents/cli-scaffold-verifier.md:77 | Q-STEPS-TERMINATION | The bounded fix-loop's stop condition (MAX_FIX_ITERATIONS) is named in the exit-code list, but the procedural steps 3 and 4 never instruct the agent what to report or escalate to the calling skill when that HALT actually occurs — the reporting bullets in step 4 cover only verdict, findings, and the fixable/needs-human-judgment reminder. | Add an explicit bullet to step 4 instructing the agent to surface the HALT/exhausted-loop condition to the calling skill as its own reported outcome. | sonnet |
plugins/cli-scaffold/agents/cli-scaffold-verifier.md:78 | Q-STEPS-GATE | Step 2 lists exit code 2 (usage/scope error) as a possible outcome of running verify_scaffold.py but the body never says what the agent should do next when it occurs — unlike exit 0 and exit 1, which are followed by concrete downstream handling (report the pass verdict; read the report JSON or relay the HALT line), exit 2 is a dead end with no named gate or return path. | Add a sentence after the exit-code list stating the action for exit 2, e.g. 'On exit 2, do not proceed to step 3 — report the usage/scope error back to the calling skill and stop.' | sonnet |
plugins/cli-scaffold/agents/cli-scaffold-verifier.md:80 | Q-STEPS-GATE | Step 3 unconditionally instructs the agent to read the report JSON, but step 2's own exit-code list includes exit 2 ('usage/scope error') for which no report is written, and step 3 never branches on or handles that case. | ⚠ Both this finding and finding index 6 target the same underlying gap (exit code 2 has no downstream handling) with the same rule_id (Q-STEPS-GATE). Applying both fixes as written adds two separate instructions for the same case in adjacent locations. Fold this into finding index 6's fix — one sentence after the exit-code list in step 2 (e.g. 'On exit 2, do not proceed to step 3 — report the usage/scope error to the calling skill and stop.') already resolves step 3's dangling read-report-JSON instruction, since step 3 is then never reached on exit 2. Don't add a second, separately-worded branch in step 3/4. (conflicts with Q-CLARITY-CONCISE) | sonnet |
plugins/cli-scaffold/agents/cli-scaffold-verifier.md:80 | Q-MEANING-ASSUMED | Step 3 unconditionally instructs the agent to "Read the report JSON", but the exit-code table two lines above only states a report path is surfaced ("report path on stdout") for the exit-1/gaps branch, leaving the exit-0/pass branch's report location an unstated precondition the agent has no documented way to satisfy. | State explicitly whether/where the report path is emitted for the pass (exit 0) case too, or scope step 3 to the branch where a path is actually printed. | sonnet |
plugins/cli-scaffold/agents/cli-scaffold-verifier.md:85 | Q-STEPS-GATE | Step 2 names three possible outcomes of the engine run — exit 0 pass, exit 1 gaps, and exit 1 with a stderr HALT line when the bounded fix loop is exhausted — but step 4's reporting instruction only tells the agent to report a verdict of pass or gaps, leaving no instruction for what to report when the HALT/exhausted-loop case occurs. | ⚠ This duplicates finding index 1's fix — both add a step-4 bullet reporting the HALT/exhausted-loop outcome for the same gap. Applying both literally adds two separately-worded bullets covering the same case. Merge with finding index 1: add exactly one bullet to step 4 for the HALT/exhausted-loop outcome, not two. (conflicts with Q-CLARITY-CONCISE) | sonnet |
plugins/cli-scaffold/skills/cli-scaffold-compiled/SKILL.md:18 | Q-MEANING-ASSUMED | The step presupposes 'the resolved language' already exists as a precondition, but this skill's own description says it can be triggered directly by user phrasing ('Use when the user requests a CLI in one of those three'), and nothing in this file names what to do or how to resolve the language if it was not handed off by scaffold-cli's lang_router.py step. | ⚠ Don't have the paradigm skill silently re-run lang_router.py and barrel ahead — that leaves two unhandled failure paths: (1) lang_router.py's AMBIGUOUS/UNSUPPORTED exits (scaffold-cli's Step 1 already defines the correct response: ask for clarification, or refuse and list the 12 options — this file would need the same gate, not a bare re-run), and (2) the resolved paradigm coming back as something other than 'compiled' (e.g. a user asks this skill directly for a Python CLI): the fix as written never says what to do then, so a sonnet applying it verbatim could end up generating a Rust/Go/.NET scaffold for a language it doesn't cover, or silently ignoring the mismatch. State the precondition instead: this skill assumes it is invoked via scaffold-cli's dispatch with the language already resolved to one of Rust/Go/.NET. If invoked directly and that isn't the case, do not resolve it here — stop and tell the user to go through scaffold-cli (or explicitly re-run lang_router.py but only follow through if its paradigm is compiled; otherwise refuse and name scaffold-cli as the correct entry point, mirroring scaffold-cli's own AMBIGUOUS/UNSUPPORTED handling for the ambiguous/unsupported exits). (conflicts with Q-STEPS-GATE) | sonnet |
plugins/cli-scaffold/skills/cli-scaffold-interpreted/SKILL.md:18 | Q-MEANING-ASSUMED | Same hidden precondition as the compiled paradigm skill: this file assumes 'the resolved language' is already established (from scaffold-cli's dispatch) but never states what happens if the skill is invoked directly, which its own description ('Use when the user requests a CLI in one of those six') explicitly allows. | State explicitly that 'the resolved language' is expected to arrive from scaffold-cli's dispatch, and add a fallback step: when invoked directly with an unresolved/ambiguous language name, run python3 "${CLAUDE_PLUGIN_ROOT}/scripts/lang_router.py" "<language-as-given>" (the same script scaffold-cli itself uses) to resolve it before reading the per-language reference — confirmed to exist at plugins/cli-scaffold/scripts/lang_router.py, so this fix is safe to apply as written. | sonnet |
plugins/cli-scaffold/skills/cli-scaffold-shell/SKILL.md:18 | Q-MEANING-ASSUMED | Same hidden precondition as the other two paradigm skills: 'the resolved dialect' is treated as a given even though the skill's own description permits direct user-triggered invocation, and the file never names what happens (or how to resolve it) if the dialect was not pre-resolved by scaffold-cli. | State explicitly that 'the resolved dialect' is expected to arrive from scaffold-cli's dispatch, and add a fallback step: when invoked directly with an unresolved/ambiguous dialect name, run python3 "${CLAUDE_PLUGIN_ROOT}/scripts/lang_router.py" "<dialect-as-given>" (the same script scaffold-cli itself uses) to resolve it before reading the per-dialect reference — confirmed to exist at plugins/cli-scaffold/scripts/lang_router.py, so this fix is safe to apply as written. | sonnet |
plugins/cli-scaffold/skills/scaffold-cli/SKILL.md:32 | Q-STEPS-TERMINATION | The AMBIGUOUS branch of step 1 creates a clarify-and-retry loop back to step 1 with no stated bound on how many clarification rounds are allowed and no escalation path if the user's answer is still ambiguous (e.g. re-asks the same question forever, or after N rounds falls back to refusal/listing options). | Fix is sound as written. Context confirms the gap is real: step 1's AMBIGUOUS branch (SKILL.md lines 31-32) sends the router's narrow clarification (e.g. constants.py's AMBIGUOUS dict: "Which shell dialect? (Bash, Zsh, PowerShell, or POSIX sh)") back through an unbounded re-run of step 1, with no cap and no escalation if the second answer is still ambiguous or off-list. The suggested fix (cap at one retry, then fall back to listing the 12 supported options and asking for an exact match) is a good choice specifically because that fallback already exists verbatim for the UNSUPPORTED branch (lines 33-35, backed by lang_router.py's _supported_list()) — reusing it as the escalation path is not new content invented for this fix, just extending an existing, already-correct behavior to a second trigger condition. No rubric rule is violated: it doesn't bloat description (change is body-only), doesn't conflict with Q-PROC-FREEDOM (language routing is the fragile/deterministic case that this skill already treats as code-driven with exact-step prose, so a precise retry-cap instruction matches that pattern), and isn't M-DUP-CONTENT (that rule is about byte-identical files, not reusing one phrase within the same skill's own steps). Minor optional tightening: word it as "if the router's second run also raises AMBIGUOUS or the user's reply doesn't match one of the printed options" so the cap's trigger is the same observable exit-code/pattern the rest of step 1 already keys off, rather than a vaguely-worded 'still ambiguous'. | sonnet |
plugins/passung/agents/align-executor.md:28 | Q-MEANING-ASSUMED | PLAYBOOK.md gets an explicit STOP-and-refuse gate if missing, but CANON.json — an equally required read-first input naming the target canonical form — has no stated behavior if it does not exist, leaving that precondition silently assumed. | In step 2 of the 'Read these first' list, after naming CANON.json, add a STOP clause parallel to the PLAYBOOK.md one: e.g. 'If CANON.json does not exist, STOP and align nothing — report that the canonical form for this module has not been determined and do not edit a file.' This mirrors the existing PLAYBOOK.md gate's phrasing and closes the same hidden-precondition gap without adding a second unrelated concern. | sonnet |
plugins/passung/commands/passung-align.md:7 | Q-CLARITY-AMBIG | argument-hint declares an optional second positional argument [dimension], but the body never ties this bracket notation to $2 anywhere in the file — unlike sibling commands passung-canonize.md ('If a [dimension-pattern] was given ($2)') and passung-scan.md, which explicitly bind the bracket to the CLI argument — leaving it unclear whether a user-supplied second argument is read at all or how it reaches the command. | Confirmed: grep of the file shows [dimension] never appears alongside $2, and even the workflow-call template uses the bare placeholder "<dimension>" rather than "$2" (contrast area: "$1" in the same block, and canonize.md/scan.md's explicit "[dimension-pattern] was given ($2)" phrasing). Change the opening sentence to: "align every divergent site for [dimension] ($2; or the brief's next unstarted phase, if omitted) onto its approved canonical form," and use $2 (not the bare placeholder) in the workflow-call template's dimension: field. | sonnet |
plugins/passung/commands/passung-align.md:9 | Q-STEPS-GATE | The gate names the failure condition (no approved brief) but never names where to return (run /passung-brief $1), unlike this same plugin's brief.md and verify.md, both of which explicitly name the remedial command when their own gates trip. | After "if the requested dimension's phase entry criteria aren't met," add: "— run /passung-brief $1 to produce and get one approved." Verified: the whole file never names /passung-brief even though it is the only source of the approval this gate requires (grep confirms zero occurrences), while verify.md and brief.md both name their remedial commands at their own gates. The one-clause addition matches those sibling files' existing style and stays well under any length concern since this is body prose, not frontmatter. | haiku |
plugins/passung/commands/passung-align.md:29 | Q-STEPS-GATE | Step 1's pilot instructs making the edit, running the module's tests, then writing PLAYBOOK.md, but never states what to do if the pilot's own tests fail — the step just proceeds to the write, with no named gate before the playbook (and the fan-out that depends on it) is produced. | Confirmed: Step 1 runs the pilot's tests and proceeds straight to writing PLAYBOOK.md with no named branch for a failing test, unlike Step 2's fan-out (which has an explicit 2/3 circuit breaker) and verify.md's Step 1 (which names newly-failing tests a blocker with two distinct causes to disambiguate). Add, immediately after "run the module's own tests": "If the pilot's tests fail, do not write PLAYBOOK.md yet — revise the canonical-form choice (or escalate to the human who approved the brief) before proceeding; Step 2's fan-out must not start from an unproven pilot." | sonnet |
plugins/passung/commands/passung-align.md:48 | Q-MEANING-ASSUMED | The workflow call requires a deps field per unit and instructs 'Enumerate the remaining divergent modules from CANON.json's divergentSites first', but CANON.json's documented divergentSites schema (in passung-canonize.md) is { module, files, count } with no deps data, so the file never says where the required deps information actually comes from. | Confirmed against workflows/align.js: deps is a real, optional field the workflow consumes for batch ordering, but CANON.json's documented divergentSites schema (module/files/count, per passung-canonize.md) carries no deps data — so "enumerate ... from CANON.json's divergentSites" cannot by itself populate the deps the call template requires. Add one sentence before the workflow-call block: state that deps must be derived by inspecting each remaining module's actual references onto the canonical form the pilot introduced (imports, shared types/utilities it now depends on) — not read out of CANON.json, which does not carry it. | sonnet |
plugins/passung/commands/passung-brief.md:61 | Q-CLARITY-AMBIG | This sentence is internally inconsistent with the ordering rule stated two paragraphs earlier ('order by blast radius, smallest first' for Phase 1), since 'whichever phase's dimension has the most divergent sites' reads as selecting the pilot from across all phases (likely the largest, not smallest, phase) rather than from within Phase 1 itself. | The suggested fix correctly identifies the fork but hands a sonnet a rewrite it can't safely make: which reading is correct is not a wording choice, it's a pipeline-semantics decision (whether the pilot always lands on the smallest-first Phase 1, or on whichever phase/dimension happens to have the most divergent sites even if that's a later, larger phase — which would silently override the smallest-first ordering rule two paragraphs above). A model guessing between these could encode the wrong execution order into the brief. This needs a human author's decision on intended behavior, then a sonnet-level rewording to state it unambiguously (e.g. either 'Phase 1 (the phase the ordering rule above placed first) additionally gets a pilot: within it, the dimension with the most divergent sites is aligned in one representative module first' or 'the pilot targets whichever phase across the whole sequence has the most divergent sites, and that phase is promoted to run first, ahead of the blast-radius ordering above'). | human |
plugins/passung/commands/passung-canonize.md:33 | Q-MEANING-ASSUMED | The maturity-weighting step depends entirely on working, non-shallow git history (exactly what /passung-preflight's Check 5 exists to verify — "Sparse or squashed history degrades the maturity signal in /passung-canonize Step 2"), but canonize.md never names PREFLIGHT.md as an input, never checks Check 5's verdict, and gives no instruction for what to do when history is sparse or absent (e.g. downgrade confidence, fall back to frequency-only) — the precondition is silently assumed rather than checked or gated. | Add one line near the top of canonize.md Step 2: "Before weighting, read analysis/$1/PREFLIGHT.md's Check 5 verdict if it exists. If it is red or the file is absent, skip the maturity signal, weight by frequency only, and cap that dimension's Pattern Card confidence at Medium" — reusing Check 2's own already-stated fallback language ("canonize falls back to frequency-only weighting and flags every derived rule as lower-confidence") instead of inventing new wording, so the two files state one consistent contract rather than two similar-but-not-identical ones. This is not a forward reference or a new hard gate: PREFLIGHT.md is an artifact from the (typically prior) preflight command, the fallback is a confidence downgrade rather than a block, and the read happens inside whichever extractor executes Step 2 (a subagent with Bash/Read access in both Method A and Method B) — not inside the workflow script itself, so it doesn't run into the 'workflow scripts have no filesystem access' gotcha. | sonnet |
plugins/passung/commands/passung-verify.md:6 | Q-CLARITY-AMBIG | Same gap as passung-align.md: argument-hint declares [dimension] as a second positional argument but the body never binds it to $2, so it is unclear whether a user-supplied dimension argument is actually consumed or how it reaches the Workflow call's dimension: "<dimension>" placeholder. | State explicitly, as passung-scan.md and passung-canonize.md both do ('If a [X] was given ($2), scope to...'), that [dimension] is $2, defaulting to every aligned dimension when omitted. Also update the Workflow call itself to pass dimension: "$2" (mirroring canonize.md's dimensionPattern: "$2" and scan.md's conventionPattern: "$2") rather than the unbound placeholder dimension: "<dimension>", so the fix closes the gap in both the prose and the actual arg passed to the workflow script. | sonnet |
plugins/passung/commands/passung-verify.md:53 | Q-MEANING-ASSUMED | The Workflow call's args are only { area: "$1", dimension: "<dimension>" } with no list of aligned modules, yet the workflow is described as fanning out one verifier per aligned module — unlike passung-align.md, which explicitly enumerates units from CANON.json before the call because 'the workflow script has no filesystem access,' this file never says to read ALIGN_NOTES.md first or pass the module list as an arg. | Add a step before the Workflow call: enumerate the modules /passung-align actually touched by reading analysis/$1/ALIGN_NOTES.md (per align.md's Write contract, one entry per module), and pass them as a units: [{ name, path }] arg. Unlike align.md's units, omit a deps field — verify's fan-out is independent per-module (each verifier 're-reads its diff... independently'), not dependency-ordered batching, so carrying over align.md's deps semantics verbatim would misrepresent verify's actual execution model. | sonnet |
plugins/passung/workflows/canonize.js:43 | Q-MEANING-CONTRACT | The shared UNTRUSTED block, embedded via ${UNTRUSTED} in the Provenance-panel prompt (and the Divergent-sites prompt), tells the judge agent to report suspicious text in the 'injectionSuspects field', but PANEL_SCHEMA (basisSound, faithful, reason) and SITES_SCHEMA (divergentSites) — the schemas that actually constrain those two agent calls — carry no such field, so an injection flagged by a provenance-panel judge or the sites-catalog agent has nowhere to be recorded and never reaches injectionFlags. | Add an injectionSuspected: { type: 'boolean' } field to PANEL_SCHEMA (mirroring VERDICT_SCHEMA's existing field) and an injectionSuspects: { type: 'array', items: { type: 'string' } } field to SITES_SCHEMA (mirroring PATTERNS_SCHEMA's existing field), then extend the loops at lines ~301-307 (panelVerdicts) and after the sitesResult call (~334) to push any flagged entries into injectionFlags, the same way lines 210 and 256 already do for the Extract and Verify phases. | sonnet |
plugins/passung/workflows/scan.js:34 | Q-MEANING-CONTRACT | The shared UNTRUSTED block, interpolated into the Verify-phase referee prompt, instructs the agent to report suspicious text in an 'injectionSuspects' field, but VERDICT_SCHEMA (the schema that constrains that referee's output) only declares verdict, reason, and correctedOutOfScopeReason — no injectionSuspects field — so a referee that spots injected text during verification has no field to report it in and the finding is lost. | Add injectionSuspects: { type: 'array', items: { type: 'string' } } to VERDICT_SCHEMA's properties (it is currently absent — VERDICT_SCHEMA only has verdict/reason/correctedOutOfScopeReason, confirmed by reading lines 75-87), then in the verdict-processing loop (lines 170-180) push each v.injectionSuspects || [] into the same injectionFlags array that the Find-phase results already feed (lines 129-135), before the final dedup at line 192. This is a small, mechanical schema+merge change with no downstream consumer to break (toolReports/injectionFlags are only read by the calling session). | sonnet |
plugins/passung/workflows/scan.js:70 | Q-MEANING-CONTRACT | DIMENSIONS_SCHEMA's toolReport field expects a summary of a linter/formatter that was run in check-only mode, but the finder prompt (lines 113-118) and the UNTRUSTED block never instruct the agent to run, or even look for, any linter/formatter tool — it only tells the agent to check documentation and version-deprecation status. Nothing in the prompt gives the agent a reason to populate this field with anything but a guess. | Confirmed by repo-wide grep: toolReports/toolReport appears nowhere outside scan.js itself (no SKILL.md, command, or viewer consumes it), so the field is dead weight today. Prefer the second branch of the suggested fix outright — delete the toolReport schema field (line 70), the toolReports array (line 130) and its population (line 133), and the toolReports key in the returned object (line 191) — rather than presenting both options as equally live; only add the lint-run instruction to the finder prompt instead if a downstream consumer (e.g. a future CONSISTENCY_SCAN.md section) is actually planned. | sonnet |
plugins/passung/workflows/verify.js:89 | Q-MEANING-ASSUMED | The Check-phase prompt assumes analysis/<area>/ALIGN_NOTES.md exists and contains a summary of the align pass, but meta.whenToUse states only 'Requires args {area, dimension, units: [{name, path}]}' with no mention of ALIGN_NOTES.md as a required input, leaving the dependency undocumented for a caller deciding when this workflow is safe to invoke. | Fine as written. whenToUse is not the capped description field, so adding one sentence naming analysis/<area>/ALIGN_NOTES.md as a required prior-pass artifact (consistent with the orchestration doc's statement that /passung-verify reads artifacts an earlier command wrote) is a safe, accurate addition. | sonnet |
plugins/passung/workflows/verify.js:135 | Q-MEANING-CONTRACT | The re-check code reads v.injectionSuspected to decide whether to record an injection flag, but RECHECK_SCHEMA (required: upheld, reason; properties: upheld, reason, revisedVerdict) declares no injectionSuspected field — the structured-output contract for that agent call never asks for it, so this branch is unreachable even though the appended UNTRUSTED text tells the adversarial re-checker to report exactly that. This is the same 'code that looks correct and silently does nothing' shape CLAUDE.md's gotchas table warns this repo has been burned by repeatedly. | ⚠ The UNTRUSTED block is a shared constant injected verbatim into both the Check prompt and the Re-check prompt, and it tells the agent in both cases to 'report it in injectionSuspects instead' (plural, array-shaped). Finding index 1's fix for CHECK_SCHEMA correctly adopts that exact name (injectionSuspects, array). This fix instead adds injectionSuspected (singular boolean) to RECHECK_SCHEMA — it matches the pre-existing code read at line 135, but if applied alongside finding 1's sibling fix it leaves two different field names for the identical 'flag a suspected prompt injection' concept sitting a few lines apart in the same file, which is exactly the terminology-inconsistency Q-CLARITY-TERMS flags. Prefer naming RECHECK_SCHEMA's field injectionSuspects too (it can still be typed as a boolean or a short array — either way the label should match the shared prompt text and the sibling schema) rather than perpetuating the code's existing mismatched name. (conflicts with Q-CLARITY-TERMS) | sonnet |
plugins/passung/workflows/verify.js:145 | Q-MEANING-CONTRACT | The Check-phase prompt embeds the UNTRUSTED block instructing the agent to 'report it in injectionSuspects instead', but CHECK_SCHEMA (the schema constraining that exact agent call) has no injectionSuspects field at all — confirmed by this dead for (const s of []) loop that the author left as an acknowledged, unfixed gap — so any injection an equivalence-verifier detects during Check has nowhere to go and is silently dropped. | Fine as written. Adding injectionSuspects: { type: 'array', items: { type: 'string' } } to CHECK_SCHEMA and replacing the dead for (const s of []) loop with a real push into injectionFlags closes a genuine unreachable-branch gap and matches the UNTRUSTED prompt's literal field name ('report it in injectionSuspects instead'), so it is also the naming-consistent half of this pair (compare finding index 3). | sonnet |
plugins/zirkel/agents/branch-proposer.md:44 | Q-MEANING-ASSUMED | The Score job's stated inputs omit the original scoped problem that Propose explicitly names as an input, yet judging Feasibility/Impact/Risk and a 'biggest blocker' meaningfully requires knowing the problem the branch is meant to solve, leaving that dependency undeclared and its absence unaddressed. | Confirmed against plugins/zirkel/workflows/explore-branches.js (lines 87-93): the Score dispatch prompt passes only the branch name and description, never the original scoped problem. Revise the fix to state this definitively rather than as an open either/or: add one sentence under 'Score' clarifying that scoring is deliberately judged from the branch's name+description alone, without the original problem statement, so a reader isn't left wondering whether an omission occurred. | sonnet |
plugins/zirkel/agents/instruction-candidate.md:10 | Q-PROC-TOOLS-ROLE | The agent grants Read, Glob, Grep but neither the Draft, Score, nor Critique job ever instructs the agent to read, search, or glob anything -- all three work purely from the task description, candidate text, and test cases handed to it in the dispatch prompt, unlike sibling agents branch-proposer.md and reasoning-path.md which explicitly justify the same tool grants in their bodies. | Confirmed against sibling agents branch-proposer.md and reasoning-path.md, which both justify their identical Read/Glob/Grep grant in-body (branch-proposer.md lines 33-38 on preferring a file_catalog.json snapshot and falling back to Glob/Grep; reasoning-path.md line 34 'Look up facts you need with Read/Glob/Grep rather than guessing'). instruction-candidate.md has no equivalent text in any of its three jobs (Draft/Score/Critique work only from the dispatch prompt's task description, candidate text, and test cases), so the grant is unexplained and per Q-PROC-TOOLS-ROLE should be the minimum the role needs. Fix as written (remove the grant, or add a body instruction justifying it) is correct and low-risk either way; simplest is to drop the tools: line entirely since none of the three jobs has an information-gathering need. | sonnet |
plugins/zirkel/agents/reasoning-path.md:33 | Q-MEANING-ASSUMED | Unlike its sibling agents (branch-proposer's 'You are given a scoped problem...', instruction-candidate's 'You are given a task description...'), reasoning-path.md never states what 'the input' actually is (a problem statement? a question with options?) before using the term here and implicitly in the 'backward from options' strategy description ('candidate answers'). | Add a sentence right after the opening paragraph (mirroring branch-proposer.md's 'You are given a scoped problem and one assigned angle.' and instruction-candidate.md's analogous line), e.g.: 'You are given a problem statement (optionally with an image or diagram, and — for the backward-from-options strategy — a set of candidate answers) and your assigned strategy.' This names the input before it's used at line 32 ('the input includes an image') and before 'candidate answers' is used implicitly under backward-from-options, without adding a new section or growing the file meaningfully (body stays well under the 500-line cap). | sonnet |
plugins/zirkel/skills/zirkel-clarify-scope/SKILL.md:15 | Q-MEANING-PROMISE | The body promises the guard validates all four required outputs, but the only Validate example shown feeds the guard flagged_uncertainties and known_facts — scoped_task and success_criteria are never passed to or checked by the guard anywhere in the file. | ⚠ Checked zirkel_lib.py: validate_clarify() only validates flagged_uncertainties and known_facts — scoped_task is a freeform restatement string and success_criteria is a list of {criterion, status} with no defined validity rule, so 'extend the guard to cover' them (the fix's first branch) has no well-specified target: what would make a scoped_task string invalid? Writing arbitrary structural checks onto genuinely open-ended prose is over-specification for content that's supposed to stay judgment-driven (Q-PROC-FREEDOM: prose direction for open-ended judgement, not invented rigidity). Drop that branch and keep only the second: reword the intro from 'Produce four structured outputs, then validate them with the guard' to 'Produce four structured outputs; validate known_facts and flagged_uncertainties with the guard (scoped_task and success_criteria have no code-level check — get them right by inspection).' That's a pure wording fix, still sonnet-tier, with no guard-code change and no new invented validation. (conflicts with Q-PROC-FREEDOM) | sonnet |
plugins/zirkel/skills/zirkel-clarify-scope/SKILL.md:37 | Q-MEANING-PROMISE | The section is headed '## The rules (enforced in code)' and every other bullet cites a specific guard-computed field, but this bullet describes model behavior at presentation time that no shown guard call (which only validates the JSON schema of clarify/known_facts/flagged_uncertainties) can actually check, so the code-enforcement promise is broken for this rule. | Verified against zirkel_lib.py's validate_clarify(): it only computes flagged/marked/must_pause from confidence and blocking fields — it has no way to check whether the model actually presented a flagged item to the user rather than silently defaulting it. The bullet's claim to be 'enforced in code' is genuinely broken for that one item, so moving it out of the '## The rules (enforced in code)' section (or adding a short 'not guard-checkable — a discipline obligation' note next to it) is the right fix and doesn't conflict with any other rubric rule. | sonnet |
plugins/zirkel/skills/zirkel-decompose-chain/SKILL.md:52 | Q-MEANING-CONTRACT | Unlike sibling phase skills zirkel-clarify-scope and zirkel-explore-branches, which each end with an explicit 'Persist (so a later zirkel-solve can reuse this)' step writing .zirkel/runs/<run-id>/state.json, zirkel-decompose-chain's Output section never states where its stage table/waves are written, so a later zirkel-solve run has no documented way to resume from a prior Decompose pass the way it can for Clarify or Explore. | ⚠ Checked zirkel-solve's Manual path: unlike Clarify (step 1) and Explore (step 2), which both open with a state-find reuse check, step 3 (Decompose) calls zirkel-decompose-chain and validates it inline with no reuse check at all — the combined pipeline state (including any dag data) is written once at the very end under 'Persisting the run'. So adding a standalone Persist step to zirkel-decompose-chain (the fix's first branch) would produce a state.json that nothing in zirkel-solve's own Decompose step ever reads back — a written artifact with no consumer, the same 'guard exists, never called' shape this repo's CLAUDE.md specifically warns about, just inverted (persist exists, never read). Take the fix's second branch only: add a line to zirkel-decompose-chain's Output section stating that stage/wave output is not separately persisted — it is captured solely inside zirkel-solve's single end-of-pipeline state-write, and zirkel-solve's Decompose step (unlike Clarify/Explore) does not attempt prior-run reuse. Only add a real Persist-and-reuse pair here if zirkel-solve's Decompose step is also updated with a matching state-find call — that pairing is a bigger, cross-file change than this single-file sonnet fix. | sonnet |
plugins/zirkel/skills/zirkel-draft-revise/SKILL.md:59 | Q-STEPS-TERMINATION | Step 4 ('Escalate if needed') names a bound (hard cap of 2 cycles) for the retry loop but never states what to do if the draft still fails a criterion after the second cycle — no escalation path (report to user, flag as unresolved, etc.) is given despite the step being titled 'Escalate'. | Confirmed against plugins/zirkel/README.md's own 'Choices made where the spec was silent' section (#6): 'If criteria still fail after two cycles, the skill reports the residual rather than looping.' The suggested fix matches this documented intent exactly — add to step 4: 'If criteria still fail after the second cycle, stop looping and report the residual (which criteria remain below threshold) to the user rather than presenting the draft as fully resolved.' No conflict with any rubric rule; this directly satisfies Q-STEPS-TERMINATION's escalation-path requirement. | sonnet |
plugins/zirkel/skills/zirkel-map-relationships/SKILL.md:9 | Q-MEANING-PROMISE | The description promises this skill can be invoked as a zirkel-solve pipeline stage, but zirkel-solve's Execute step (SKILL.md) enumerates exactly four valid stage modes — reason-verify, investigate-dynamically, ground-evidence, calibrate-format — and its stage-dispatch guard example never includes map-relationships or a graph-traversal mode, so the promised handoff has nothing on the zirkel-solve side to receive it. | ⚠ The two branches are not equally cheap and bundling them as an 'either/or' risks a sonnet-tier executor picking the harder one without the judgment it needs. Adding map-relationships as a fifth zirkel-solve stage mode touches zirkel-solve's Execute-step invariant ('one of reason-verify, investigate-dynamically, ground-evidence, calibrate-format ... never hardcode a mode in advance') and the compiled guard's stage-dispatch validation in zirkel.py -- that is a pipeline-contract change per this rubric's own tier definition ('human: changes a plugin's contract or pipeline order'), not a sonnet rewrite. Split the fix: (1) sonnet-tier default -- rewrite zirkel-map-relationships' description to drop the 'zirkel-solve stage' clause and describe standalone multi-hop use only; (2) if the maintainer actually wants the pipeline integration, open it as a separate human-tier decision covering zirkel-solve's Execute section, its stage-dispatch guard schema, and solve.js, not a description edit. | human |
plugins/zirkel/skills/zirkel-optimize-instruction/SKILL.md:50 | Q-STEPS-GATE | The candidates-guard call in step 3 states what the guard requires but never says what to do or where to return when it rejects the candidate set, unlike the sibling skills (e.g. zirkel-map-relationships' 'fix it before answering') that name the remedial action for a failed validation. | Add a sentence naming both the remedial action and a bound on it, e.g.: 'If the guard rejects the set (wrong count, missing/duplicate framing, or malformed JSON), regenerate only the missing/duplicate framing(s) and re-validate. If the guard still rejects after 2 attempts, stop and report which framing(s) could not be produced rather than looping indefinitely.' The finding's own suggested text omits a stop condition for the retry it introduces (Q-STEPS-TERMINATION); this version keeps the sibling-skill remedial-action pattern while closing that gap. | sonnet |
plugins/zirkel/skills/zirkel-summarize-trace/SKILL.md:4 | Q-MEANING-ASSUMED | zirkel-solve's own SKILL.md states its persisted state.json exists 'for a later zirkel-summarize-trace', but zirkel-summarize-trace's SKILL.md never names that file (or any other artifact) as a required input, so a reader has no way to know whether to load .zirkel/runs/<id>/state.json, pull data from conversation context, or something else. | Add an Input section naming .zirkel/runs/<id>/state.json (or its equivalent) as the required source for dag.stages, explore_ran, and the section contents, and state what to do when it's missing. | sonnet |
plugins/zirkel/skills/zirkel-summarize-trace/SKILL.md:5 | Q-MEANING-PROMISE | The description promises 'exactly 7 sections' unconditionally, but the body's own rule ('omit this section ENTIRELY if Explore did not run') and the Output line ('the 7 (or 6, if Explore was skipped) sections') make the count conditional on whether Explore ran, so the description over-promises a fixed count the body does not deliver. | Reword the description to '7 sections (6 if Explore did not run)' or similar, matching the conditional behavior stated in the body. | sonnet |
plugins/zirkel/skills/zirkel-summarize-trace/SKILL.md:14 | Q-MEANING-ASSUMED | Unlike its siblings (zirkel-optimize-instruction, zirkel-verify-assumptions), this file has no '## Input' or precondition section at all — it never states where the zirkel-solve run's data (dag_stages, explore_ran, per-section content) actually comes from (a persisted state.json path, or in-conversation context), so a reader has no named source to consume before writing the record. | ⚠ This is the same defect as finding index 2 (both point at the missing Input/source-of-run-data section in this same file), and both suggest adding an 'Input' section naming the state.json path. Applying both literally produces two overlapping Input sections at different spots in a 60-line file, which is redundant token cost with no added value. Apply only one: use index 2's fix (it is more complete — it also names dag.stages, explore_ran, and the missing-input fallback explicitly) and drop this one, or fold this finding's point ('name the source of the run data before Section 1, e.g. the state.json path written by zirkel-solve's state-write step') into that single Input section rather than authoring a second one. (conflicts with Q-CLARITY-CONCISE) | haiku |
plugins/zirkel/workflows/explore-branches.js:87 | Q-STEPS-GATE | The Score step filters out null results from parallel() but, unlike the Propose step's branches.length < 2 gate and reason-verify.js's attempts.length !== SELF_CONSISTENCY_ATTEMPTS gate, never checks scored.length against branches.length afterward — a branch whose scoring dispatch failed is silently dropped from the comparison with no error and no log, so the winner can be selected from an incomplete, unannounced subset of the proposed branches. | ⚠ The suggested fix's primary form (throw if scored.length !== branches.length) is stricter than the file's own established fault-tolerance design and would make the workflow more brittle, not just more visible: the Propose step already tolerates a reduced set via .filter(Boolean) plus a soft branches.length < 2 minimum (proceeding fine with, say, 2 of 3 requested branches). Requiring exact equality at Score would turn a single dropped scoring dispatch into a hard failure of the whole run even when enough branches remain to pick a winner — a new, more rigid failure mode inconsistent with the degradation the file already accepts one step earlier. Prefer the finding's own secondary option (log) combined with the same soft threshold already used at Propose: after filtering, if (scored.length < branches.length) log(...dropped branch names...), then if (scored.length < 2) throw new Error('explore-branches: fewer than 2 branches scored successfully') — visible on partial loss, but only fatal when selection genuinely cannot proceed. | sonnet |
plugins/zirkel/workflows/explore-branches.js:96 | Q-MEANING-CONTRACT | The Select phase sums risk directly into total (so a branch scored as higher-risk gets a higher, more-winning total) while the tie-breaker sorts ascending by risk ((a.risk - b.risk), preferring lower risk) — the output contract's meaning of Total and Risk is internally contradictory, and SCORE_SCHEMA.properties.risk never states which direction is favorable, so a caller cannot tell whether high Total or high Risk in the returned scores is good or bad. | ⚠ Take only the first branch of the suggested 'or' — invert risk before summing: s.total = s.feasibility + s.impact + (SCORE_MAX + SCORE_MIN - s.risk) — and leave the existing tie-break (a.risk - b.risk, ascending, already preferring the lower/safer risk score) unchanged, since it already agrees with that inversion once Risk is understood as 'higher = more dangerous.' Then add a one-clause direction hint to SCORE_SCHEMA.properties.risk (e.g. a description field: 'higher = more dangerous') and to the scoring prompt on line 89, so a caller reading either the schema or the prompt knows the axis's polarity without having to read the workflow's arithmetic. Do not apply the suggested fix's second alternative ('document that higher risk score means lower danger') on its own: that flips the meaning of raw risk so a high score is now 'safe,' but the sort comparator (a.risk - b.risk) still picks the numerically lowest score as the tie-break winner — under the new documented meaning that is now the most dangerous branch, the opposite of the stated intent. Applying that half of the 'or' turns a caller-visible ambiguity (Q-MEANING-CONTRACT) into an active contradiction between documented semantics and actual behavior (Q-MEANING-PROMISE) — the code would promise one thing and silently do the other, which is worse than the original unstated ambiguity. (conflicts with Q-MEANING-PROMISE) | sonnet |
plugins/zirkel/workflows/optimize-instruction.js:69 | Q-STEPS-GATE | Unlike the Generate step in this same file (which validates candidates.length !== CANDIDATE_COUNT at line 56) and unlike the analogous parallel-dispatch steps in explore-branches.js and reason-verify.js (which wrap their parallel() results in .filter(Boolean)), the Score step here has neither a .filter(Boolean) on the parallel() result nor any post-check that scored.length === CANDIDATE_COUNT — a dropped/failed dispatch returning null would crash inside .then((s) => ({ ...c, score: s.passed })) with an opaque TypeError instead of a named, gated error. | Fix is correct as written: mirror the Generate step's existing pattern exactly — .filter(Boolean) on the parallel() result, then if (scored.length !== CANDIDATE_COUNT) throw new Error(...). Since APE requires all 5 framings to be scored (the winner is picked from a fixed, complete set, same as Generate's own exact-coverage requirement), an exact-count gate here is the right predicate, not an over-strict one. No rubric conflict. | sonnet |
plugins/zirkel/workflows/reason-verify.js:33 | Q-MEANING-ASSUMED | parsedArgs carries only a hasImageOrDiagram boolean and a task string — there is no field anywhere in this file for actual image or diagram content — yet when the flag is set every Attempt agent is told to 'describe the visual, then reason', leaving nothing concrete for it to describe unless the caller silently embedded it in the task string. | Drop the schema-change branch of the fix; keep only the documentation branch. The workflow already has a channel for visual content -- task is a free-text string, and the SKILL.md's documented args contract is { task, hasImageOrDiagram? } with no separate content field, so adding a new parsedArgs field would also require updating that contract in plugins/zirkel/skills/zirkel-reason-verify/SKILL.md (and any guard schema in scripts/zirkel.py) for no real gain. Instead, make the existing channel explicit: in reason-verify.js line 49, change the multimodal instruction to something like 'Apply Multimodal-CoT: the visual is described in the task text below -- read that description, then reason.' and add one sentence to SKILL.md's 'args { task, hasImageOrDiagram? }' line stating that when hasImageOrDiagram is true, task must include a textual description of the image/diagram content. This closes the Q-MEANING-ASSUMED gap (the precondition is now named, with no silent assumption) without touching the calling contract. | sonnet |
plugins/zirkel/workflows/solve.js | Q-MEANING-CONTRACT | phasesRun records 'Revise' as having run unconditionally, before the criteria.length >= 3 gate is even checked, so when that gate fails the returned phases_run contract falsely claims Revise executed even though no scoring or revision happened. | Same one-line relocation as the duplicate finding on this same defect (index 1): move the phasesRun.push('Revise') statement inside the criteria-length gate. | haiku |
plugins/zirkel/workflows/solve.js:22 | Q-MEANING-PROMISE | Execute's stage prompt has the agent self-report which named technique it 'used' (mode: one of MODES, matching the real sibling workflow zirkel-reason-verify by name), but the script only ever makes a single generic agent() call per stage — it never actually dispatches zirkel-reason-verify's real mechanism (3 isolated strategy attempts + majority vote, as implemented in reason-verify.js), so a stage reporting mode:'reason-verify' has not actually run self-consistency. | The suggested fix offers two disjunctive paths of very different cost (rewire Execute to actually dispatch zirkel-reason-verify's real 3-attempt/majority-vote mechanism for that mode, vs. simply stop reusing that exact name). Collapse to the cheaper, lower-risk path as the primary fix: rename the MODES entries (or otherwise decouple the label from the sibling skill's name) so 'reason-verify' as reported here is not read as an invocation of zirkel-reason-verify's real mechanism. Reserve the actual-dispatch option only if the intent is genuinely to run that heavier mechanism per stage, in which case it is a pipeline-shape change, not a prompt tweak. | sonnet |
plugins/zirkel/workflows/solve.js:121 | Q-MEANING-CONTRACT | zirkel-solve returns two structurally different output shapes — PAUSED_AWAITING_USER (status, reason, scoped_task, blocking_uncertainties, next) versus COMPLETE (status, scoped_task, explore, stage_plan, waves, composed_result, revised, phases_run) — but the meta header only hints 'pause if any block' in the Clarify phase detail without stating the paused shape's actual fields anywhere, so a caller integrating with this workflow cannot find the full output contract in one place. | Add a short comment near the meta block (or immediately above the early PAUSED_AWAITING_USER return) enumerating both possible return shapes and the fields each carries. | sonnet |
plugins/zirkel/workflows/solve.js:121 | Q-MEANING-CONTRACT | The file's meta.description and meta.phases document only the linear Clarify->Explore->Decompose->Execute->Revise flow ending in the status: 'COMPLETE' return shape at the bottom of the file, but the Clarify phase can instead return this entirely different PAUSED_AWAITING_USER shape with different fields (blocking_uncertainties, next) — the two possible output contracts are never stated together anywhere a caller can find them in one place. | Add the PAUSED_AWAITING_USER early-exit shape to meta (or a comment above the Clarify gate) alongside the COMPLETE shape so both output contracts are documented together. | sonnet |
plugins/zirkel/workflows/solve.js:140 | Q-MEANING-ASSUMED | The priorExplore reuse path (like the sibling requestedBranches/maxBranchCount forwarding a few lines below) reads raw args instead of parsedArgs, so when args arrives as a JSON string the reuse check args?.priorExplore is always falsy and the workflow silently re-dispatches a fresh zirkel-explore-branches run instead of reusing the prior one, contrary to the comment above it describing exactly this reuse contract. | Change both args?.priorExplore and args.priorExplore to read from parsedArgs so the reuse path actually fires when args is passed as a JSON string. | haiku |
plugins/zirkel/workflows/solve.js:144 | Q-MEANING-ASSUMED | priorClarify, priorExplore, requestedBranches, and maxBranchCount are read from the raw args parameter instead of the normalized parsedArgs that normalizeArgs produces at the top of the file specifically to handle callers that pass args as a JSON string; when args does arrive as such a string (the exact case the file's own error message anticipates: 'pass args as an object in the tool call, not JSON.stringify(...)'), args?.priorClarify etc. silently evaluate to undefined on the string, so the prior-run reuse and branch-count overrides are dropped without any error — the same 'looks correct, silently does nothing' shape this repo's own CLAUDE.md calls out. | Read priorClarify, priorExplore, requestedBranches, and maxBranchCount from parsedArgs (not the raw args) everywhere they are used in solve.js, matching how rawTask, multipleApproaches, and successCriteria already do it. | haiku |
plugins/zirkel/workflows/solve.js:144 | Q-MEANING-ASSUMED | This call dispatches zirkel-explore-branches using the raw args (not the file's own parsedArgs normalization), so when args arrives as a JSON string — the exact case normalizeArgs exists to handle — args.requestedBranches and args.maxBranchCount silently evaluate to undefined and the caller's branch-count request is dropped with no error. | Read these two fields from parsedArgs instead of args, matching every other args access in this file. | haiku |
plugins/zirkel/workflows/solve.js:197 | Q-CLARITY-AMBIG | The Execute-stage prompt requires the agent to pick one of the four MODES by name only ('reason-verify', 'investigate-dynamically', 'ground-evidence', 'calibrate-format') without ever defining what operationally distinguishes them, so the choice is made by guessing at the label rather than an understood criterion. | ⚠ Applying this fix in isolation would likely deepen finding index-1's Q-MEANING-PROMISE violation rather than fix the ambiguity: 'a short inline definition of each of the four MODES' for a name like 'reason-verify' will naturally be written to match what that name means elsewhere in the plugin (zirkel-reason-verify's real 3-strategy majority-vote procedure), making the Execute prompt describe rigor the script still never performs (only a single generic agent() call per stage) — now explicitly, instead of just by name collision. Sequence this after resolving index-1: if the modes are renamed/decoupled from the sibling skills' names (as recommended there), define those decoupled labels narrowly as informal self-report categories (e.g. 'reason-verify: you sanity-checked your own output for internal consistency before finalizing') rather than restating the sibling mechanism's actual steps. If instead index-1 is fixed by genuinely dispatching the sibling workflow per mode, no separate inline definition is needed here — the real workflow's own prompt already supplies it. (conflicts with Q-MEANING-PROMISE) | sonnet |
plugins/zirkel/workflows/solve.js:198 | Q-MEANING-CONTRACT | Each stage declares an output_contract string in Decompose, but Execute only ever echoes that string into the next agent's prompt as instruction text — the actual produced output is never checked against the contract it declared before being stored in results and handed to downstream stages as "upstream outputs", so a stage that ignores its own stated output contract is passed through undetected. | ⚠ The fix as written ('validate ... before storing it in results') only names a check, not a gate: it never says what happens when a stage's output fails to satisfy its own output_contract, so implementing it literally adds a new failure path with no defined resolution (silently pass anyway? throw? retry?) — itself a Q-STEPS-GATE violation layered on top of the one being fixed. A complete fix must specify the gate: e.g. extend STAGE_SCHEMA (or a lightweight code-level check) to assert the declared output_contract's shape, and on mismatch either retry that stage once (bounded, per Q-STEPS-TERMINATION) or record contract_violation: true on that stage's result and surface it in the final phases_run/results payload rather than storing an unverified output as if confirmed. This is a redesign of the Execute wave loop's control flow, not a local edit. (conflicts with Q-STEPS-GATE) | opus |
plugins/zirkel/workflows/solve.js:215 | Q-MEANING-CONTRACT | phasesRun.push('Revise') (line 211) executes unconditionally before this gate, so the returned phases_run contract always lists 'Revise' as having run even when fewer than 3 criteria exist and the entire revise block — including the actual scoring/rewriting work — is skipped, leaving revised: null silently contradicting the phase log. | Move phasesRun.push('Revise') to inside the if (Array.isArray(criteria) && criteria.length >= 3) block (after the loop, once revision actually ran), leaving the unconditional phase('Revise') log call where it is. This is a one-line relocation with no judgment involved. | haiku |
plugins/zeugnis/skills/zeugnis-agentic-reliability/SKILL.md:37 | Q-STEPS-GATE | Step 4 states the script 'rejects' a miscategorized finding but never says what the dispatching agent should do when that happens (retry the Find pass, drop the finding, or halt), leaving a step that can fail with no named gate or return path. | ⚠ The suggested fix's premise is factually wrong: agentic_reliability.py's validate_finding never causes a non-zero exit for a bad category. On InvalidFindingError it prints 'WARNING: dropping invalid finding: ...' to stderr, silently drops that one finding via continue, and the script still returns 0 (see lines 79-85, 138). Instructing the dispatching agent to watch for 'the script exits non-zero' would bake a false claim about the script's behavior into the SKILL.md, which is worse than the current silence. Revised fix: after step 4, add 'Check the script's stderr for any WARNING: dropping invalid finding lines — the script drops a miscategorized finding silently and still exits 0, so a dropped finding will not otherwise be visible. If any appear, re-dispatch Find mode with the category constraint restated rather than trusting the reported finding count as complete.' (conflicts with Q-MEANING-PROMISE) | sonnet |
plugins/zeugnis/skills/zeugnis-code-change/SKILL.md:35 | Q-MEANING-PROMISE | The skill's framing promises that 'each Find-phase pass here is a single, un-verified check' distinguishing it from the fully-verified zeugnis-*-audit skills, but the dependency_audit step tells the caller to simply run scripts/dependency_audit.py directly — which (per zeugnis-dependency-audit's own SKILL.md) performs its mandatory-unless-flagged independent verification pass by default — so the dependency_audit domain in this 'lightweight, un-verified' pass is actually fully verified while the other three domains are not, contradicting the skill's own description of uniform rigor. | ⚠ Don't replace the script invocation with an agent-only dispatch — the dependency_audit.py script itself performs the actual bounded registry lookups and typosquat heuristic; the dependency-auditor agent (per zeugnis-dependency-audit step 3) is only an optional supplementary judgment layer for ambiguous names, not a substitute for the deterministic check. Swapping in 'dispatch the agent in Find mode only' would drop real registry verification and replace it with agent guesses, weakening this domain rather than aligning its rigor with the other three. Instead, change zeugnis-code-change step 3's dependency_audit line to call scripts/dependency_audit.py <repo_root> --skip-verification (omitting the mandatory independent re-check, not the lookup itself) so this domain's rigor genuinely matches the pass's single-check, advisory framing — and keep the optional agent dispatch, if used, scoped as a supplement to that call rather than a replacement for it. | sonnet |
plugins/zeugnis/skills/zeugnis-cycle/SKILL.md:46 | Q-STEPS-GATE | The step treats every non-zero exit from cycle_engine.py plan-next-pass as proof the pass cap was reached, silently folding other failure modes (bad repo_root, corrupted ledger, invalid --mode) into the same benign "stop and wrap up" path instead of naming how to tell the two apart. | Have the step check the script's stderr/exit code for the specific cap-reached signal and name a different, explicit path (surface the error to the user) for any other non-zero exit. | sonnet |
plugins/zeugnis/skills/zeugnis-cycle/SKILL.md:98 | Q-CLARITY-AMBIG | The per-finding outcome enum has no value for a 'propose'-mode finding that was merely described (step 5's propose branch explicitly never dispatches zeugnis-remediator and only narrates a proposed fix), so a reader following step 6 in propose mode cannot tell what outcome to record — and since the wrap-up logic treats 'zero fix/draft outcomes' as convergence, an all-propose pass may look converged on its first pass regardless of how many findings were actually described. | ⚠ Do not add a fifth outcome literal. lib/ledger.py's apply_outcome() is an if/elif chain over exactly "fixed"/"blocked"/"drafted"/"reopened" with no else and no validation — record_pass_result would accept a "proposed" value silently (no exit-2 rejection, matching the other three's explicit checks), pass it through, and apply_outcome would fall through every branch leaving the finding's status untouched and not counted in fix_or_draft_outcomes. That is a documentation change with no matching engine support, in a file that states its entire job is to accurately describe scripts/cycle_engine.py's behavior. Instead, clarify step 5/6 to say a propose-mode-described fixable finding is recorded with the existing "blocked" outcome and a blockReason of e.g. "not applied: cycle is in propose mode" (this already matches apply_outcome's blocked/drafted branch: status stays open, and the engine's existing non-empty-blockReason check is satisfied) — and add a sentence that convergence in propose mode should not be read as 'nothing needs attention', since propose-mode passes will structurally always show fix_or_draft_outcomes derived only from any assertion-audit drafts. (conflicts with Q-MEANING-CONTRACT) | sonnet |
plugins/cupertino/agents/handbook-drift-auditor.md:15 | Q-PROC-TOOLS-ROLE | The agent's stated role explicitly forbids looking beyond the exact target-file list it is dispatched with, yet its tool grant includes Glob — a file-discovery-by-pattern tool with no legitimate use once the file set is already fixed — so the grant exceeds what the role needs and dangles the exact scope-creep capability the body refuses. | Drop Glob from the tools: frontmatter, leaving Read, Grep, Bash, since target files are always named explicitly and never discovered. | haiku |
plugins/cupertino/references/parallel-safe-research-protocol.md:83 | Q-CLARITY-AMBIG | "the run coordinator" and (line 84) "the assigned Git worktree" are used with no antecedent anywhere in this file, and none of cupertino's own skills (handbook-draft/check, which are this file's actual callers) mention a run coordinator or worktree assignment, leaving a reader of this synced-in reference unable to tell who/what these refer to in cupertino's context. | ⚠ The file's own header states it is synced byte-identical into every plugin's references/ directory from a single canonical source (tools/symbol-indexer/parallel-safe-research-protocol.md) and regenerated via rrt artifacts --regenerate — editing the copy at plugins/cupertino/references/parallel-safe-research-protocol.md directly, as the finding's file path implies, will be silently reverted by the next regenerate and will desync the six-plus plugin copies in the meantime. Apply the fix (defining 'run coordinator'/'assigned Git worktree' in generic, plugin-agnostic terms, since the doc must stay identical across all consuming plugins — not cupertino-specific wording) to the canonical source file, then run rrt artifacts --regenerate so it propagates identically to every plugin's synced copy. | sonnet |
plugins/cupertino/skills/cupertino-council/SKILL.md:3 | Q-MEANING-ASSUMED | cupertino-backwards/SKILL.md states that a PreToolUse hook blocks cupertino-council until the backwards-done marker exists ("cupertino-focus, cupertino-longevity, cupertino-integrate, cupertino-council) is blocked by a PreToolUse hook until this marker exists"), but cupertino-council's own file never mentions this gate, its trigger condition, or what to do if denied — unlike cupertino-focus, which documents the identical gate itself ("This skill is gated: a PreToolUse hook blocks it until cupertino-backwards has run in this repo"). | Add a paragraph to cupertino-council/SKILL.md mirroring cupertino-focus's gate disclosure: state that a PreToolUse hook blocks dispatch until cupertino-backwards has run and set the backwards-done marker, and instruct running cupertino-backwards first on denial. | sonnet |
plugins/cupertino/skills/cupertino-council/SKILL.md:3 | Q-MEANING-ASSUMED | cupertino-backwards/SKILL.md's own gate step names cupertino-council as one of the skills a PreToolUse hook blocks until the backwards-done marker exists ('cupertino-focus, cupertino-longevity, cupertino-integrate, cupertino-council'), but cupertino-council's SKILL.md never mentions this precondition or that dispatch can be denied, unlike cupertino-focus which documents its own equivalent gate in its own file. | ⚠ This is the same underlying gap as finding index 1 (cupertino-council/SKILL.md omits the PreToolUse gate that cupertino-backwards's file says blocks it), evidenced by a different quote from the same file. Applying both index 1's and index 2's suggested_fix independently would insert the same gate-disclosure paragraph into cupertino-council/SKILL.md twice, producing a duplicated/redundant paragraph. Treat this as one finding: apply only index 1's fix (add a single paragraph mirroring cupertino-focus's gate-disclosure sentence) and drop this one, or merge them into a single edit instruction before dispatching to a fixer. (conflicts with Q-CLARITY-CONCISE) | sonnet |
plugins/cupertino/skills/cupertino-council/SKILL.md:3 | Q-CANN-NEGATIVE | cupertino-council's description overlaps with cupertino-backwards' trigger for a brand-new, tech-undecided feature ('design a way for users to...', 'we need a feature that...') but never names backwards or excludes that case, even though cupertino-backwards' own SKILL.md states a PreToolUse hook blocks council ('cupertino-focus, cupertino-longevity, cupertino-integrate, cupertino-council') until the backwards marker exists — so a router reading only council's description can dispatch it for a new-scope UI request and hit a hook denial instead of being routed to cupertino-backwards first, unlike cupertino-focus, whose description already says 'Use immediately after cupertino-backwards has established the customer experience.' | Add a negative-trigger clause to cupertino-council's description (mirroring cupertino-focus's) noting it should not be invoked for a not-yet-scoped new feature until cupertino-backwards has established the experience statement. | sonnet |
plugins/cupertino/skills/cupertino-council/SKILL.md:24 | Q-STEPS-GATE | Step 4's mechanical tension-order validation has no stated failure path — unlike step 4 of cupertino-focus and step 4 of cupertino-backwards, nothing tells the model what to do if the resolved order violates the fixed precedence. | Add an explicit instruction after the validator call: if the check fails, return to step 3 and re-resolve the offending tension using the fixed precedence order before proceeding. | sonnet |
plugins/cupertino/skills/cupertino-elevate/SKILL.md:17 | Q-STEPS-TERMINATION | The status-flip check sends the model back to step 3 on failure with no bound on how many times to retry the transfiguration and no escalation path if a satisfying answer never emerges. | In step 4, after "go back to step 3, don't ship a half-measure," add: "If two revisions still fail the status-flip check, stop iterating and tell the user this feature may not be a good elevate candidate rather than looping indefinitely." This is a small, local addition (file is 24 lines, well under the 500-line body cap) and doesn't touch the rest of the step sequence. | sonnet |
plugins/cupertino/skills/cupertino-handbook-check/SKILL.md:14 | Q-STEPS-GATE | The handbook-check-active flag is set in step 3 and only cleared in step 6 'when done'; no step names what happens to the flag if step 4 or step 5 (rule extraction, the Workflow dispatch) fails or is aborted, so a failed run can leave the repo permanently stuck with the check marked active. | ⚠ This is the same defect as finding index 2 (missing failure/interrupt handling for the handbook-check-active flag), just anchored to the step-3 quote instead of the step-6 quote. Applying both suggested fixes literally would insert two separate, near-identical 'clear the flag on failure' sentences into a 32-line SKILL.md — redundant prose that restates the same guidance twice. Apply the fix once, at step 6 (see index 2's revisedFix), and do not add a second cleanup instruction near step 3; step 3 only needs to stay as the 'set active' step. (conflicts with Q-CLARITY-CONCISE) | sonnet |
plugins/cupertino/skills/cupertino-handbook-check/SKILL.md:22 | Q-STEPS-GATE | Nothing in the procedure covers clearing handbook-check-active if step 5's Workflow run fails or is interrupted, so a failed check can leave the write/no-mutation guarantee flag stuck active and block subsequent runs that depend on it. | Rewrite step 6 to cover both the normal and failure paths in one place, e.g.: 'Clear the active flag when done — and immediately, before stopping, if step 4 or step 5 fails or is interrupted, rather than leaving it set:' followed by the existing state.py clear handbook-check-active command. This supersedes/absorbs index 0's suggestion so the guidance is stated once, not twice. | sonnet |
plugins/cupertino/skills/cupertino-handbook-draft/SKILL.md:20 | Q-STEPS-GATE | The re-verification gate names no outcome for a rule that fails it — cupertino-handbook-check states explicitly that unverified findings 'are dropped, not reported', but this file never says whether a failed dimension rule is dropped, rewritten, or marked scaffolded. | State the failure outcome explicitly, e.g. 'a candidate rule that fails re-verification is discarded and the dimension is written as scaffolded rather than analyzed.' | sonnet |
plugins/cupertino/skills/cupertino-handbook-fix/SKILL.md:36 | Q-STEPS-GATE | Step 5's fix pipeline can fail or error partway, but nothing states what to do in that case (retry? abort? still clear the flag?), unlike cupertino-review which explicitly handles 'success, early stop, or the user cancels' for its own flag cleanup — here step 6 unconditionally says clear the flag with no branch for a failed step 5, risking the fix-active flag (which blocks all commits/pushes) staying set indefinitely after a failure. | After step 5 add an explicit branch: 'If the workflow in step 5 errors, is interrupted, or any verifier rejects a fix, still run step 6 to clear handbook-fix-active before reporting the failure — do not leave the flag set on a failed or partial run. Only skip step 6 if the state file itself cannot be written, in which case tell the user to clear handbook-fix-active manually.' This keeps the fix local to steps 5-6, states the gate condition and the fallback, and matches the escape-hatch pattern cupertino-review already uses for its own flag cleanup on 'success, early stop, or the user cancels.' | sonnet |
plugins/cupertino/skills/cupertino-review/SKILL.md:107 | Q-CLARITY-AMBIG | This sentence tells the reader to render the diagram "before the end-of-run flag cleanup below," but the actual cleanup bash block (the for f in review-pipeline-active backwards-done ... loop) is placed earlier in the file, under the "Before you start" heading — not below this section — so a careful reader has no cleanup block to point at at this location and could mistakenly re-run the earlier one immediately instead of holding it for the true end of the run. | Move the 'Clear every flag when you finish' bash block (currently lines 33-36, inside 'Before you start') into a new subsection placed after 'Render the flow diagram before cleanup', so 'below' becomes physically true; leave the explanatory sentence about review-pipeline-active/the PreToolUse hook where it is in 'Before you start' (it's about what setting the flag does, not about when to clear it). This also fixes the incidental oddity of a cleanup-on-finish instruction living under a 'Before you start' heading. | sonnet |
plugins/cupertino/skills/cupertino-review/SKILL.md:108 | Q-STEPS-GATE | The flow-diagram render step is called out as the last chance to capture stage data before cleanup, yet the skill names no gate or fallback for the case where build_review_flow_html.py itself fails — the pipeline gives no instruction to halt before clearing flags if the render errors. | Add, immediately after the build_review_flow_html.py command block: 'If this command exits non-zero, stop here and report the failure before running the end-of-run flag cleanup below — do not clear the flags on a failed render, since that would discard the only remaining copy of every stage's persisted content.' This matches the stop-and-fix gate pattern already used earlier in the file for a false check. | sonnet |
plugins/cupertino/workflows/handbook-check.js:119 | Q-STEPS-GATE | A rule whose find-step fails or returns nothing is silently dropped by filter(Boolean) with zero logging, unlike its sibling workflows: handbook-draft.js explicitly logs when a dimension fails to produce a candidate ('dimension(s) failed to produce a candidate and were dropped'), and handbook-fix.js logs skippedCount -- handbook-check.js has no equivalent check comparing perRule.length to rules.length, so an entire rule silently receiving zero coverage looks identical to a rule that was checked and found clean. | ⚠ Per the workflow runtime's pipeline() semantics (documented in workflow-authoring: 'A stage that throws drops that item to null and skips its remaining stages'), pipeline() never shrinks its result array on a failed item -- it nulls that item's slot in place. So perRule.length is always equal to rules.length, whether or not any rule's find-step failed; comparing the two lengths is a no-op that will always report zero dropped rules, which is a guard that looks like it checks something but never can (exactly the failure shape this repo's own CLAUDE.md warns about repeatedly). The correct diagnostic counts null entries in perRule directly: const droppedRules = perRule.filter((r) => r == null).length; if (droppedRules > 0) log(${droppedRules} rule(s)' find-step failed and were dropped.) placed before the existing perRule.filter(Boolean).flat().filter(Boolean) line. | sonnet |
plugins/cupertino/workflows/handbook-fix.js:65 | Q-MEANING-ASSUMED | The only gate the code enforces on incoming findings is mechanical === true; there is no check that a finding actually survived handbook-check.js's independent re-verification (verification.verdict === 'confirmed'), so unverified or already-refuted findings passed into this workflow's findings arg would be silently remediated as if confirmed, with the description and comments never stating that only verified/confirmed input is acceptable. | The suggested f.verification && f.verification.verdict === 'confirmed' check is well-founded: handbook-check.js's own confirmed output (what this workflow's findings arg is meant to receive) attaches exactly { ...f, rule, dimension, verification: {location, verdict, note} }, so the shape isn't invented. Fold it into the existing mechanical === true filter as a single combined predicate, and extend the existing skippedCount accounting (and its log message) to say how many were skipped for each reason (non-mechanical vs. not independently confirmed) rather than adding a second, separately-silent filter alongside the first -- otherwise this fix reintroduces finding 0's exact 'legitimate empty result vs. missing/invalid input' ambiguity for the new predicate. | sonnet |
plugins/cupertino/workflows/handbook-fix.js:67 | Q-MEANING-ASSUMED | Unlike handbook-check.js and handbook-draft.js, which throw a named error when a required arg (rules/targetFiles/domain) is missing, handbook-fix.js never validates that args.findings was supplied at all -- a caller who forgot to pass findings silently produces the identical []/skippedCount=0 path as a legitimate 'zero findings were mechanical' outcome, so the log message 'No mechanical findings to fix (0 non-mechanical finding(s)...)' misreports a missing-input error as a normal empty result. | Add if (!NORMALIZED_ARGS || !Array.isArray(NORMALIZED_ARGS.findings)) throw new Error('cupertino-handbook-fix: no findings supplied') before the mechanical/verification filters, matching the throw-on-missing-required-arg style already used in handbook-check.js for rules/targetFiles. | sonnet |
plugins/cupertino/workflows/handbook-fix.js:77 | Q-MEANING-ASSUMED | Clustering and the remediation prompt depend on every finding carrying a .rule string, but .rule is not part of the FIND_SCHEMA a findings object must satisfy elsewhere (required: ['file','line','title','severity','evidence','mechanical','suggestedFix']) -- it is only present because handbook-check.js's own pipeline happens to merge it in -- so findings supplied any other way silently produce a file::undefined cluster key and a RULE: undefined prompt with no error. | Always validate and throw (drop the 'or document it as required' alternative -- documentation alone doesn't stop the runtime file::undefined / RULE: undefined bug): before clustering, filter or fail on any finding missing a non-empty .rule string, throwing cupertino-handbook-fix: finding missing required 'rule' field (file:line) naming the offending finding, since .rule is only present today because handbook-check.js happens to merge it in and is not enforced by any schema this workflow controls. | sonnet |
plugins/lehre/agents/conformance-remediator.md:3 | Q-MEANING-ASSUMED | The description states two independent gating preconditions for dispatch — the finding must be 'already-verified' AND 'brief-approved' — but the Rules section only ever defensively re-checks verification ('Refuse an unverified finding. If the dispatch does not state the finding was verified, stop.') and never checks or names a fallback for a dispatch that omits or lies about brief-phase approval, so an unapproved fix can be applied by an agent whose whole safety model is 'the human approved this'. | Add a rule mirroring the unverified-finding refusal, e.g. 'Refuse a finding whose dispatch does not state which approved brief phase it came from', so the agent enforces both stated preconditions instead of only one. | sonnet |
plugins/lehre/agents/pattern-investigator.md:22 | Q-PROC-TOOLS-ROLE | The body instructs the agent to prefer the serena MCP's find_symbol/find_referencing_symbols over grep, but frontmatter grants only tools: Read, Glob, Grep, Bash — no serena tool — so the agent will silently fall back to grep every time, exactly the silent-degradation shape this repo's own CLAUDE.md warns about. | ⚠ The suggested fix offers 'add serena to tools:' as an equally valid alternative to removing the instruction -- it isn't. pattern-investigator is a portable lehre-plugin agent meant to be dispatched against arbitrary target codebases (per its own description: 'Brownfield only'), not exclusively against the werkstoff repo where serena happens to be a connected MCP. Formally granting serena in this agent's tools: frontmatter would hard-wire a session-specific, optional MCP into a leaf component that most consuming repos/sessions won't have connected, and it isn't even clear serena's MCP tool names qualify as 'known tool names' under M-AGENT-TOOLS-SHAPE's comma-separated-string contract for agent tool grants. That both trades one silent-degradation risk for an undocumented environmental precondition (Q-MEANING-ASSUMED) and doesn't guarantee the fallback problem is actually fixed when serena isn't connected. Keep only the second option: delete or rewrite the 'Prefer the serena MCP over grep...' paragraph so the guidance matches what's actually granted (Read, Glob, Grep, Bash) -- e.g. 'Grep for the symbol name, then Read each match to tell a real call site from a prose mention or comment' -- with no reference to serena/find_symbol/find_referencing_symbols. (conflicts with Q-MEANING-ASSUMED) | sonnet |
plugins/lehre/agents/spec-fidelity-auditor.md:27 | Q-MEANING-ASSUMED | The body names three inputs as given but never states what happens if one is missing — a real gap for its own listed 'Direct question' trigger ('Is the adapters unit actually finished?'), where a caller may have no recorded intent or decomposition artifact for the unit at all, since those are optional upstream outputs, not guaranteed ones. | ⚠ The two named inputs fail differently and the fix as written collapses them into one blanket refusal, which throws away the agent's existing per-clause partial-verdict design (the worked example already reports individual clauses as N/A rather than aborting the whole audit). Split the fallback: (1) if no recorded intent can be produced, the agent has nothing to audit against and must say so and decline to verdict entirely; (2) if recorded intent exists but no owns/must not know lines are given, the agent should still check every intent clause it can, and report the must-not-know section as 'UNVERIFIABLE -- no owns/must-not-know lines given' rather than inferring boundaries from the code -- not refuse the whole verdict when the intent-clause check is still possible. (conflicts with Q-STEPS-PREDICATE) | sonnet |
plugins/lehre/skills/lehre-brief/SKILL.md:90 | Q-MEANING-CONTRACT | The header field or format that records partial phase approval is never specified anywhere: this file's own worked frontmatter example (lines 46-53) shows only a single status: awaiting-approval key with no per-phase field, and lehre-conform's authorization step ("Proceed only if its header says the phase is approved") reads the same undefined field, so the two skills' shared artifact contract has a gap neither file closes. | Add approved_phases: [] to the LEHRE_BRIEF.md frontmatter example (lines 46-53), and add one sentence to the Rules/step-6 text saying who populates it: since approval happens in chat rather than by direct file edit, lehre-brief itself must rewrite the header's approved_phases list (and update status) when it resumes after the user approves, before handing off. Then have lehre-conform's step 1 'Brownfield' bullet read that exact field ('Proceed only if the current phase number appears in approved_phases') instead of the vague 'its header says the phase is approved'. Naming the field alone (as the original fix does) closes most of the gap; naming who writes it closes the rest, since neither file currently says that either. | sonnet |
plugins/lehre/skills/lehre-codify/SKILL.md:41 | Q-STEPS-GATE | Step 5's rule-critic dispatch is the ruleset's only quality gate against fabricated evidence and forced-consistency rules, but the step names no failure path if the dispatch errors or returns nothing, unlike step 7 which explicitly says what to do when validation fails. | Add a line stating what to do if rule-critic's dispatch fails or is unreachable (e.g. stop and report, do not proceed to write the ruleset unreviewed). | sonnet |
plugins/lehre/skills/lehre-codify/SKILL.md:63 | Q-STEPS-TERMINATION | The fix-then-revalidate loop implied by 'if it fails, fix the ruleset before finishing' names no bound on attempts and no escalation path if the ruleset genuinely cannot be made to validate. | State a bound (e.g. 'after one fix attempt still fails, stop and show the validator error to the user rather than retrying indefinitely'). | sonnet |
plugins/lehre/skills/lehre-conform/SKILL.md:13 | Q-MEANING-ASSUMED | Step 1's greenfield path only handles a unit that exists in the ruleset and is reported blocked; it never states what to do when .lehre/ruleset.json has no units at all (lehre-decompose never ran) or the named unit id isn't present, unlike lehre-brief's explicit 'Refuse without inputs' step for the same class of precondition. | ⚠ Keep the existing flow (name the unit, then run lehre_cli.py status) rather than checking 'before proceeding to status' as the suggested fix says — status is the only source that reveals the unit list (cmd_status prints every declared unit's state; it takes no unit argument and handles zero units fine by printing an empty table), so there is no artifact available to gate on before that call runs. Instead, extend the existing bullet: after running status, if the named unit does not appear among the printed rows (or the ruleset has no units at all), say so and stop — do not proceed to step 2 — rather than only handling the blocked case as the current text does. (conflicts with Q-STEPS-INPUTS) | sonnet |
plugins/lehre/skills/lehre-decompose/SKILL.md:34 | Q-STEPS-GATE | Step 4 says to 'reject any ordering that needs a cycle' but never names where to return or what to do next when the derived seams genuinely require one (redefine seams in step 3? ask the user?), leaving the failure path of this gate undefined. | Add 'if no acyclic ordering exists, return to step 3 and re-derive the seams, or surface the conflict to the user' to name the gate's destination. | sonnet |
plugins/lehre/skills/lehre-pin/SKILL.md:44 | Q-STEPS-INPUTS | Step 5 directs tests at 'the seams the decomposition declared' but no earlier step in this file names or reads the decomposition artifact (unlike lehre-validate, which explicitly names .lehre/ruleset.json as the source before referencing seams) — the input is consumed without ever being produced or named. | State explicitly in step 5 that seams are read from .lehre/ruleset.json (or wherever the decomposition is recorded), the same way lehre-validate names its source. | sonnet |
plugins/lehre/skills/lehre-pin/SKILL.md:91 | Q-STEPS-GATE | The precondition that a unit must already be validated by lehre-validate before pinning is stated only in the trailing Rules list, not as a gate inside the numbered Steps 1-6, so nothing in the executable procedure actually checks or names where to return if the unit is unvalidated. | Add a step 0 that checks .lehre/units/<unit-id>.done exists (or run python3 "${CLAUDE_PLUGIN_ROOT}/scripts/lehre_cli.py" status) before emitting the CI check in step 1. If the marker is absent, stop and name lehre-validate as the skill to run first, rather than the vaguer 'checks the unit/phase has passed lehre-validate' — this repo's own convention (confirmed in lehre-validate's step 4 and lehre-pin's own step 3) is to name the concrete artifact/command, not the upstream skill's name alone. | sonnet |
plugins/befund/agents/business-rules-miner.md:6 | Q-PROC-TOOLS-ROLE | The agent explicitly states "Do not modify files -- this is read-only" and its whole job is mining/citing executable code via Read/Glob/Grep, yet it also holds Bash with no responsibility, trigger, or refusal ever naming a command it runs. | Remove Bash from the frontmatter tools list (tools: Read, Glob, Grep, Bash -> tools: Read, Glob, Grep). The body's three responsibilities (mine executable logic via Read/Glob/Grep, act as citation referee re-reading a cited file:line, act as P0-panel judge voting from an independent read) never invoke or describe a shell command, and 'Must refuse' explicitly states 'this is read-only' -- Bash is the only granted tool that contradicts that self-description. No output-format or workflow text depends on shell access, so trimming it is a pure deletion with no follow-on edits needed elsewhere in the file. | haiku |
plugins/befund/agents/complexity-surveyor.md:24 | Q-MEANING-CONTRACT | The body instructs trying gocyclo (and other per-language tools via "etc.") for cyclomatic complexity, but the Output format contract's tool_used field only enumerates "radon" | "lizard" | "none", leaving no valid value to report when gocyclo or any other named tool is actually the one used. | Generalize tool_used to an open string (rather than enumerating every per-language tool by name) since the body's must-try list is explicitly open-ended ("etc.") -- enumerating gocyclo alone would leave the same gap for the next unnamed language tool. Note in Output format that tool_used should be the actual tool name used, or "none". | sonnet |
plugins/befund/agents/complexity-surveyor.md:32 | Q-CLARITY-AMBIG | Must-refuse says to report "-1 or 0" when a tool is unavailable, but the output-format contract only ever defines "N or -1" for mean_ccn/max_ccn and core responsibility 3 only ever mentions "-1" -- a careful reader could legitimately emit either sentinel, producing inconsistent output across runs. | Remove "or 0" from the must-refuse line so it matches the single -1 sentinel used everywhere else in the output contract. | sonnet |
plugins/befund/agents/convention-auditor.md:6 | Q-PROC-TOOLS-ROLE | Like business-rules-miner and arch-health-auditor, convention-auditor holds Bash even though its Extract-Find-Verify procedure and "Do not modify files -- this is read-only" refusal describe only reading documented-rule sources and re-reading candidate violation locations, never a shell command. | Change tools: Read, Glob, Grep, Bash to tools: Read, Glob, Grep -- nothing in 'When to invoke', 'core responsibilities', or 'Must refuse' names a shell command to run (reading CLAUDE.md/house-rules.md/CONTRIBUTING/linter config and re-reading candidate violation locations are all Read/Glob/Grep operations), and 'Must refuse' explicitly scopes the agent to read-only. | sonnet |
plugins/befund/agents/idiom-remediator.md:3 | Q-MEANING-CONTRACT | idiom-remediator is dispatched per "(file, kind) cluster" and its body says the cluster shares "the same idiom kind," but idiom-auditor.md — the sole producer of the findings it consumes — defines its finding schema as only category, file, line, description, severityNote; no kind field is ever emitted, so the clustering key idiom-remediator's contract depends on does not exist in the data it is handed. | Add a kind field to idiom-auditor's finding schema (e.g. a specific idiom-pattern slug like "optional-to-union") in plugins/befund/agents/idiom-auditor.md's Output format section (prose list + both fenced json examples), and additionally touch plugins/befund/skills/befund-code-idiom/SKILL.md Step 2, which currently only says 'Every finding MUST carry category in {modernization, smell}' -- add that modernization findings MUST also carry kind, so the contract idiom-remediator and befund-idiom-fix depend on for (file, kind) clustering is stated at its point of production, not just inferred from a downstream consumer. | sonnet |
plugins/befund/agents/transform-executor.md:11 | Q-MEANING-ASSUMED | MODERNIZATION_BRIEF.md is the agent's sole source of the phase decision, Open Questions, and stage scope, but the file never states where it lives, whether it is handed to the agent verbatim or must be located and read, or what to do if it is missing at dispatch time. | State either the expected path/location of MODERNIZATION_BRIEF.md or that its relevant phase content is supplied directly in the dispatch prompt, plus what to report if it cannot be found. | sonnet |
plugins/befund/agents/transform-executor.md:27 | Q-PROC-TOOLS-ROLE | The body requires deleting and moving files, but frontmatter grants only tools: Read, Glob, Grep, Write, Edit (no Bash and no delete-capable tool), so the stated role cannot actually be performed with the granted tools. | ⚠ Do not grant Bash. The agent's frontmatter and body deliberately brand it 'the only Write/Edit-capable agent in befund' -- adding unscoped Bash breaks that minimal-blast-radius design and grants far more (arbitrary command execution) than the stated need (moving/deleting a handful of files). Instead, close the gap from the body/output side: change responsibility 3 to 'Create/rewrite files at their new locations via Write/Edit; for any file the decision requires removed, do not delete it yourself -- list it in a new files_to_delete output field for a human or andon-verify to remove' and add that field to the Output format block. This keeps tools: Read, Glob, Grep, Write, Edit accurate to what the agent actually does, satisfying Q-PROC-TOOLS-ROLE's minimum-grant requirement instead of violating it. (conflicts with Q-PROC-TOOLS-ROLE) | sonnet |
plugins/befund/agents/ui-auditor.md:11 | Q-PROC-TOOLS-ROLE | ui-auditor is granted Bash while the body explicitly disclaims running, building, or rendering anything and never states any Bash-based action, leaving the grant unexplained for what is otherwise a pure Read/Glob/Grep static-audit role. | Drop Bash from the tools: line (leave tools: Read, Glob, Grep). The body's responsibilities are all static reads covered by Read/Glob/Grep, and nothing in ui-auditor.md or the befund-ui-audit SKILL.md that dispatches it calls for a shell command, so there is no legitimate static use to document -- removal is the correct fix, not a documentation task. | haiku |
plugins/befund/skills/befund-ci-topology/SKILL.md:28 | Q-MEANING-PROMISE | The skill's bundled workflow (and thus its own audit scope) includes a commit-signing check, but the frontmatter description only promises "redundancy, mirror risk, and doc-vs-config drift" — commit-signing is never mentioned there, so a router or reader relying on the description would not expect this behavior. | The description is only 354/1024 chars, so there is ample room; append a short clause rather than reworking the whole sentence, e.g. change '...for redundancy, mirror risk, and doc-vs-config drift, masking...' to '...for redundancy, mirror risk, doc-vs-config drift, and commit-signing inconsistencies, masking...'. Note the description's when-only opening ('This skill should be used when...') is a separate pre-existing M-DESC-WHENONLY issue this fix should not need to touch. | sonnet |
plugins/befund/skills/befund-docs-drift/SKILL.md:37 | Q-CANN-NEGATIVE | The body implements an explicit carve-out routing every CI/CD-related doc claim to befund-ci-topology, but befund-docs-drift's frontmatter description ("verify our docs match the code") never states this negative scope or names the sibling, so a router matching on the description alone has no signal to send CI-flavored doc-accuracy queries to befund-ci-topology instead, whose own description ("verify docs about CI/CD are accurate") overlaps closely. | Add a clause to befund-docs-drift's description such as "excludes CI/CD-specific doc claims, handled by befund-ci-topology" so the negative scope is visible without reading the body. | sonnet |
plugins/befund/skills/befund-extract-rules/SKILL.md:64 | Q-MEANING-CONTRACT | Step 4 resolves an output path for DATA_OBJECTS.md (and separately for business_rules.json, distinct from the validated business_rules_summary.json) but neither this file nor its three references/ files (rule-card-template.md, rule-card-examples.md, business-rules-report-sample.md) ever say what DATA_OBJECTS.md contains or how it differs from business_rules.json, so the artifact's shape is undefined anywhere a reader can find it. | Confirmed by checking plugins/befund/scripts/lib/validators.py (VALIDATORS has no 'data_objects' or 'business_rules' entry, only 'business_rules_summary') and business-rules-miner.md (only ever emits Given/When/Then rule JSON, never a DATA_OBJECTS shape) that DATA_OBJECTS.md and business_rules.json are resolved as output paths but produced by nothing in the pipeline and validated by nothing. The suggested fix's either/or (document the shape vs. remove the vestigial resolve-output-path calls) is the right shape for a human-tier fix here, since only a human/maintainer can determine whether these paths are leftover from a prior design or an intended-but-undocumented artifact -- an automated fix should not guess and delete or fabricate a schema. Keep fix_tier as human; no change needed to the fix itself. | human |
plugins/befund/skills/befund-idiom-fix/SKILL.md:64 | Q-STEPS-GATE | Step 5's handoff to andon:andon-verify/andon-loop names no fallback if the andon plugin isn't installed, unlike befund-autopilot's Step 4 which explicitly handles that exact case for the same handoff. | In Step 5, extend the verify-dispatch-handoff rule with an escape hatch mirroring autopilot's andon-not-installed handling but adapted to this skill's actual state (edits already made, not merely planned): 'If neither andon:andon-verify nor andon:andon-loop is installed, report plainly that the remediator edits have already been applied to the working tree but remain unverified, and stop -- do not attempt any self-check as a substitute.' This keeps the same stop-and-report shape as autopilot's fallback while correctly reflecting that Step 4 already mutated files (autopilot's parallel case halts before FIX ever runs). | sonnet |
plugins/befund/skills/befund-lint-audit/SKILL.md:29 | Q-STEPS-INPUTS | Step 2 passes <settings.lint_max_rules> to cap-lint-rules but no earlier step in this file states that check-enabled's output carries a lint_max_rules setting, unlike befund-preflight which explicitly says its check-enabled call returns resolved settings including output_dir. | State in Step 0 that check-enabled's stdout JSON carries resolved settings including lint_max_rules, mirroring befund-preflight's Step 0. | sonnet |
plugins/befund/skills/befund-lint-audit/SKILL.md:40 | Q-CLARITY-AMBIG | The staleness check for the symbol-index snapshot says the build should be re-run when the file's source_fingerprint 'no longer matches' but never states what it is being compared against (the repo's current file hashes? the latest commit? something else), leaving the actual comparison undefined for whoever implements or follows this step. | ⚠ Don't invent new terminology in-line ('tracked-file hashes' appears nowhere in this codebase; build_symbol_index.py's fingerprint() hashes each cataloged file's path+content digest, not specifically 'tracked' files in the git sense). The exact phrase 'if missing or its source_fingerprint no longer matches' is a deliberately truncated echo of references/parallel-safe-research-protocol.md line 44 ('...no longer matches the repository...'), a sentence this same SKILL.md step already points to ('see references/parallel-safe-research-protocol.md'), and befund-arch-health/SKILL.md uses the identical truncated wording. Restore the three dropped words -- '...no longer matches the repository, run...' -- rather than adding a new, possibly-inaccurate technical gloss; the full definition already lives one link away and should stay there per progressive disclosure. (conflicts with Q-PROC-DISCLOSURE) | haiku |
plugins/befund/skills/befund-lint-audit/SKILL.md:63 | Q-STEPS-GATE | Step 4 names the exact rejection condition for validate-artifact but never states what to do when the artifact is rejected (fix rules_skipped and retry which step), leaving the failure path unspecified. | Add a sentence stating the remediation on rejection, e.g. 'populate rules_skipped from the capped list and re-run validate-artifact before writing.' | sonnet |
plugins/befund/skills/befund-lint-audit/SKILL.md:67 | Q-MEANING-CONTRACT | Step 4 ("Validate and write") resolves LINT_AUDIT.md's path and jumps straight to the read-only constraint without ever instructing that it be written or describing its overall shape beyond one required section, unlike befund-preflight's Step 4 which explicitly states PREFLIGHT.md's content and says to write it with the Write tool. | Add a sentence describing LINT_AUDIT.md's full content and an explicit instruction to write it with the Write tool, matching befund-preflight's Step 4. | sonnet |
plugins/befund/skills/befund-portfolio/SKILL.md:28 | Q-STEPS-INPUTS | Step 3 checks each swept repo's artifacts against "its configured output_dir," but this skill never calls check-enabled (or any equivalent) per repo to resolve that repo's own .claude/befund.local.md output_dir setting, so the value this step reads was never produced by any prior step in this file. | ⚠ Do not use check-enabled for this: befund_cli.py's cmd_check_enabled calls settings_mod.require_enabled(), which raises SelfAssessError whenever that repo's .claude/befund.local.md sets enabled: false (repo-wide or under the skill's own key) -- see plugins/befund/scripts/lib/settings.py's require_enabled(). Invoking check-enabled per swept repo would make Step 3 hard-fail on any repo where befund is disabled, exactly the kind of repo a portfolio sweep needs to grade Gray rather than abort on. Instead add a per-repo call to the ungated get-settings --repo <repo> command (befund_cli.py's cmd_get_settings, which just loads and returns the settings dict with no enabled check) before Step 3, and read its output_dir -- so 'configured output_dir' becomes an artifact this skill actually produces, without introducing a new unhandled failure mode on disabled repos. (conflicts with Q-MEANING-ASSUMED) | sonnet |
plugins/befund/skills/befund-stage-map/SKILL.md:85 | Q-MEANING-CONTRACT | Step 6 instructs writing stage_map_summary.json alongside stage_graph.json and file_stage_index.json, but unlike those two -- each built and passed through validate-artifact in Steps 4-5 with a stated schema -- stage_map_summary.json's contents are never built, described, or validated anywhere in the file. | ⚠ Do not route this through validate-artifact -- there is no 'stage_map_summary' entry in befund_cli.py's VALIDATORS dict (plugins/befund/scripts/lib/validators.py only defines arch_health_summary, ci_topology_summary, code_idiom_summary, complexity_score_summary, docs_drift_summary, business_rules_summary, lint_audit_summary, preflight_summary, stage_graph, file_stage_index, transform_brief_summary, ui_audit_summary), so instructing a validate-artifact --kind stage_map_summary call as written would fail at runtime. The codebase already documents stage_map_summary.json's actual role: plugins/befund/scripts/lib/staleness.py's STAGE_MAP_REQUIRED_ARTIFACTS comment calls it 'a reporting sidecar, not one of the artifacts a downstream skill reads for its own analysis' (only stage_graph.json/file_stage_index.json are load-bearing), and plugins/befund/scripts/lib/status.py's STRUCTURAL_SIDECAR_FILES uses it purely as a presence flag for befund-status's dashboard. The real gap is just that Step 6 never says what goes in it -- fix that with one or two prose sentences (e.g. 'stage_map_summary.json records a short run summary -- stage/wire counts and a timestamp -- for befund-status's presence check; it carries no schema other befund skill depends on') rather than wiring it into the validate-artifact machinery built for load-bearing artifacts. | sonnet |
plugins/befund/skills/befund-stage-map/SKILL.md:88 | Q-MEANING-CONTRACT | This file states stage_map.json "may also be written" (optional), but befund-transform-brief's own Step 7 treats it as already existing with stage/wire data and only appends a flows field to it "without altering its existing stage/wire data" -- the producing skill's contract for this artifact is stated as optional while the consuming skill's contract assumes it is guaranteed, and neither file reconciles the two. | Change "may also be written here" to state plainly whether befund-stage-map always writes stage_map.json, and if not, state what befund-transform-brief does when it is absent. | sonnet |
plugins/befund/skills/befund-transform-brief/SKILL.md:25 | Q-STEPS-GATE | Step 1 reads arch_health_summary.json as the sole source of structural decisions and only handles the case where a stage simply has no finding in it ("No arch-health finding touching a stage means Keep(1:1)"); it never states what to do if befund-arch-health has not run at all and the file is entirely absent, unlike the explicit stage_graph.json prerequisite gate in Step 0. | ⚠ Do not mirror Step 0's hard stop-and-refuse gate here -- Step 1 already states a graceful default ('No arch-health finding touching a stage means Keep(1:1)'), and a full-file-absence hard gate would contradict that default and leave the reader unable to tell which rule governs when the file is missing. Instead, add one sentence to Step 1: 'A missing arch_health_summary.json is treated as zero findings for every stage, so this default applies to all phases; note in the brief that arch-health did not run this pass.' This keeps Steps 2-7 executable and resolves the ambiguity without introducing a second, conflicting halt condition. (conflicts with Q-CLARITY-AMBIG) | sonnet |
plugins/befund/skills/befund-transform-brief/SKILL.md:25 | Q-MEANING-ASSUMED | Step 1 requires reading arch_health_summary.json and mandates every phase's decision MUST derive from it, but unlike the Step 0 gate that explicitly handles a missing stage_graph.json (writing a 'Ready-with-gaps' brief and stopping), the file never states what happens if arch_health_summary.json itself does not exist -- befund-arch-health may simply never have run. | ⚠ Drop the 'either degrades... or refuses' branch -- offering both as equal options is itself the menu-of-approaches problem the rubric flags, and 'refuses' would also duplicate/conflict with Step 1's existing per-stage default. Use the single degrade-only fix instead: 'A missing arch_health_summary.json is treated as zero findings for every stage, so every phase defaults to Keep(1:1) (the same rule already stated for a stage with no arch-health finding); note in the brief that arch-health did not run.' This is the same revised text as the index-0 duplicate of this finding. (conflicts with Q-CLARITY-OPTIONS) | sonnet |
plugins/befund/skills/befund-transform-brief/SKILL.md:76 | Q-MEANING-ASSUMED | Step 6 reads business_rules_summary.json as a required input for P0-blocker flagging but never states what happens when befund-extract-rules has not run and the file is absent, unlike stage_graph.json (Step 0) and file_stage_index.json (Step 3) which both have explicit absent-case handling. | Add an absent-case branch for Step 6 stating that P0-blocker flagging is skipped (and noted as unavailable) when business_rules_summary.json does not exist. | sonnet |
plugins/befund/skills/befund-transform-brief/SKILL.md:101 | Q-STEPS-GATE | Step 7 instructs updating stage_map.json's existing stage/wire data with a new flows field, but befund-stage-map's own Step 6 says stage_map.json "may also be written here" (optional), so this step never states what to do if stage_map.json does not yet exist. | ⚠ The fix offers 'create it fresh OR note it could not be written' as two undifferentiated alternatives -- pick one default instead. Add to Step 7: 'If stage_map.json does not yet exist, create it here containing only the flows field (leave stage/wire population to befund-stage-map).' A single, always-executable default keeps this a step-logic fix rather than adding a decision point the author still has to make. (conflicts with Q-CLARITY-OPTIONS) | sonnet |
minor (131)
| where | rule | claim | fix | tier |
|---|---|---|---|---|
plugins/andon/agents/andon-adjudicator.md:48 | Q-PROC-OUTPUT-SHOWN | The adjudicator produces a structured per-criterion verdict plus an overall green/red/unknown verdict, but 'What to produce' only names the fields in prose -- no fenced instance of the shape (including how a mixed pass/fail/neither set of criteria renders) is ever shown. | Add one fenced example under 'What to produce' showing a filled-in verdict for a wire with a mix of pass/fail/neither criteria and the resulting overall verdict line. | sonnet |
plugins/andon/agents/andon-adjudicator.md:48 | Q-CLARITY-EXAMPLE | The 'What to produce' section describes the verdict format in prose only, with no fenced worked example of a filled-in criterion verdict or the overall green/red/unknown line, despite output-shape-findings.md explicitly flagging this exact agent's tribunal-verdict output as still lacking a worked example. | ⚠ This finding targets the same sentence and the same gap as index 1 (Q-PROC-OUTPUT-SHOWN) and proposes the identical remedy -- one fenced example of a mixed pass/fail/neither verdict set plus the overall verdict line, at the same location in 'What to produce.' Applying both suggested_fix texts as separate edits would add two near-duplicate worked examples to the same short section, which is exactly the kind of paragraph that fails to justify its own recurring token cost. Do not apply this as an independent edit: fold it into index 1's fix (a single fenced example already satisfies both Q-PROC-OUTPUT-SHOWN's 'show the shape' requirement and Q-CLARITY-EXAMPLE's 'a concrete complete example exists' requirement) and drop this finding as a separate change. (conflicts with Q-CLARITY-CONCISE) | sonnet |
plugins/andon/agents/andon-challenger.md:40 | Q-PROC-OUTPUT-SHOWN | The Challenger's structured output (file:line citation, what's wrong, withdrawal condition, per criterion) is described only in prose in 'What to produce' with no fenced worked example of the shape. | Add a short fenced example under 'What to produce' showing one filled-in objection: a file:line citation, the defect, and the withdrawal condition -- e.g. a fenced block like - plugins/andon/scripts/apply_fix.py:112 -- contract requires the migration to be idempotent, but this write is unconditional; withdraw if a pre-write existence check is added. This is the single fix for the missing worked example; do not also apply index 1's near-identical fix on top of it. | sonnet |
plugins/andon/agents/andon-challenger.md:40 | Q-CLARITY-EXAMPLE | The output format (per-criterion objection with file:line, description, and withdrawal condition) is described only in prose with no fenced worked example, matching output-shape-findings.md's finding that none of the andon tribunal agents show a finished output end to end. | ⚠ This finding and index 0 flag the exact same defect (missing fenced example under 'What to produce') with the same suggested fix location. Applying both literally would add two overlapping worked examples to a 45-line file, which is redundant, recurring token cost with no added value (Q-CLARITY-CONCISE) and risks near-duplicate content (M-DUP-CONTENT). Treat this as a duplicate of index 0: apply only index 0's single fenced example and skip this one, rather than adding a second example. (conflicts with Q-CLARITY-CONCISE) | sonnet |
plugins/andon/agents/andon-defender.md:40 | Q-PROC-OUTPUT-SHOWN | The Defender's structured per-criterion case (satisfying evidence or an explicit gap) is described only in prose in 'What to produce' with no fenced worked example of the shape. | Add one fenced example under 'What to produce' showing a single criterion with (a) satisfying file:line evidence and (b) the explicit-gap phrasing for a criterion with no evidence found -- covering both branches Q-PROC-OUTPUT-SHOWN requires in one worked instance, e.g.: Criterion: "rejects on missing config" -- SATISFIED Evidence: src/loader.py:42-47 raises ConfigError when `path` is None before any read. Criterion: "logs a warning on fallback" -- NOT SATISFIED Could not find evidence: no logging call in src/loader.py's fallback branch (lines 51-58). | sonnet |
plugins/andon/agents/andon-defender.md:40 | Q-CLARITY-EXAMPLE | The output format is described in prose only; no fenced worked instance of a per-criterion defense (with a real-looking file:line citation) is shown, the same gap output-shape-findings.md records for this agent. | ⚠ This is the same gap as finding 0 (Q-PROC-OUTPUT-SHOWN), anchored to the same quote and the same 'What to produce' section. Applying both suggested fixes independently would insert two separate fenced worked examples into a 44-line file for the same criterion-case shape, which is redundant, not additive -- every paragraph must justify its own recurring token cost (Q-CLARITY-CONCISE), and a second near-identical example does not. Do not apply this fix on its own: fold it into finding 0's single fenced example (which should already show a concrete file:line citation for the satisfied branch) rather than adding a second one. (conflicts with Q-CLARITY-CONCISE) | sonnet |
plugins/andon/agents/andon-verifier.md:44 | Q-PROC-OUTPUT-SHOWN | The Verifier's structured output (command run, fenced output, reproduces/contradicts/unverifiable) is described only in prose in 'What to produce' with no fenced worked example, including no example of the 'unverifiable' branch. | Add one fenced worked example under 'What to produce' covering both branches: a sample claim, the exact command/check run, its fenced credential-masked output, and the resulting reproduces/contradicts/unverifiable line -- plus a second short line showing the unverifiable case (e.g. 'no runtime available to run migration; check: unverifiable'). File is only 46 lines total, well under the 500-line cap, so there is room without triggering M-BODY-LINES. | sonnet |
plugins/andon/agents/andon-verifier.md:45 | Q-CLARITY-EXAMPLE | output-shape-findings.md quotes this exact sentence as 'good prose, no example' and states a single worked example (one fake claim through fenced command+output to a verdict line) is still missing from this file. | ⚠ This finding cites the same three-sentence 'What to produce' gap as index 0 (Q-PROC-OUTPUT-SHOWN) and proposes the same remedy -- one fenced worked example with claim, command, output, and verdict. Applying both suggested_fix texts literally and independently would insert two overlapping fenced examples into a 3-line section, which is redundant token cost with no added information (Q-CLARITY-CONCISE: 'every paragraph justifies its recurring token cost'). Treat index 0 and index 1 as one defect with one fix: add a single fenced example (claim -> command -> fenced/masked output -> reproduces/contradicts/unverifiable verdict, plus a one-line unverifiable-branch illustration) and do not add a second, separate example for this finding. (conflicts with Q-CLARITY-CONCISE) | sonnet |
plugins/andon/references/okf-ledger-schema.md:41 | Q-PROC-OUTPUT-SHOWN | The stage/gap/evidence doc schemas -- the exact artifact shape every andon-loop write-doc call must satisfy -- are described entirely as prose field lists with no fenced example of a complete doc instance anywhere in the file. | Add one fenced example of a full gap doc showing status:open with a filled proposal object -- base it on the existing fixture plugins/andon/scripts/fixtures/sample_ledger/gaps/ingest-retry-storm-on-429.md (frontmatter: type: gap, title, stage: ingest, kind: bug, status: open, blast_radius: local+reversible, proposal: {...}, tags: [...]) rather than inventing new field values, so the example and the fixture the tests exercise stay in sync. | sonnet |
plugins/andon/skills/andon-loop/SKILL.md:166 | Q-STEPS-INPUTS | Phase 4's dispatch to andon-verify never forwards the verify_strategy/verify_strategy_rationale fields that andon-propose's Phase 1 step 4 was expressly written to produce ('do not leave strategy selection to andon-verify to guess; you have the most context on the gap right now'), and andon-verify's own Step 1 unconditionally re-derives the strategy via route-wire regardless ('never skip, never default'), so andon-propose's careful strategy choice is recorded on the gap doc but orphaned at the one point it should be consumed. | ⚠ plugins/andon/skills/andon-verify/SKILL.md Step 1 currently reads 'route the wire (never skip, never default)' and the skill's opening line states 'Never default to strategy a (tribunal) as a starting guess -- route through the classifier below first, every time.' That is a deliberate independence guarantee: the verifier must derive its own strategy from the wire's contract, not from the proposer who has an interest in the outcome. The suggested fix ('treat it as a hint to reconcile with route-wire's own routing rather than silently ignoring it') grafts exactly the kind of 'unconditional rule plus exemption clause' Q-STEPS-PREDICATE flags -- it turns 'never skip, never default' into 'never skip, never default, unless andon-propose already suggested something.' Applying it as written risks reintroducing self-verification bias into the one step designed to prevent it. Keep andon-verify's Step 1 completely unchanged. Instead, only change andon-loop's Phase 4: pass the gap doc's recorded verify_strategy/verify_strategy_rationale to andon-verify purely as read-only audit context to include verbatim in the evidence doc (e.g. 'proposed strategy: X, actual routed strategy: Y') so a mismatch is visible in the ledger, without andon-verify's routing decision ever consuming or reconciling against it. This uses the orphaned field (satisfies Q-STEPS-INPUTS) without weakening the independence invariant. (conflicts with Q-STEPS-PREDICATE) | sonnet |
plugins/andon/skills/andon-preflight/SKILL.md:56 | Q-STEPS-GATE | Step 3 instructs running validate-write-path before writing PREFLIGHT.md but never states what to do if that check fails (the path resolves outside output_dir). | ⚠ State that any error from validate-write-path (exit non-zero / raised AndonError) means the write must not proceed. Do not suggest 'recomputing' the path and retrying: the script's only failure modes are WRITE_SCOPE_ABSOLUTE, WRITE_SCOPE_TRAVERSAL, and WRITE_SCOPE_OUTSIDE (checked in plugins/andon/scripts/andon_core.py's validate_write_path), and WRITE_SCOPE_OUTSIDE in particular means output_dir itself resolves outside repo_root -- a caller misconfiguration a fresh path guess cannot fix. Instead: stop, report the AndonError's message verbatim to the user as 'output_dir appears misconfigured', and do not write PREFLIGHT.md. This avoids an unbounded retry loop with no named stop condition. (conflicts with Q-STEPS-TERMINATION) | sonnet |
plugins/andon/skills/andon-preflight/SKILL.md:66 | Q-STEPS-GATE | Output 1 (PREFLIGHT.md) is explicitly gated behind a validate-write-path call before writing, but output 2 (preflight_summary.json), written to the same output_dir with the same traversal/outside-repo risk, names no gate at all -- an inconsistent safety check across two structurally identical writes in the same step. | Require the same validate-write-path <output_dir>/preflight_summary.json <repo_root> <output_dir> call before writing the JSON output, matching the check already required for the markdown output. | sonnet |
plugins/andon/skills/andon-status/SKILL.md:3 | Q-CANN-NEGATIVE | andon-status's description distinguishes itself only by behavior ('without running a new pass') and never names its adjacent-job sibling andon-loop by name, breaking the convention the plugin otherwise follows -- andon-preflight's own description explicitly names all three siblings ('ready for andon-loop, andon-propose, or andon-verify') so a router (and a reader) can separate the read-only reporter from the skill that actually advances the ledger. | Description is 339/1024 chars, so there is ample room. Append a short clause naming the sibling explicitly, e.g. end with "...without wanting a new pass run (not andon-loop, which advances the ledger)." Keeps third person, adds no XML, stays well under the cap. | sonnet |
plugins/andon/skills/andon-verify/references/oracle-gap-techniques.md:34 | Q-CLARITY-EXAMPLE | The required trust-ledger entry (quantity checked, technique used, significant-digit count) is only described in a prose bullet list with no fenced worked example of what such an evidence-doc body actually reads like, even though the exact wording (e.g. how to phrase a metamorphic-relation tolerance versus a significant-digit count) is non-obvious. | Add a short fenced example directly under the 'Trust-ledger entry (required)' section, e.g. a 3-4 line block showing filled-in Quantity checked / Technique used / Significant-digit count values for synthetic recovery. Confirmed the andon-verify SKILL.md body does not already contain such an example, so this is not a duplicate inline. | sonnet |
plugins/cli-scaffold/agents/cli-scaffold-verifier.md:4 | Q-MEANING-PROMISE | The description's stated trigger is narrowly a paradigm-skill handoff after scaffold generation, yet the description's own third example ('A user is unsure a generated CLI meets the doctrine... Does this generated CLI actually satisfy the five pillars?') shows the agent invoked ad hoc by a direct user question with no paradigm-skill handoff involved, so the stated 'when' does not cover a scope the description itself demonstrates. | ⚠ The frontmatter description is already 1361 characters, well over the 1024-char M-DESC-LEN cap (confirmed by measuring the actual YAML field) — this is itself an unreported blocker-adjacent defect. Broadening the opening trigger sentence, as the fix's first option proposes, adds length to an already over-budget field and makes the violation worse. Take the fix's second option instead, and go further: cut the description below 1024 chars first — the embedded <example> blocks inside it also independently violate M-DESC-XML ('description contains no XML-style tags') and are the biggest reduction opportunity — then, only if standalone user-initiated verification is genuinely intended, add a short clause for it within the trimmed budget; otherwise drop the third example as the fix suggests. (conflicts with M-DESC-LEN) | sonnet |
plugins/cli-scaffold/agents/cli-scaffold-verifier.md:8 | Q-CLARITY-TERMS | The description calls each disposed doctrine violation a "gap" ("reports gaps as either "fixable" or "needs-human-judgment""), but the body's own reporting step calls the identical per-rule item a "finding" ("each failing finding with its rule_id, its disposition"), so the same concept carries two names across the same file. | The fix is scoped correctly by its own 'for the same concept' qualifier, but to remove any risk of an implementer also touching the unrelated 'verdict (pass/gaps)' wording in step 2/4 (where 'gaps' is the literal string the engine returns, not a synonym for 'finding'), state the fix more narrowly: change only the description's 'reports gaps as either...' phrase to 'reports findings as either...'; leave every mention of the verdict value 'gaps' untouched. | sonnet |
plugins/cli-scaffold/agents/cli-scaffold-verifier.md:85 | Q-PROC-OUTPUT-SHOWN | The structured report the agent hands back to the calling skill (verdict plus per-finding rule_id/disposition/detail/evidence) is only described in prose bullets; no fenced worked example is shown for either the gaps case or the empty/pass case. | Add a fenced example block showing a sample 'gaps' report alongside the empty 'pass' case. | sonnet |
plugins/cli-scaffold/agents/cli-scaffold-verifier.md:87 | Q-CLARITY-EXAMPLE | The report-back shape the agent must hand to the calling skill (verdict, per-finding rule_id/disposition/detail/evidence, the fixable-vs-needs-human-judgment reminder) is described entirely in prose with no fenced example of a filled instance, even though the shape has enough fields that a worked example would remove ambiguity. | ⚠ This duplicates finding index 2's fix — both propose adding a fenced example of the same report-back shape (verdict + per-finding rule_id/disposition), just motivated by different rule ids (Q-PROC-OUTPUT-SHOWN vs Q-CLARITY-EXAMPLE). Applying both literally produces two separate example blocks for the same content. Merge into finding index 2's addition: one fenced example showing the pass/empty case plus a gaps case with one fixable and one needs-human-judgment finding satisfies both rules; don't add a second block here. (conflicts with Q-CLARITY-CONCISE) | sonnet |
plugins/cli-scaffold/skills/cli-architecture/SKILL.md:3 | Q-MEANING-PROMISE | The frontmatter description and the body heading both claim 12 supported languages, but the body's own enumeration is 3 compiled (Rust, Go, .NET) + 6 interpreted (Python, TypeScript, JavaScript, Ruby, PHP, Perl) + 4 shell (Bash, Zsh, PowerShell, POSIX sh) = 13, so the doctrine's stated scope contradicts what it actually documents. | Correct '12' to '13' in the frontmatter description and in the '## The 12 languages and 3 paradigms' heading, AND also fix the third occurrence at line 38 ('Identical in all 12 languages, no exceptions.') — the suggested fix only names two of the three '12'-language mentions in this file; applying it as written would leave line 38 still contradicting the corrected heading/description. Verified counts: description ('12 supported languages'), line 38 ('Identical in all 12 languages'), and the heading (both say 12) must all become 13, matching the enumerated list of Rust/Go/.NET (3) + Python/TypeScript/JavaScript/Ruby/PHP/Perl (6) + Bash/Zsh/PowerShell/POSIX sh (4) = 13. Description length after the edit stays well under the 1024-char cap (534 chars), so no M-DESC-LEN conflict. | haiku |
plugins/cli-scaffold/skills/cli-scaffold-compiled/SKILL.md:59 | Q-PROC-OUTPUT-SHOWN | The verifier's JSON gap report — the structured artifact this step's whole branching logic depends on (disposition: fixable vs disposition: needs-human-judgment) — is only described in prose; no fenced instance of its shape (fields, an example fixable finding, an example needs-human-judgment finding, or the empty/pass case) is shown anywhere in this file or the doctrine. | Add one fenced JSON example of a gaps report showing both a fixable and a needs-human-judgment finding. | sonnet |
plugins/cli-scaffold/skills/cli-scaffold-compiled/SKILL.md:64 | Q-CLARITY-TERMS | The same verifier-report item is called a 'finding' here ('For each finding with disposition: fixable', 'Only findings marked...') and a 'gap' two sentences later ('Never present a scaffold that still has fixable gaps', 'surface the remaining gaps'), so Step 5 uses two interchangeable terms for one concept. | Standardize Step 5 on one term (e.g. always 'finding'), reserving 'gaps' only for the verdict name gaps itself. | sonnet |
plugins/cli-scaffold/skills/cli-scaffold-interpreted/SKILL.md:64 | Q-CLARITY-TERMS | Same finding/gap term drift as the compiled paradigm skill: 'finding' names the JSON report item while 'gaps' (in 'fixable gaps' and 'surface the remaining gaps') is used for the identical concept two lines later. | ⚠ The suggested fix ('standardize on one term, matching whatever term the sibling skills use') is unsafe as written: verify_scaffold.py's own contract defines two distinct things sharing the word 'gap(s)' — the verdict enum VERDICT_GAPS ('verdict gaps', a literal script output that must not be renamed) and the JSON report's actual item key, which is literally "findings" (see verify_scaffold.py line 541: "findings": findings, and the finding() builder function). 'Standardize on one term throughout' with no exception carved out for the literal verdict \gaps`value risks an editor renaming that literal to match 'finding', which would misdescribe the tool's actual output and be factually wrong. Also, checking the sibling cli-scaffold-compiled/SKILL.md shows it has the identical 'finding'/'gaps' drift (including in its own description's 'fixes any fixable gaps'), so there is no already-consistent sibling term to converge on — deferring to 'the sibling' resolves nothing. Correct fix: leave 'verdictgaps' untouched (it is the literal VERDICT_GAPS constant name), and replace only the colloquial uses referring to individual report items — 'surface the remaining gaps' and 'still has fixable gaps' — with 'findings', matching the JSON schema's actual findingskey. Apply the same correction identically across all three paradigm skills (compiled, interpreted, shell) in one pass so they stay in sync. (conflicts withQ-MEANING-CONTRACT`) | sonnet |
plugins/cli-scaffold/skills/cli-scaffold-interpreted/references/ruby.md:22 | Q-CLARITY-OPTIONS | This gives two undifferentiated options with no stated default, unlike the parallel no-completion cases in perl.md and posix-sh.md, which both give one concrete instruction (declare completion.supported: false in the manifest) rather than a menu. | Replace the sentence with: "No first-party completion mechanism — declare completion.supported: false in the manifest (as in perl.md/posix-sh.md); a hand-written bash-completion file may optionally be shipped on top of that default, not in place of it." This states one default matching the sibling references' concrete manifest-field instruction while preserving the bash-completion option as a named, clearly subordinate escape hatch rather than a second equal choice. | sonnet |
plugins/cli-scaffold/skills/cli-scaffold-shell/SKILL.md:70 | Q-CLARITY-TERMS | Same finding/gap term drift present in all three paradigm skills: 'finding' and 'gap' name the same verifier-report item within the same Step 5 paragraph. | ⚠ Same problem as index 1's fix, and this file has the identical text verbatim. Do not blanket-replace 'gap(s)' with 'finding(s)' throughout Step 5: 'verdict gaps' is the literal VERDICT_GAPS constant from verify_scaffold.py and must stay as written, or the file misdescribes the actual tool output. Only 'surface the remaining gaps' and 'still has fixable gaps' should become 'findings', matching the JSON report's real findings key (verify_scaffold.py: "findings": findings). Apply this identical correction across cli-scaffold-compiled, cli-scaffold-interpreted, and cli-scaffold-shell together, since all three currently share the exact same drift (including in cli-scaffold-compiled's own frontmatter description). (conflicts with Q-MEANING-CONTRACT) | sonnet |
plugins/passung/agents/align-executor.md:30 | Q-STEPS-GATE | Unlike the missing-PLAYBOOK.md gate two lines above it, which names the exact recovery action ('Report that the pilot hasn't been done and do not edit a file'), this gate only says 'refuse' with no stated output — it never says what to report or to whom, leaving the failure path underspecified next to a well-specified sibling gate in the same section. | Replace 'if it does, refuse' with 'if it does, report that the dimension is undecided and do not edit any file for that module' — this stays a short clause consistent with the surrounding gate style and does not turn the section into a menu of options (Q-CLARITY-OPTIONS is not implicated since it's one prescribed action, not several). | sonnet |
plugins/passung/agents/passung-critic.md:49 | Q-CLARITY-EXAMPLE | The four-tier finding format plus the mandatory closing "If I could only change one thing" paragraph is stated only in prose, with no fenced worked example of a finding or the closing line. | Add one fenced example block after the 'Output' section showing a single finding in full (e.g. 'Blocker — plugins/x/foo.py:42 — ... — suggested change: ...') and a sample one-sentence closing paragraph ('If I could only change one thing, it would be tightening the basis claim in the Pattern Card for error-handling, which cites two files but asserts majority over five.'). Keep it to ~6-8 lines total so the file stays well under the 500-line cap and doesn't turn into a second copy of the review-lens prose already above it. | sonnet |
plugins/passung/agents/passung-critic.md:49 | Q-PROC-OUTPUT-SHOWN | The finding schema (severity tier, what/where/why/suggested-change, closing one-thing-to-change paragraph) is named field-by-field in prose but no fenced instance of a filled finding is ever shown. | Add a fenced example of one complete finding (severity, file:line, rationale, suggested change) plus the closing 'If I could only change one thing' line. | sonnet |
plugins/passung/agents/equivalence-verifier.md:39 | Q-PROC-OUTPUT-SHOWN | The three-way verdict output (PASS / PASS-WITH-GAPS / FAIL) plus the required 're-derived evidence' statement is only described in prose, with no fenced example of a completed verdict report. | Add a fenced example verdict (module name, verdict, what was independently re-derived, any gap noted) covering both a clean PASS and a PASS-WITH-GAPS instance. | sonnet |
plugins/passung/agents/pattern-analyst.md:46 | Q-PROC-OUTPUT-SHOWN | The variant-cluster output shape (label, representative file:line, site count, maturity/recency note, plus a 'Confidence & Gaps' footer) is named in prose only, with no fenced instance of a filled cluster entry or of what an empty-gaps footer looks like. | Add a fenced example of one variant-cluster entry and a sample 'Confidence & Gaps' footer, including the case where there is nothing to flag. | sonnet |
plugins/passung/agents/pattern-analyst.md:48 | Q-PROC-OUTPUT-SHOWN | The mandatory 'Confidence & Gaps' footer is a distinct structured sub-artifact (three named sub-cases plus an ask-the-human line) specified only in prose, with no fenced example of a filled footer or the empty-gaps case shown anywhere in the file. | ⚠ Finding 2 (same file, same 'Output format' section) already prescribes a fenced example that covers 'one variant-cluster entry AND a sample Confidence & Gaps footer, including the empty-gaps case' — exactly what this finding asks for again. Applying both suggested_fix strings independently would add two separate fenced examples illustrating the same footer, which is redundant prose that costs recurring tokens for no new information (Q-CLARITY-CONCISE), not a second finding to fix. Do not add a standalone example for the footer alone; instead, when implementing finding index 2's fix, make sure the single fenced example it adds includes the footer's filled case (e.g. a thin-history note) and the empty/nothing-to-report case as one of its sub-parts. Treat this finding as folded into index 2 rather than actioned separately. (conflicts with Q-CLARITY-CONCISE) | sonnet |
plugins/passung/agents/pattern-extractor.md:56 | Q-PROC-OUTPUT-SHOWN | pattern-extractor is the agent that actually authors the Pattern Card artifact, but its Output format section shows no fenced instance and does not even name the fields in prose — it defers entirely to the /passung-canonize command file, which this agent is not guaranteed to have loaded when dispatched standalone. | ⚠ Don't inline the full markdown Pattern Card text from passung-canonize.md — that duplicates a format that already lives in one place (canonize.md's card template and CANON.json shape) and, worse, is the WRONG shape for this agent's dominant invocation path. Per workflows/canonize.js, when pattern-extractor runs via Method A (Workflow, 'preferred when available'), the runtime dispatches it with schema: PATTERNS_SCHEMA — a JSON object with fields dimension, provenance, canonicalForm, basisFrequency, basisMaturity, basisRecency, divergentSites[], confidence — and the calling session, not the agent, renders that into the markdown Pattern Card afterward. Only in Method B (direct subagent fan-out, the fallback) does pattern-extractor need to produce the markdown-formatted card text itself. Fix the Output format section by naming both contracts explicitly and showing one fenced JSON example matching PATTERNS_SCHEMA (a derived-majority case and a needs-human-decision case, e.g. {"dimension": "error-handling-style", "provenance": "needs-human-decision", "canonicalForm": "", ...}), with a one-line note that when dispatched via Method B instead, output the full markdown card in the exact format /passung-canonize defines (keep that as a pointer, don't duplicate the template text). (conflicts with M-DUP-CONTENT) | sonnet |
plugins/passung/commands/passung-brief.md:31 | Q-PROC-OUTPUT-SHOWN | CONSISTENCY_BRIEF.md's shape (Canon Summary table, Phased Sequence entries, Per-Dimension Detail) is described entirely in prose across sections 1-6, with no fenced worked instance anywhere in the file — unlike scan.md, map.md, and canonize.md, which all show a concrete JSON/Pattern-Card example of their output artifact. | Fix is fine as written. The file is only 94 lines (well under the 500-line M-BODY-LINES cap), so an inline fenced example costs nothing structurally, and no other section already shows the Brief's own document shape (section 7's fenced block is only the Approval footer, not the Canon Summary/Phase content). Add one small fenced block right after section 2's table description and section 3's per-phase bullet list showing one filled Canon Summary row (e.g. | error-type | derived-majority | \AppError` wraps all... | 14 sites | Medium |`) plus one filled phase entry (Scope/Entry/Exit/Scale/Risk) — matching the worked-example convention scan.md/map.md/canonize.md already use. | sonnet |
plugins/passung/commands/passung-canonize.md:13 | Q-CLARITY-TERMS | The same $2 positional argument is named dimension-pattern here but convention-pattern in passung-scan.md ('If a [convention-pattern] was given ($2), scope the scan to dimensions matching it'), even though both filter the identical set of dimension ids produced by the scan — the drift makes it unclear to a reader whether the two commands accept the same kind of value. | Standardize on dimension-pattern in both files (rename scan.md's argument-hint [convention-pattern] -> [dimension-pattern] and its prose "If a [convention-pattern] was given ($2), scope the scan to dimensions matching it" -> "If a [dimension-pattern] was given..."). dimension-pattern is the better target term, not convention-pattern: scan.md's own section heading already says "Default convention dimensions" and the JSON schema it writes uses dimensions/id, so keeping convention-pattern would leave scan.md self-inconsistent even after the cross-file fix. No other rubric rule is touched by this rename (argument-hint length, XML-tag, and third-person rules are all unaffected). | haiku |
plugins/passung/commands/passung-map.md:67 | Q-STEPS-NUMBERED | The file's own prose ('written in the previous step') treats 'Build the matrix data' and 'Render' as sequential steps with a real data dependency, but neither section heading is numbered the way this plugin's other multi-step commands (align.md's Step 1/2, canonize.md's Step 1-3, verify.md's Step 1-3) are, leaving the execution order implicit. | Number the '## Build the matrix data' and '## Render' headings as Step 1 / Step 2 to match sibling commands' convention. | haiku |
plugins/passung/commands/passung-preflight.md:114 | Q-PROC-OUTPUT-SHOWN | The PREFLIGHT.md report structure (Check 0 answers, status table, verdict) is described entirely in prose with no fenced instance of the actual table/verdict shape, unlike other commands in this plugin (e.g. passung-scan.md's consistency.json, passung-canonize.md's CANON.json) that show a concrete example. | Add a short fenced example of the status table (a couple of ✅/⚠️/❌ rows) and the Ready/Ready-with-gaps/Not ready verdict line. | sonnet |
plugins/passung/commands/passung-status.md:37 | Q-PROC-OUTPUT-SHOWN | The three-line Verdict format (Where you are / What's stale / Next command) and the artifact-inventory table are both described only in prose/table-of-fields form; no fenced instance of a filled-in status report is shown anywhere in the file. | Add one short fenced example of a filled status report (inventory table plus the three verdict lines) so the exact output shape is unambiguous. | sonnet |
plugins/passung/workflows/align.js:157 | Q-PROC-OUTPUT-SHOWN | RESULT_SCHEMA names every field of the per-unit result in prose descriptions but the file never shows one worked, fenced example object (with real values for testsRan/aligned/testCommand/filesChanged/etc.) for the align-executor agent to pattern-match against. | Add the example as a JS comment (not a markdown fence, since this is a .js file) directly below the RESULT_SCHEMA object literal — one worked instance showing a passing unit (testsRan:true, aligned:true, filesChanged/testCommand populated) and one showing the no-test-run branch (testsRan:false, aligned:false, testFailures explaining why no command could run). Keep it as a comment so it stays documentation, not executable code. | sonnet |
plugins/passung/workflows/align.js:169 | Q-CLARITY-TERMS | The whole args contract, schema, and every log/report field call the thing being aligned a "unit" (units, unit, remainingUnits, failedUnits, blockedUnits), but the actual prompt sent to the align-executor agent calls it a "module" ("Align module", "Run this module's own tests") without ever stating the two words denote the same object. | The mismatch isn't confined to promptFor: the top-of-file description/docstring also mixes terms ('one align-executor agent per module', 'pilot module is aligned', 'module directory', 'module level' vs. 'unit' everywhere else, including the schema and all list names). Fixing only promptFor would leave the description still inconsistent. Standardize on 'unit' across the entire file — description/docstring plus promptFor — since 'unit' is what the schema, args contract, and result lists (remainingUnits/failedUnits/blockedUnits) already commit to. | sonnet |
plugins/passung/workflows/canonize.js:8 | Q-MEANING-CONTRACT | whenToUse states the workflow returns only Pattern Cards for PATTERN_CARDS.md/CANON.json, but the return statement (lines 339-354) also emits a separate top-level divergentSites catalog from a fourth 'Divergent sites' phase that /passung-align depends on, and the contract never mentions it. | Extend the whenToUse sentence (line 6) to: 'Returns structured Pattern Cards plus a top-level divergentSites catalog from the Divergent-sites phase — the calling session writes PATTERN_CARDS.md and CANON.json from the cards, and hands divergentSites to /passung-align.' This is a short, targeted addition and does not push whenToUse toward any documented length cap (M-DESC-LEN governs frontmatter description, not a workflow's whenToUse field). | sonnet |
plugins/passung/workflows/canonize.js:76 | Q-PROC-OUTPUT-SHOWN | PATTERNS_SCHEMA is a nested, non-obvious shape (patterns[] with divergentSites[] sub-objects, four provenance enum values each with different required-field implications) described entirely in prose, with no fenced worked instance the pattern-extractor agent can copy the structure from. | Add a code comment directly above PATTERNS_SCHEMA (or an examples array within the JSON Schema itself, so it also reaches the pattern-extractor agent as part of the structured-output contract) showing one fenced instance: a derived-majority card with basisFrequency/basisMaturity/basisRecency filled in and a populated divergentSites[] array, plus a one-line note that openQuestion is required only when provenance is needs-human-decision (a cross-field constraint the JSON Schema type system cannot express on its own). Keep it to one worked instance, not one per provenance enum value, to avoid bloating the file. | sonnet |
plugins/passung/workflows/scan.js:51 | Q-PROC-OUTPUT-SHOWN | DIMENSIONS_SCHEMA's inScope/outOfScopeReason/variants interplay (an object whose required fields differ by verdict) is explained only through per-field 'e.g.' hints, never as one complete worked instance showing both an in-scope and an out-of-scope dimension card. | The fix as written ('next to DIMENSIONS_SCHEMA') is ambiguous in a way that matters: DIMENSIONS_SCHEMA is a plain JS object (lines 41-73) whose field description strings are the only part of it that reaches the model (as JSON-schema property descriptions); a fenced example placed as a source comment beside the const DIMENSIONS_SCHEMA = {...} declaration is never seen by the finder agent and would not actually close the Q-PROC-OUTPUT-SHOWN gap, only give the appearance of having done so. Retarget the fix: insert the two worked fenced examples (one inScope:true with variants, one inScope:false with outOfScopeReason) directly into the finder agent's prompt template string, i.e. appended within the text built at lines 113-118 before ${UNTRUSTED} is interpolated, so the text is actually part of what the agent receives at runtime. | sonnet |
plugins/passung/workflows/verify.js:89 | Q-STEPS-NUMBERED | The Check-phase prompt chains four distinct actions (read the diff, run tests, check for coverage gaps, check for doc drift) in one unbroken paragraph with no ordering markers or numbering, unlike scan.js's parallel prompt which uses explicit FIRST/THEN/ONLY IF sequencing for a comparable multi-step check. | Fine as written. Unlike scan.js's FIRST/THEN/ONLY IF gate (each step's outcome determines whether the next runs), the four Check-phase items (read diff, run tests, check coverage, check doc drift) are independent checks that jointly feed one verdict, not a dependency chain — so a plain numbered checklist (no FIRST/THEN gating language) is the correct fix shape and doesn't misrepresent the task as more sequential than it is, nor does it conflict with Q-PROC-FREEDOM since numbering a checklist doesn't remove the judgment latitude needed for assessing coverage gaps or doc drift. | sonnet |
plugins/zirkel/agents/branch-proposer.md:55 | Q-PROC-OUTPUT-SHOWN | The two structured outputs -- the Propose branch (name/description) and the Score object (Feasibility/Impact/Risk/blocker) -- are only described in prose with no fenced example, leaving exact field names and casing to guesswork by whatever consumes the object programmatically. | Add two small fenced JSON examples matching the exact schemas the calling workflow (plugins/zirkel/workflows/explore-branches.js) already enforces at dispatch: for Propose, {"name": "...", "description": "..."} (BRANCH_SCHEMA); for Score, {"feasibility": 7, "impact": 8, "risk": 4, "biggest_blocker": "..."} (SCORE_SCHEMA, lowercase snake_case keys, integers 1-10). Keep each example to 3-4 lines so the body stays well under the 500-line cap. | sonnet |
plugins/zirkel/agents/instruction-candidate.md:52 | Q-PROC-OUTPUT-SHOWN | The Critique job's 'checklist' output (pass/fail plus a one-line reason for each of the 4 meta-prompting items) is never shown as a concrete fenced instance, so whether it is a list, a keyed object, or something else is left unspecified even though the content is non-obvious. | File is 58 lines total, well under the M-BODY-LINES 500-line cap, so a short fenced example adds negligible bulk and doesn't force a references/ split. Suggested fix (a fenced list of {item, verdict, reason} entries alongside final_prompt) matches Q-PROC-OUTPUT-SHOWN and F7/Q-OTHER-RIGIDITY's preference for a positive worked example over more prose. One refinement: since the Critique job can legitimately produce a checklist where all 4 items pass (no rewrite needed), the example should show that as the normal/degenerate case rather than treating an all-fail checklist as the only demonstrated shape. | sonnet |
plugins/zirkel/references/parallel-safe-research-protocol.md:83 | Q-CLARITY-AMBIG | "the run coordinator" is never defined anywhere in this file (or elsewhere in the read zirkel skills, which instead speak of "the calling skill" or "the workflow"), so a reader has no fixed referent for who or what this hands results to. | ⚠ Replace "the run coordinator" with the exact phrase this same file already uses for this concept two lines above ("the calling skill or agent", line 72) — e.g. "return structured results to the calling skill or agent instead." Introducing a third variant ("the calling skill or workflow that dispatched this agent") would leave two non-identical phrasings for the same referent within one file, which is itself a Q-CLARITY-TERMS violation. Reuse the file's own established term rather than coining a new one. (conflicts with Q-CLARITY-TERMS) | haiku |
plugins/zirkel/skills/zirkel-explore-branches/SKILL.md:34 | Q-CLARITY-CONCISE | This sentence restates, almost verbatim, the 'Scale guidance' section of references/parallel-safe-research-protocol.md, which the same paragraph already cites one sentence earlier — the guidance is paid for twice in recurring token cost instead of once. | ⚠ Don't delete the sentence outright — it is the only place in the always-loaded SKILL.md body that tells the executor to skip step 0 for small repos, and that trigger needs to stay in the body per Q-PROC-DISCLOSURE (bulky reasoning goes to references/, but the standing instruction/trigger itself stays put). Instead trim the sentence to keep the actionable threshold and drop only the restated reasoning that duplicates the reference's 'Scale guidance' section verbatim. E.g.: 'Skip this step for a repository well under roughly 50 tracked files — this is a judgment call the script itself does not make (see "Scale guidance" in references/parallel-safe-research-protocol.md).' That keeps the decision-relevant trigger in the body, cuts the duplicated elaboration ('the build's own overhead may exceed...', 'the script makes no such size check itself, so this is your judgment call, not its'), and still points to the reference for anyone who wants the fuller rationale. (conflicts with Q-PROC-DISCLOSURE) | haiku |
plugins/zirkel/skills/zirkel-investigate-dynamically/SKILL.md:29 | Q-STEPS-TERMINATION | The Reasoning/Action/Observation loop explicitly disclaims any fixed step count and names only a convergence condition (question answered) as its stop, with no upper bound and no escalation path for the case where the investigation stalls (unknowns remain but no available action closes them). | ⚠ Do not add a fixed iteration cap — the skill explicitly states its stop condition is convergence-based ('no remaining unknown blocks the answer', not a fixed step count'), and this is exactly what differentiates it from the sibling zirkel-verify-assumptions, which the file's own Related section already describes as 'hard-capped at 3 steps.' Imposing a numeric bound here would contradict the file's own stated design and blur that distinction. Instead add a stall predicate plus escalation, no cap: e.g. under Discipline or the loop description, 'If a step's Reasoning names a remaining unknown but no available Action can close it, stop the loop, report the blocking unknown, and either ask the user how to proceed or hand off to zirkel-ground-evidence (stronger sourcing) or zirkel-verify-assumptions (one bounded check).' This gives an observable, escalation-bearing stop for the non-convergence case without reintroducing a step count. (conflicts with Q-CANN-INTERNAL) | sonnet |
plugins/zirkel/skills/zirkel-investigate-dynamically/SKILL.md:37 | Q-CLARITY-CONCISE | This 'Discipline' bullet restates the exact rule already given two paragraphs earlier ('Choose a tool only when your Reasoning names a gap it resolves... if Reasoning didn't name the gap, don't take the action') without adding new information, costing recurring tokens for no new content. | Delete this bullet; the earlier Action-bullet rule already states it. | haiku |
plugins/zirkel/skills/zirkel-negotiate-tradeoffs/SKILL.md:29 | Q-PROC-OUTPUT-SHOWN | The trade-off matrix is a required deliverable named twice (Process step 1 and the Output list) but its shape — columns, one row per source branch, how 'preserves' is worded — is never shown as a fenced instance; only the guard's numeric score JSON is shown, which does not represent the qualitative matrix at all. | Add a small fenced markdown-table example of the trade-off matrix (branch | gives up | hybrid preserves) alongside the score-guard JSON. | sonnet |
plugins/zirkel/skills/zirkel-optimize-instruction/SKILL.md:66 | Q-PROC-OUTPUT-SHOWN | The Output section promises 'the final prompt, ready to use' as a deliverable, but every fenced example in the file is a guard-validation JSON snippet about scores or checklist items — no fenced instance shows what the actual final output (candidates table + winner + critique + final prompt) looks like assembled. | Add one fenced example of the complete Output section as a maintainer would actually write it, including a sample final prompt. | sonnet |
plugins/zirkel/skills/zirkel-reason-verify/SKILL.md:70 | Q-STEPS-GATE | The self-consistency validate call states the count/coverage requirement but never states what to do when the guard rejects the attempt set (e.g. a missing or duplicated strategy), leaving the failure path undefined unlike other zirkel guard calls in this same plugin. | ⚠ Add a sentence after 'The guard requires exactly 3 attempts covering all three strategies.' stating: a non-zero exit means a strategy is missing or duplicated (the guard's stderr names which); re-dispatch only that missing/duplicated attempt once and re-validate. If the guard still rejects after that single retry, stop and report the guard's error rather than proceeding to a majority vote. (conflicts with Q-STEPS-TERMINATION) | sonnet |
plugins/zirkel/skills/zirkel-summarize-trace/SKILL.md:31 | Q-PROC-OUTPUT-SHOWN | The 'completeness score table' is required after the 7 sections and repeated in the Output list, but the file never shows an instance of it or defines its columns/scoring criteria — a maintainer has no way to know what it should contain. | Show a fenced example of the completeness score table (e.g. section | present | notes) or define its scoring criteria explicitly. | sonnet |
plugins/zirkel/skills/zirkel-summarize-trace/SKILL.md:43 | Q-PROC-OUTPUT-SHOWN | The only example of the fixed 7-section report fills every section's body with the placeholder ellipsis '…' rather than real content, so a non-obvious, strictly-ordered artifact (exact section set, omission rule for 'Approaches weighed', always-present 'What was revised') is never actually demonstrated with filled content. | Replace at least one placeholder section body in the example with a short real-content sentence so the fixed 7-section shape is demonstrated, not just named. | sonnet |
plugins/zirkel/skills/zirkel-verify-assumptions/SKILL.md:47 | Q-PROC-OUTPUT-SHOWN | The file names three possible outcome kinds (confidence_raised, reading_changed, still_unresolved) but the only fenced JSON example shows the confidence_raised shape — the still_unresolved branch (which routes through a special refusal template) and reading_changed are never shown, leaving their JSON structure to guesswork. | Add a second, compact fenced JSON example directly after the existing one in the Validate section showing the still_unresolved shape specifically (not 'or reading_changed' as an either/or) — e.g. {"assumption":"...","steps":[...],"outcome":{"kind":"still_unresolved"}} — since zirkel_lib.py's validate_verify_run requires no confidence/citations fields for that kind, making it the true 'empty branch' Q-PROC-OUTPUT-SHOWN asks to show, and the smallest possible addition (no new fields to explain, unlike reading_changed which reuses the citations requirement already visible via confidence_raised). | sonnet |
plugins/zirkel/workflows/solve.js | Q-MEANING-CONTRACT | CLARIFY_SCHEMA declares success_criteria items as bare untyped objects with no required 'criterion' key, yet the Revise phase later does clarify.success_criteria?.map((c) => c.criterion ?? c), silently falling back to stuffing a whole object where a string criterion is expected if the agent's output doesn't happen to include that key. | Add required: ['criterion'] with criterion: {type:'string'} to the success_criteria item schema. | sonnet |
plugins/zirkel/workflows/solve.js:108 | Q-CLARITY-AMBIG | The Clarify prompt tells the dispatched agent to mark low-confidence facts 'with the warning marker' as if it were already established, but no marker (symbol, string, or format) is ever defined anywhere in this file, so the agent cannot know what to actually emit. | Replace 'the warning marker' with a literal, defined marker (e.g. state the exact symbol or prefix to prepend) directly in this prompt string. | sonnet |
plugins/zirkel/workflows/solve.js:173 | Q-CLARITY-EXAMPLE | The Decompose prompt specifies a non-obvious structural contract (acyclic dependsOn graph, at least one dependsOn:[] entry point, no dangling references — exactly what computeWaves() validates and can throw on) entirely in prose, with no worked example of a valid stages array for the dispatched agent to pattern-match against. | Add one fenced example of a valid 2-stage stages array (including a dependsOn: [] entry stage and a dependent second stage) to the Decompose prompt. | sonnet |
plugins/zirkel/workflows/solve.js:174 | Q-CLARITY-CONCISE | When there is no strategic fork, approach is set to clarify.scoped_task, and the Decompose prompt then appends Scoped task:\n${clarify.scoped_task} right after it, so the identical scoped-task text is sent twice in the same prompt with no added information. | Only append the separate 'Scoped task:' line when explore is set (i.e. when approach is not already clarify.scoped_task). | haiku |
plugins/zirkel/workflows/solve.js:239 | Q-STEPS-TERMINATION | The Revise loop's stop condition is bounded (MAX_REVISION_CYCLES) but its escalation path when criteria still fail after the cap is only a log() line — the function falls through to an unconditional status: 'COMPLETE' return with no field indicating unresolved failing criteria, so a caller reading only the returned status cannot tell a fully-converged revision from one that gave up after 2 cycles. | Surface the unresolved-failure case in the return value (e.g. a revise_converged: false flag or a distinct status) instead of only logging when cycle === MAX_REVISION_CYCLES. | sonnet |
plugins/zeugnis/agents/agentic-reliability-auditor.md:8 | Q-MEANING-PROMISE | The body states its audit scope covers skills/, agents/, commands/, and workflows/, but the frontmatter description only lists "skills/, agents/, and workflows/* files" and never mentions commands/, so the description understates what the agent actually does. | Insert "commands/, " into the description's file-scope list so it reads "...needs one categorized pass over skills/, agents/, commands/, and workflows/* files." The description is 484 of 1024 chars, so this ~13-character addition leaves ample headroom under M-DESC-LEN and does not risk the cap. | sonnet |
plugins/zeugnis/agents/assertion-auditor.md:21 | Q-MEANING-ASSUMED | Find mode and Verify mode both state their input explicitly ("for the given target files", "given one Find-phase finding"), but Suggest mode's input — a "confirmed-weak mutation" — is never named as something the dispatch prompt must supply, leaving where that mutation comes from unstated. | Change 'Suggest mode: draft a replacement or additional assertion that WOULD catch a confirmed-weak mutation.' to 'Suggest mode: given a confirmed-weak-mutation finding in your dispatch prompt, draft a replacement or additional assertion that WOULD catch it.' — mirrors the explicit-input phrasing already used for Find ('for the given target files... the given test files') and Verify ('given one Find-phase finding') immediately above it in the same file. | haiku |
plugins/zeugnis/agents/zeugnis-remediator.md:64 | Q-PROC-OUTPUT-SHOWN | Unlike the other four zeugnis agent files (which each give a fenced JSON code block with concrete filled-in values), zeugnis-remediator's output contract is inline backticked JSON using ellipsis placeholders instead of a worked example, for a structured artifact whose two branches (applied/blocked) are non-obvious in shape. | As suggested: replace the inline backtick JSON with a fenced json block giving one concrete 'applied' instance (real-looking findingId/file/summary) and one concrete 'blocked' instance (real-looking reason), matching the 'One instance, with concrete values:' pattern used in assertion-auditor.md and contract-auditor.md. Since this agent's contract has two meaningfully different branches (applied vs. blocked), both should be shown, not just one. | sonnet |
plugins/zeugnis/agents/contract-auditor.md:53 | Q-PROC-OUTPUT-SHOWN | The output-contract example shows only a single populated finding and never the empty/no-mismatches-found branch, so a reader cannot tell from the file whether the expected empty output is [], {}, or omitted entirely. | Add one line directly after the existing fenced example: 'If no mismatches are found, return an empty list: [].' (or whichever shape the calling writer script actually expects — check zeugnis-cycle's writer script for the real empty-case shape before committing to [] vs {}, since assertion-auditor.md has the identical gap and should get the same treatment for consistency). | sonnet |
plugins/zeugnis/agents/dependency-auditor.md:49 | Q-CLARITY-EXAMPLE | The output contract promises "One instance of each, with concrete values" across the three categories (hallucinated-dependency, typosquat-adjacent, registry-unreachable), but the fenced JSON example below only shows two instances (hallucinated-dependency and registry-unreachable) — typosquat-adjacent has no worked example anywhere in the file. | Add a third JSON object to the output-contract example — e.g. {"severity": "Medium", "title": "declared dependency 'python-dateutil-utils' is one character from popular 'python-dateutil'", "evidence": "requirements.txt:14", "category": "typosquat-adjacent", "fixability": "advisory"} — so the fenced example actually shows one instance of each of the three categories the prose promises (hallucinated-dependency, typosquat-adjacent, registry-unreachable). | sonnet |
plugins/zeugnis/skills/zeugnis-assertion-audit/SKILL.md:23 | Q-STEPS-INPUTS | Step 2 makes building the symbol index optional ("Optionally build (or reuse...)"), but step 3 hands "the symbol index" to the agent unconditionally — unlike "the named tool (if any)" on the same line, it never says what to pass when step 2 was skipped. | Add the same "(if built)" qualifier used for the named tool, and state what the agent should be told when no symbol index was built. | sonnet |
plugins/zeugnis/skills/zeugnis-contract-drift/SKILL.md:22 | Q-STEPS-INPUTS | Step 2 calls the symbol index build "Optionally", but step 3 tells the executor to hand "the symbol index" to contract-auditor as if it always exists, with no branch for the case it was never built. | Qualify the symbol-index hand-off as conditional on step 2 having run, and state what to give the agent when it was skipped. | sonnet |
plugins/zeugnis/skills/zeugnis-cycle/SKILL.md:52 | Q-STEPS-PREDICATE | Step 4 branches on whether a prior domain summary is 'fresh' to decide reuse-vs-rerun, but the file never defines what makes a summary fresh (e.g. matching commit, matching source_fingerprint, an age bound) — unlike zeugnis-status, which explicitly grounds staleness in a git log -1 reference — leaving the branch keyed to an undefined term rather than an observable condition. | ⚠ Do not invent a 'recorded commit/fingerprint' field on the domain summary — no domain *_summary.json (dependency_audit/assertion_audit/contract_drift/agentic_reliability) stores one; only the unrelated symbol-index artifact (build_symbol_index.py) has a source_fingerprint. The actual, already-implemented staleness check lives in scripts/status_dashboard.py (_git_last_commit_epoch vs the summary file's mtime, exposed as staleRelativeToLatestCommit). Define 'fresh' in step 4 as that same check — the summary sidecar's mtime is not older than git -C <repo_root> log -1 --format=%ct — rather than a field that doesn't exist in the schema. (conflicts with Q-MEANING-CONTRACT) | sonnet |
plugins/zeugnis/skills/zeugnis-status/SKILL.md:24 | Q-PROC-OUTPUT-SHOWN | The stdout summary and suggestion output are only described in prose (which fields exist, the suggestion's priority order) with no fenced example of the actual JSON shape, unlike zeugnis-cycle and zeugnis-agentic-reliability which show fenced instances of their own schemas. | Add one fenced JSON example directly after step 2's description of status_dashboard.py's stdout, matching the real shape emitted by that script (verified in plugins/zeugnis/scripts/status_dashboard.py): a top-level object with domains (map of domain id -> {skill, hasRun, lastRunEpoch, staleRelativeToLatestCommit, findingCount}), ledgerPresent (bool), cycleConverged (bool|null), and suggestion ({action: string|null, reason: string}). Show one populated branch and note the action: null / "nothing pending" case as the empty branch, since Q-PROC-OUTPUT-SHOWN calls for the empty case too. Keep it short (the skill body is only 59 lines, well under the 500-line cap) and place it right where step 2 currently only describes the fields in prose, so the existing prose in steps 4-5 isn't duplicated, just grounded. | sonnet |
plugins/cupertino/agents/handbook-dimension-analyst.md:9 | Q-MEANING-ASSUMED | The body only handles the 'more than one DIMENSION: line' case; it never states what to do if the marker is missing entirely (zero DIMENSION: lines), leaving that precondition unguarded despite the repo's own stated rule (CLAUDE.md) to 'never infer a missing gating value'. | Add an explicit instruction for the zero-marker case, e.g. refuse and report the dispatch as malformed when no DIMENSION: line is present. | sonnet |
plugins/cupertino/agents/handbook-drift-auditor.md:18 | Q-PROC-OUTPUT-SHOWN | The fenced output schema only shows a populated findings array with one example object; the explicitly valid empty-findings outcome is described in prose but never shown as a fenced instance of the shape. | Add a single short fenced example directly below the existing one: json {"findings": []} . Keep it minimal (no repeated field descriptions) since the prose right above it already explains the empty-findings case — the fenced block just needs to make the shape visible, not re-explain it. | sonnet |
plugins/cupertino/agents/handbook-drift-auditor.md:18 | Q-PROC-OUTPUT-SHOWN | The body states the empty-findings case is a valid, expected outcome, but the fenced Find-mode output schema on line 22 only shows a findings array populated with one item and never shows the empty-array branch. | ⚠ This finding is a duplicate of index 0 (identical file, quote, rule, and suggested fix — differing only in which line number is cited for the same fenced JSON block). Applying both fixes independently would insert two separate {"findings": []} examples into a 41-line agent file, which is itself the kind of non-justifying, redundant content Q-CLARITY-CONCISE flags. Do not apply this fix separately: resolve index 0's fix once, and treat this finding as satisfied by that single edit rather than adding a second empty-array example. (conflicts with Q-CLARITY-CONCISE) | haiku |
plugins/cupertino/agents/handbook-remediator.md:25 | Q-MEANING-CONTRACT | The Refuse section instructs reporting a mechanical:false finding with status 'skipped', but the declared output schema's status field enum is only "applied|blocked" — there is no 'skipped' value in the stated contract, so following the Refuse instruction literally produces output that violates the schema shown just above it. | ⚠ Instead of expanding the status enum to a third value, change the word in the Refuse bullet (line 25) from 'skipped' to 'blocked': "Any finding with mechanical: false — that requires design judgment you do not attempt; report it as blocked, never guess." The schema's change field is already documented as '<what you changed, or the blocking reason>', so the mechanical:false case fits the existing 'blocked' status with the reason text explaining why (no design judgment attempted) — no need to introduce a second term ('skipped') for what is functionally the same outcome ('fix not applied, reason given') as 'blocked'. Adding a third enum value partitions the same concept into two near-synonymous statuses with no described downstream distinction, which is exactly what Q-CLARITY-TERMS (one term per concept) flags. (conflicts with Q-CLARITY-TERMS) | haiku |
plugins/cupertino/agents/handbook-verifier.md:20 | Q-CLARITY-EXAMPLE | The output template hardcodes "compliant": true instead of the enum true|false shown by every sibling agent's other status fields (e.g. 'applied|blocked', 'confirmed|false_positive'), even though the body explicitly instructs reporting non-compliance ('If you notice the fix is wrong, report it as non-compliant') — the negative branch is never shown. | Change the template to "compliant": true|false so the non-compliant branch is visible, matching the enum style used in the other three agents' output schemas. | haiku |
plugins/cupertino/skills/cupertino-council/SKILL.md:18 | Q-PROC-OUTPUT-SHOWN | The Council Brief (an audit table) and Design Identity outputs are named and described only in prose, with no fenced instance showing what a filled row or the resulting sentences actually look like. | Add one fenced example showing a filled Council Brief table (five rows) and a sample Design Identity sentence. | sonnet |
plugins/cupertino/skills/cupertino-elevate/SKILL.md:3 | Q-CANN-NEGATIVE | cupertino-elevate's description excludes brand-new capabilities from its scope but never names the sibling that actually owns that adjacent job (cupertino-backwards for undecided new scope, or cupertino-council for a new user-facing surface), leaving a router with a dead end instead of a handoff. | ⚠ Extend the excluded-case sentence to name only cupertino-backwards as the routing destination for a brand-new capability, not a two-way choice between cupertino-backwards and cupertino-council. cupertino-backwards's own description states 'Use FIRST, before cupertino-focus or any other cupertino technique' and docs/orchestration/README.md confirms it 'goes first in any design-shaped task — before cupertino-focus or any other cupertino technique.' More concretely, cupertino-council is itself gated by the same PreToolUse hook (GATED_AFTER_BACKWARDS in hooks/pretooluse_guard.py) that blocks it until the backwards-done marker exists — so even a brand-new UI-surface request must go through cupertino-backwards first, and council is never a valid alternative first stop. Presenting the two as parallel routing options would tell a router it's fine to dispatch council directly for a new UI feature, which the hook will then deny. (conflicts with Q-CLARITY-OPTIONS) | sonnet |
plugins/cupertino/skills/cupertino-elevate/SKILL.md:14 | Q-CLARITY-TERMS | The file refers to the same target concept inconsistently — "commodity feature" in the title/intro/scope-check ('Transfigure an existing, already-in-scope commodity feature', 'Confirm the feature is (a) already in scope') versus "commodity function" in step 1 — forcing the reader to infer these are the same referent rather than tracking one term. | The suggested fix is incomplete: "commodity function" also appears a second time in the Output Format line ("Scope check → commodity function + dullness-root → ..."), which the fix as written would leave unchanged, so the inconsistency Q-CLARITY-TERMS flags would survive a rerun. Replace both occurrences of "commodity function" (step 1's bold lead-in and the Output Format line) with "commodity feature" so the term is consistent everywhere in the file, not just in step 1. | haiku |
plugins/cupertino/skills/cupertino-focus/SKILL.md:13 | Q-PROC-OUTPUT-SHOWN | The grid and cut-list artifacts this skill produces are described entirely in prose ('pick the 2 (or 3) structural axes...place every enumerated item on the grid') with no fenced example of a filled grid or cut list to show the expected shape. | ⚠ Don't show a filled grid with invented structural axes — step 2 explicitly instructs picking axes that reveal real overlap 'for this portfolio... not generic axes', which is exactly the open-ended-judgment case Q-PROC-FREEDOM says needs prose direction, not a worked template; a concrete axis example invites readers to reuse those axes instead of deriving their own, undermining the skill's core instruction. Instead, add a small fenced example of only the cut-list format (item name, disposition — killed/merged/surviving — and the cost stated explicitly), which is the genuinely non-obvious, axis-independent part of the output and carries no risk of being copied verbatim. (conflicts with Q-PROC-FREEDOM) | sonnet |
plugins/cupertino/skills/cupertino-handbook-check/SKILL.md:3 | Q-CANN-NEGATIVE | cupertino-handbook-check's description requires 'an existing domain handbook' as its precondition but never names cupertino-handbook-draft as the sibling that creates one — unlike cupertino-handbook-apply, whose description explicitly names cupertino-handbook-draft for the identical precondition ('in a domain where cupertino-handbook-draft has already run'), so a router reading only descriptions has no path from a missing-handbook case to the right skill for handbook-check specifically. | Add a clause to the description naming cupertino-handbook-draft as the skill to run first when no handbook exists yet, matching the pattern already used in cupertino-handbook-apply's description. | sonnet |
plugins/cupertino/skills/cupertino-handbook-check/SKILL.md:28 | Q-PROC-OUTPUT-SHOWN | The summary sidecar's required-field schema is only named in prose (severity, mechanical, line); unlike cupertino-council and cupertino-focus, which show a fenced JSON instance for their validated artifacts, no fenced example of the sidecar JSON (populated or empty) is shown anywhere in this file. | Add a short fenced JSON block in or right after step 7 (or in the Output format section) showing the sidecar shape twice: once with one populated finding object (e.g. {"rule": ..., "file": ..., "line": 42, "severity": "Medium", "mechanical": true}) and once as {"findings": []} for the zero-findings case. The file is only 32 lines and has no references/ directory, so an inline example (not a separate reference file) is the right level of disclosure — keep it to the minimum fields already named in prose (severity, mechanical, line) plus whatever identifies the rule/location, rather than inventing a larger schema. | sonnet |
plugins/cupertino/skills/cupertino-handbook-draft/SKILL.md:23 | Q-OTHER-STATEFUL | The handbook's Exceptions & waivers section is promised as a place where "future waivers get recorded", and cupertino-handbook-apply reads it, but none of the three handbook skills (draft/apply/check) contains any step that actually writes a waiver into it, so the field has no maintainer. | Reword the promise only — do not add a waiver-writing step to this skill or any other cupertino-handbook skill (that would be scope creep for a drafting skill, and cupertino-handbook-apply's stated job is to read exceptions, not author them). Change '## Exceptions & waivers — empty to start; this is where future waivers get recorded.' to something like '## Exceptions & waivers — empty to start; populated only by manual, out-of-band edits (no cupertino skill writes to this section automatically).' The suggested_fix's first alternative (add a step/skill that appends waivers) is a bigger, unjustified design change; only the reword alternative is warranted here. | sonnet |
plugins/cupertino/skills/cupertino-handbook-draft/SKILL.md:25 | Q-PROC-OUTPUT-SHOWN | The handbook summary sidecar's schema, including the analyzed-vs-scaffolded branch, is described only in prose with no fenced JSON instance shown for either branch. | Add a fenced JSON example of .cupertino/<domain>-handbook_summary.json showing one analyzed dimension (with evidence) and one scaffolded dimension (with note). | sonnet |
plugins/cupertino/skills/cupertino-handbook-fix/SKILL.md:46 | Q-PROC-OUTPUT-SHOWN | The output is a structured multi-field report (a per-finding accept/fail table with two verdict columns) but is only described in prose, never shown as a fenced worked instance. | Add a fenced example under 'Output format' showing a populated accept/fail table (e.g. two rows with a finding id, file:line, remediation status, and independent verifier verdict) AND the empty branch Q-PROC-OUTPUT-SHOWN calls for — a line/row for the case where the validated findings count is zero mechanical findings (e.g. 'Mechanical findings: 0 — nothing to fix, N findings skipped as non-mechanical'). The suggested fix as written only covers the populated case. | sonnet |
plugins/cupertino/skills/cupertino-longevity/SKILL.md:11 | Q-PROC-FREEDOM | The script enforces exact 1-5 integer precision on all six dimensions and computes a hard threshold from their sum, but the skill gives zero scoring rubric or anchor (e.g. what a 2 vs a 4 on "Dependency exit costs" looks like), so the mechanically-computed total sums an otherwise unanchored, non-reproducible judgment call — a fragile precision requirement paired with no guidance at all. | Add a one-line 1/5 anchor for each of the 6 dimensions, phrased so 5 always means 'most evolution-ready' (to avoid the inverted-polarity trap on a dimension named for a cost, e.g. 'Dependency exit costs': 1 = locked into a single vendor/format with no migration path, 5 = swappable behind a stable interface with a documented exit route) — e.g. API/interface stability: 1 = no versioning strategy at all, 5 = full semver with deprecation windows. This keeps the file well under the 500-line body cap and matches Q-PROC-FREEDOM's 'exact commands for fragile sequences' since the total feeds a hard mechanical threshold. | sonnet |
plugins/cupertino/skills/cupertino-prototype/SKILL.md:26 | Q-PROC-OUTPUT-SHOWN | The Output format section names five structured fields as an arrow chain but never shows a fenced, filled-in worked example of the shape. | Add a short fenced example directly under the Output format section showing one filled instance of the arrow chain (e.g. a one-line question, a 3-5 line spike snippet, a captured stdout/exit-code line, a one-sentence observation, and a 'Discard' or 'Deliberately promote: <parts>' fate line). Keep it compact (roughly 10-15 lines) so it demonstrates the shape without duplicating the detail already given in Steps 1-5, and include the discard/promote fork so both fates are visible. | sonnet |
plugins/cupertino/skills/cupertino-reveal/SKILL.md:15 | Q-MEANING-CONTRACT | The skill requires "building" production-grade code but never states where that code lands — into the existing artifact's source files, a new file, or just presented inline — leaving the output contract's destination unspecified. | State explicitly that the built reveal code is written into the shipped artifact's actual source tree (or name the alternative when that's not possible) rather than only requiring a fenced code block. | sonnet |
plugins/cupertino/skills/cupertino-reveal/SKILL.md:16 | Q-PROC-OUTPUT-SHOWN | The seven-part reveal shape (baseline, gap, pivot, description, rationale, sketch+code, impact sentence) is only ever described field-by-field in prose across the numbered steps; no single fenced worked instance shows what a complete reveal write-up actually looks like end to end. | Add one fenced example of a complete reveal (all seven parts, including a short code block) so the shape is shown, not just named. | sonnet |
plugins/cupertino/skills/cupertino-reveal/SKILL.md:26 | Q-STEPS-TERMINATION | The validator-fail retry ('cut to one idea and build it before presenting again') is a loop with no stated bound on retries and no escalation path if the reveal keeps failing the mechanical check. | Add a retry bound (e.g. after two failed validations, stop and report the blocker to the user) rather than an open-ended 'present again'. | sonnet |
plugins/cupertino/skills/cupertino-review/SKILL.md:97 | Q-STEPS-PREDICATE | Steps 5 (prototype) and 6 (elevate) both define an observable predicate and require reporting "explicitly skipped" with a persisted {"skipped": true, "reason": ...} when the condition doesn't hold; step 7 (unbox) reuses the same conditional shape ("if applicable") but drops that handling entirely, so a reader has no instruction for what to persist or report when unbox doesn't apply to the scope. | Reword step 7 to mirror steps 5 and 6 exactly: state the observable predicate for 'if applicable to this scope', add 'If not applicable, report this stage as explicitly skipped ("<why>") — never omit it silently', and add 'if skipped, persist {"skipped": true, "reason": "<why>"} to unbox-output instead of leaving it absent.' | sonnet |
plugins/cupertino/skills/cupertino-review/SKILL.md:128 | Q-PROC-OUTPUT-SHOWN | The final report is a complex structured artifact (eight stage results, a side-by-side attribution format for stage 3, explicit skip markers for stages 5-6, a built reveal, an optional cannibalization flag) described entirely in prose with no fenced worked instance anywhere in the file. | Add a short fenced skeleton under 'Output format' showing the section headers in the fixed stage order, one sample side-by-side attribution line for stage 3 ('longevity says: ...' / 'integrate says: ...'), one sample explicit-skip line for stages 5/6 ('cupertino-prototype: skipped — no empirical uncertainty identified for this scope'), the built reveal, and an optional trailing cannibalization-flag line — keep it short since the file is well under the 500-line cap. | sonnet |
plugins/cupertino/workflows/handbook-check.js:69 | Q-MEANING-ASSUMED | Input validation only checks that rules is a non-empty array; it never checks that each item actually has the .rule field that findPrompt interpolates (RULE: ${r.rule}), so a malformed rules entry silently sends a RULE: undefined prompt to the drift-auditor agent instead of failing loudly. | Immediately after the existing if (!Array.isArray(rules) || rules.length === 0) guard, add a loop that checks each entry has a non-empty string .rule (e.g. typeof r.rule !== 'string' || !r.rule.trim()), and throw cupertino-handbook-check: rules[<i>] is missing a non-empty 'rule' field naming the first offending index -- matching the existing throw style/message prefix in this file so the caller fails loudly instead of shipping 'RULE: undefined' into the drift-auditor prompt. | sonnet |
plugins/cupertino/workflows/handbook-fix.js:117 | Q-STEPS-INPUTS | The Verify phase dispatches verifyFixPrompt for every finding in c.items (the pre-remediation cluster) without cross-referencing which of those locations the remediator's own remediation.results marked 'applied' vs 'blocked', so a location the remediator explicitly declined to touch is verified for compliance exactly as if a fix had been attempted, conflating 'never attempted' with 'attempted and still wrong'. | ⚠ The component's own description promises verification 'blind to the remediator' -- the current code deliberately re-checks the actual file state for every cluster item regardless of what the remediator self-reported, which is exactly what catches a remediator that mislabels a location (e.g. claims 'blocked' but touched the file anyway, or claims 'applied' incorrectly). Filtering verification to only remediation.results-status==='applied' items, as suggested, makes verification coverage depend on the remediator's own self-report -- the opposite of 'blind' -- and creates a blind spot precisely where a wrong self-report matters most. Keep verifying every item in c.items unconditionally, but attach the matching remediation.results entry's status ('applied'/'blocked'/'missing') to each verification result before returning, so downstream reporting can distinguish 'attempted and still non-compliant' from 'never attempted and still non-compliant' without dropping any independent check. (conflicts with Q-MEANING-PROMISE) | sonnet |
plugins/lehre/agents/pattern-investigator.md:22 | Q-MEANING-ASSUMED | The body instructs the agent to prefer the serena MCP tool (find_symbol, find_referencing_symbols) over grep, but the frontmatter only grants tools: Read, Glob, Grep, Bash — serena is never declared, so the instruction directs the agent toward a tool it has no access to and the guidance is dead on arrival. | ⚠ Don't fix this by adding "the serena MCP tool" to frontmatter as written — that's not an actionable edit under M-AGENT-TOOLS-SHAPE, which requires a comma-separated list of known, exact tool names. serena exposes several distinct tools the body actually names (find_symbol, get_symbols_overview, find_referencing_symbols), not one generic "serena" tool, so literally applying option 1 either produces an invalid/unrecognized tools entry or forces an unstated choice about which of the three to grant. Option 2 (rewrite the instruction) is also the better default on its own merits: this agent's tools are deliberately minimal — the body explicitly justifies Bash as granted 'only' for git log recency checks (line 40), signalling a considered minimal-tool-grant design, not an oversight. Adding a second MCP surface to satisfy one paragraph cuts against that stated minimalism (Q-PROC-TOOLS-ROLE: grant should match the agent's stated role). Preferred fix: rewrite the "How to survey" section to drop the serena recommendation and instead give grep/Glob-based guidance for the 'what calls this' question the agent can actually execute with its declared tools (e.g., cross-checking grep hits by reading surrounding call context rather than treating every grep hit as a real caller). Only if the author deliberately wants serena granted should the fix name the exact tool identifiers to add, not "the serena MCP tool." (conflicts with M-AGENT-TOOLS-SHAPE) | sonnet |
plugins/lehre/agents/spec-decomposer.md:41 | Q-PROC-OUTPUT-SHOWN | The rule states that a concern resolving to a single unit is a legitimate, expected answer, but the fenced Output format example always shows a concern split into two units (adapters, contracts) and never shows the degenerate one-unit response the rule explicitly sanctions. | Add a compact second fenced instance immediately after the existing example (not a full duplicate with seams/candidate-rules sections) showing a concern that resolves to exactly one unit, e.g. a 'send a welcome email on signup' concern producing one notifications unit with a why separate: n/a — this is the whole concern line and a short not decomposed further note explaining why splitting further would be artificial. Keep it short (~8-10 lines) so it stays proportionate to the ~30-line first example and doesn't push the 75-line body meaningfully closer to the 500-line cap or duplicate the full seams/candidate-rules scaffolding that only applies to the multi-unit case. | sonnet |
plugins/lehre/agents/spec-fidelity-auditor.md:42 | Q-CANN-NEGATIVE | This is the file's only negative-trigger boundary against the adjacent rule-checking job (owned by violation-auditor / rule-critic via lehre-gauge), but it never names which sibling component actually owns that excluded territory, leaving a router or reader unable to tell where a style/structure finding should go instead. | Name the sibling as lehre-gauge / violation-auditor specifically (not rule-critic, which reviews candidate rulesets pre-write rather than auditing existing code for style/naming/structure violations): e.g. "Those are rules — lehre-gauge dispatches violation-auditor for those, not this agent." | sonnet |
plugins/lehre/agents/spec-fidelity-auditor.md:72 | Q-PROC-OUTPUT-SHOWN | The only worked Output format example shows the failing (NOT FAITHFUL) verdict; no instance shows the passing/clean verdict a fully faithful unit would produce, so the shape of the common-case output is only described in prose, not shown. | Add a second short fenced example showing a unit whose intent clauses all PASS and the verdict line reading FAITHFUL — safe to close. | sonnet |
plugins/lehre/agents/violation-auditor.md:34 | Q-CLARITY-TERMS | The file opens by defining the agent's job as finding "violations" ("You find violations of exactly one judgement-kind rule") but then switches to "finding" as the noun for the same reported item throughout the Rules and Output format sections, mixing the two terms for one concept in a file that itself distinguishes CERTAIN/ARGUABLE violation entries. | Replace the two occurrences of "finding" in the 'Every finding carries file:line...' bullet (lines 34-35 of plugins/lehre/agents/violation-auditor.md) with "violation", matching the term already used in the opening sentence ('You find violations of exactly one judgement-kind rule'), the frontmatter description ('surveyed for violations by reading'), and the 'Never report a violation of a different rule' bullet. Result: "Every violation carries file:line and a quoted span. A violation a reader cannot navigate to is not a violation." This does not conflict with the CERTAIN/ARGUABLE split: the file already uses "violation" as the general class the agent hunts for (including ARGUABLE candidates), and the ARGUABLE bucket's "Flagged, not asserted" hedge is preserved by that heading itself, not by the word "finding". | sonnet |
plugins/lehre/references/ruleset-schema.md:131 | Q-CLARITY-AMBIG | The check.kind vocabulary section states 'Five kinds' but the table immediately above it lists six rows (forbid-path, require-location, python-import, python-construct, linter, judgement); read literally, this could lead a reader to treat 'judgement' as outside the closed vocabulary and therefore an 'unknown kind' schema error — the opposite of the dedicated subsection two paragraphs later that establishes judgement as a legitimate sixth kind, added after this count was apparently written. | Change 'Five kinds' to 'Six kinds' so the prose count matches the table. | haiku |
plugins/lehre/skills/lehre-brief/SKILL.md:29 | Q-CLARITY-TERMS | The same object — one place the code breaks a rule — is called a 'violation set' in the frontmatter description (line 3), a 'finding' in step 4's heading and body (this line, and 'brings the rest to the user' three lines later), and an 'item' in the same step ('Label each item'), drifting across three terms inside one short file. | The suggested fix under-scopes: grepping the file shows three drifting instances, not two — line 3 ('violation set', already correct), line 14 ('A brief built on remembered findings...') in step 1, and line 29/32 ('per finding' / 'Label each item') in step 4. The fix as written only touches step 4 and would leave line 14's 'findings' as a residual fourth term-drift instance right next to the two it fixes. Broaden the instruction to: standardize on 'violation' at all three sites (line 14, line 29, line 32), matching lehre-gauge's vocabulary, not just 'in step 4'. | sonnet |
plugins/lehre/skills/lehre-codify/SKILL.md:57 | Q-STEPS-INPUTS | Step 7 is titled 'Write and validate' but its body only runs lehre_cli.py validate — no step in the numbered sequence actually instructs persisting the researched/labeled rules into .lehre/ruleset.json, so validate consumes an artifact no earlier step produced. | Insert a 7a sub-step before the validate command: 'Serialize the candidate rules (plus any pre-existing rules/units already in .lehre/ruleset.json, per the Rules section's amend-don't-rewrite requirement) into the file per references/ruleset-schema.md, then run:' followed by the existing validate command. | sonnet |
plugins/lehre/skills/lehre-gauge/SKILL.md:41 | Q-CLARITY-EXAMPLE | Step 5 mandates dispatching violation-auditor for every judgement-kind rule and reporting its findings, but the Output format example (lines 59-86) never shows what a surfaced judgement-rule result looks like — every section shown (blocking, advisory, UNEVALUATED) is a machine-decided check kind, so the one output shape whose content is genuinely non-obvious (a judgement rule's asks question plus the auditor's answer) is never demonstrated. | Add a 'judgement' section to the existing (populated) example, between advisory and UNEVALUATED, showing one entry's asks question and the violation-auditor's answer, e.g.: judgement / no-premature-abstraction asks: does this factory have >=2 real call sites? / src/factories/widget.py auditor: no — single call site, inline it — keep it to one entry so the addition stays proportionate to the rest of the block. | sonnet |
plugins/lehre/skills/lehre-gauge/SKILL.md:61 | Q-PROC-OUTPUT-SHOWN | The Output format example only shows a populated violation report; it never shows the empty/clean-sweep branch (0 violations, 0 unevaluated), leaving the intended shape of a passing gauge run undemonstrated even though that is the state the whole pipeline aims to reach. | Add a short second fenced example directly below the current one showing the clean-sweep branch, e.g. swept 412 file(s) against 9 rule(s) [complete] / 0 violation(s): 0 blocking · 0 advisory 0 file(s) could not be evaluated with no rule sections below it — keep it a few lines, not a full restatement of the populated example. | sonnet |
plugins/lehre/skills/lehre-gauge/SKILL.md:64 | Q-CLARITY-TERMS | The skill's own description and top-line summary use 'violation' as the canonical term ('reports every violation with file:line', '27 violation(s): 22 blocking'), but the worked output example immediately switches to 'hits' per rule ('18 hits', '1 hit', '3 hits', '5 hits') for the identical concept, splitting one concept across two terms inside the same fenced instance. | Rename the per-rule count label from 'hits'/'hit' to 'violations'/'violation' throughout the output block, preserving the existing singular/plural handling — i.e. also change the singular occurrence ('1 hit' -> '1 violation') on the no-utils-dumping-ground row, not just the plural 'hits' occurrences, so no instance of the old term is left behind. | haiku |
plugins/lehre/skills/lehre-pin/SKILL.md:3 | Q-PROC-WHATWHEN | The description's trigger clause is a list of quoted literal user phrasings rather than router-matchable key terms, overfitting to exact utterances a real query is unlikely to repeat verbatim (rubric F1). | Replace the quoted-phrase list with a short clause naming the key concepts (CI pinning, durable enforcement, regression tests) that a router can match on paraphrase. | sonnet |
plugins/lehre/skills/lehre-validate/SKILL.md:82 | Q-CLARITY-CONCISE | This Rules bullet is the third statement of the same point in one file — the intro paragraph already says closing carelessly 'disables the ordering guarantee for everything downstream' and step 6 already says 'Close the unit only on a clean result' — adding no new information. | Delete the first Rules bullet ("Close on a clean result only. A unit closed with a known gap silently unblocks every dependent, and the guarantee the build order provides is gone for the rest of the project.") in its entirety, leaving the intro paragraph as the sole statement of the 'why' and step 6 as the sole statement of the procedural gate ("On any fail ... do not close. Report what failed and name the skill that fixes it."). No replacement text is needed: the intro already carries the fuller rationale and step 6 already carries the operational instruction, so nothing is lost by removing the third copy, and the remaining three Rules bullets (never close on request, report fidelity failures distinctly, never fix here) stand fine on their own without renumbering or rewording. | haiku |
plugins/befund/agents/arch-health-auditor.md:6 | Q-PROC-TOOLS-ROLE | The agent is granted Bash but every core responsibility and refusal describes only reading source at cited wires/files (Read/Glob/Grep) -- no sentence in the body ever names a command Bash would run, unlike ci-topology-auditor (git remote -v) or complexity-surveyor (radon/lizard/gocyclo), so the grant exceeds the stated role. | Drop Bash from the tools list (leaving Read, Glob, Grep), matching the sub-agents guidance for read-only agents whose body never names a command to run. | sonnet |
plugins/befund/agents/arch-health-auditor.md:48 | Q-CLARITY-EXAMPLE | The output format names three finding types (god-module, cycle, layering-violation) but the worked example only instantiates god-module and cycle; layering-violation's members shape (which stage is the production side vs. the test/fixture side, and in what order) is never shown, even though it is the one type with no structural signature to fall back on and is therefore the least obvious to format correctly. | Add a third fenced instance for layering-violation (e.g. a production stage importing a stage:tests or stage:fixtures stage) with members ordered production-stage first, consistent with the other two instances' ordering. | sonnet |
plugins/befund/agents/complexity-surveyor.md:37 | Q-CLARITY-EXAMPLE | Unlike all five sibling befund auditor agents (arch-health-auditor, business-rules-miner, ci-topology-auditor, convention-auditor, docs-drift-auditor), which each show a fenced json block with a concrete filled instance, complexity-surveyor's Output format section states only the schema with generic placeholders (N, "...") and never a real filled example, even though the meaning of unmeasured and the choice among tool_used values are non-obvious. | Add a fenced json block with one concrete instance (e.g. sloc: 4200, tool_used: "radon", unmeasured: false) matching the pattern the other five agents use. | sonnet |
plugins/befund/agents/convention-auditor.md:3 | Q-MEANING-PROMISE | The description promises checking conventions sourced from ADRs, but ADRs are never mentioned again anywhere in the body — not in 'When to invoke', not in the source-file list of 'Your core responsibilities' item 1 ("house-rules.md, CLAUDE.md, CONTRIBUTING, linter config"), and not in 'Must refuse' ("CLAUDE.md/CONTRIBUTING/linter config") — so the promised ADR capability is never operationalized. | Add "ADRs" to the source-file enumeration in core responsibility 1 ("house-rules.md, CLAUDE.md, CONTRIBUTING, ADRs, linter config") and to the 'Must refuse' sentence ("CLAUDE.md/CONTRIBUTING/ADRs/linter config"), so the description's promise is actually operationalized in the body -- this is the better of the fix's two offered branches since dropping ADRs from the description would narrow the agent's claimed scope for no benefit. | sonnet |
plugins/befund/agents/idiom-auditor.md:6 | Q-PROC-TOOLS-ROLE | idiom-auditor is granted Bash but the body never names any command it runs with it -- every described action (checking version, categorizing findings, re-reading cited code) is expressible with Read/Glob/Grep alone, unlike sibling agent stage-mapper which explicitly documents its Bash usage pattern ('run it as an inline python3 -c "..."'). | ⚠ Drop Bash from the frontmatter tools grant (tools: Read, Glob, Grep). The 'add a sentence naming the Bash use' branch of the suggested fix is worse than dropping the grant: the body already states the agent receives its version input from the caller ('befund-code-idiom hands you a detected version per language (from the manifest...)'), so inventing a sentence like 'reading a manifest's declared version' to justify Bash would assert a capability the agent doesn't actually exercise -- fabricating a use case rather than removing the unused grant. Since nothing in the body's three invocation scenarios (version-scoped find, verify pass, direct modernization request) or its four core responsibilities names any shell command, the minimum-grant fix is simply to remove Bash. (conflicts with Q-MEANING-PROMISE) | haiku |
plugins/befund/agents/idiom-remediator.md:3 | Q-MEANING-PROMISE | The description's first sentence promises the agent handles "a single, already-verified ... finding" needing "exactly that one mechanical rewrite", but its own second sentence and the body ("apply the rewrite at each cited file:line in your cluster", "one entry per finding in your cluster") describe a cluster that can contain multiple findings at multiple lines in the same file — the description contradicts itself about singular-vs-plural scope. | Rewrite the first description sentence to say the agent applies a cluster of same-file, same-kind mechanical rewrites rather than "a single ... finding" and "that one mechanical rewrite". | sonnet |
plugins/befund/agents/idiom-remediator.md:40 | Q-PROC-OUTPUT-SHOWN | The output contract is named entirely in an inline prose sentence with placeholder values, not shown as a fenced, concrete worked instance (with real file/line/reason values) the way idiom-auditor and ui-auditor do for their own outputs. | Replace the inline sentence with a fenced json block showing one concrete applied:true entry and one concrete applied:false entry with a real reason string. | sonnet |
plugins/befund/agents/stage-mapper.md:18 | Q-CLARITY-TERMS | The file uses both "wire" and "edge" for the same concept throughout ("one candidate wire (an edge ...)", "wires (every edge as [from_stage, to_stage] ...)", output key wire for verification vs. field name wires/prose word "edge" for the find pass), forcing the reader to track two labels for one thing instead of one. | ⚠ Don't simply delete the parenthetical. 'Wire' is first introduced at line 17's bold heading ('Wire verification') and is otherwise undefined until the Output format section at the very bottom -- the parenthetical at line 18 is the file's only inline definition for a reader encountering the term early. Instead of dropping it, reword it to drop 'edge' while keeping the definition: 'hands you one candidate wire -- a proposed connection between two stages -- to confirm by reading...'. Then in the Output format line, change 'every edge as [from_stage, to_stage]' to 'every wire as [from_stage, to_stage]'. This achieves the same one-term-per-concept goal without stripping the only definition a first-time reader has. | sonnet |
plugins/befund/agents/stage-mapper.md:51 | Q-CLARITY-EXAMPLE | The primary find-pass output contract (stages, wires, deadEnds) is described only in prose with no fenced concrete instance, unlike the sibling agents idiom-auditor.md and ui-auditor.md which both show a filled JSON example for their find-pass output — the shape of a "stage id with its file set" and a wire entry is left to inference. | ⚠ This is the same gap as finding index 0 (Q-PROC-OUTPUT-SHOWN), which proposes the identical remedy -- a fenced JSON example of the stages/wires/deadEnds report. Applying both fixes independently would add two overlapping worked examples for the same output contract, which is redundant token cost the rubric itself penalizes (Q-CLARITY-CONCISE: 'every paragraph justifies its recurring token cost'). Apply only one fenced json example covering both the find-pass shape (stages/wires/deadEnds, with concrete stage ids, file lists, and a file:line citation) and the verify-pass shape ({"wire": [...], "verified": ..., "evidence": ...}), and drop this as a separate addition. (conflicts with Q-CLARITY-CONCISE) | sonnet |
plugins/befund/agents/stage-mapper.md:52 | Q-PROC-OUTPUT-SHOWN | Unlike idiom-auditor and ui-auditor, stage-mapper's primary extraction-pass output (stages/wires/deadEnds) is only described in prose with no fenced concrete instance, leaving the actual shape of a "stage id with its file set" undemonstrated. | Add a fenced json example showing two stages with real file lists, one wire with a citing file:line, and a populated deadEnds entry. | sonnet |
plugins/befund/agents/transform-executor.md:42 | Q-PROC-OUTPUT-SHOWN | The output contract is stated as an inline prose sentence with bare placeholders rather than a fenced, concrete worked example, so the actual shape of decision, files_changed, etc. for a real Merge/Split phase is never demonstrated. | Add a fenced json block showing a concrete phase_number, a real decision string (e.g. "Merge stage A into stage B"), and populated files_changed/files_created/files_deleted arrays. | sonnet |
plugins/befund/skills/befund-arch-health/SKILL.md:42 | Q-PROC-DISCLOSURE | Step 2 re-explains the build-or-reuse symbol-index procedure and its '~50 tracked files' scale threshold inline instead of pointing to plugins/befund/references/parallel-safe-research-protocol.md, which exists specifically to be the single shared copy of this protocol. | ⚠ Don't strip the inline procedure -- the exact build_symbol_index.py --repo-path . --plugin-name befund invocation and the ~50-file skip threshold are the actual executable steps a fragile build-or-reuse sequence needs inline (Q-PROC-FREEDOM), and befund-lint-audit's sibling copy of this same paragraph keeps them inline too rather than replacing them with a bare pointer. The real, narrower gap is that arch-health's copy is missing the one thing lint-audit's copy adds: a trailing cross-reference. Change '(single-flight lock makes concurrent callers safe)' to '(single-flight lock makes concurrent callers safe -- see references/parallel-safe-research-protocol.md)', matching lint-audit's Step 3 verbatim, instead of deleting the inline steps. (conflicts with Q-PROC-FREEDOM) | sonnet |
plugins/befund/skills/befund-arch-health/SKILL.md:70 | Q-PROC-OUTPUT-SHOWN | arch_health_summary.json's shape is described only in scattered prose (allowed type values, members, verified) and never shown as a fenced worked instance, unlike the sibling befund-extract-rules skill's references. | ⚠ The per-finding shape (type/members/evidence/verified) this finding wants shown is already a fenced worked example in agents/arch-health-auditor.md's 'Output format' section (one confirmed + one refuted instance with concrete values) -- adding a second copy of that same example into SKILL.md's Step 3 duplicates content that already lives in one place (the agent file the skill dispatches to), the same 'inlining a reference the body already links' problem this review is watching for. The genuine gap is narrower: validators.py's validate_arch_health_summary expects the findings wrapped as {"findings": [...]}, and the all-degrade empty case from Step 0 is never shown anywhere. Fix: in Step 3, add one line noting the artifact wraps the agent's already-shown finding shape in a top-level findings array, plus a one-line fenced empty-findings instance ({"findings": []}) for the Step 0 degrade path -- without re-printing the per-finding example that agents/arch-health-auditor.md already carries. (conflicts with Q-PROC-DISCLOSURE) | sonnet |
plugins/befund/skills/befund-ci-topology/SKILL.md:26 | Q-STEPS-PREDICATE | The branch between the bundled Workflow tool and the ci-topology-auditor agent keys off 'available' with no observable check defined, unlike this same skill's check-enabled and require-git-repo gates which are scripted. | Reword to match the identical, already-established repo convention rather than inventing new phrasing: 'When the Workflow tool is available in this session, prefer dispatching...' (as used verbatim in plugins/zirkel/skills/zirkel-solve/SKILL.md and plugins/passung/commands/passung-scan.md) — or, if a concrete check is wanted, phrase it as an observable self-check ('if the Workflow tool appears among the tools available to this session') applied consistently to all three files, not just this one, so the fix doesn't introduce a new, single-file variant of a convention the repo already uses three times unchanged. | sonnet |
plugins/befund/skills/befund-complexity-score/SKILL.md:51 | Q-PROC-OUTPUT-SHOWN | complexity_score_summary.json's per-stage structure (sloc, complexity_index, unmeasured flag) is never shown as a fenced instance even though the exact formula is the whole point of the skill. | Add a fenced JSON example directly after Step 4's validate-artifact block, e.g.: json { "stages": [ {"stage": "api", "sloc": 4200, "complexity_index": 187.3, "unmeasured": false}, {"stage": "legacy-perl", "sloc": 0, "complexity_index": null, "unmeasured": true} ] } This shows both branches (measured and unmeasured) without restating the formula itself, so it doesn't duplicate Step 3's scripts/lib/formulas.py pointer. | sonnet |
plugins/befund/skills/befund-docs-drift/SKILL.md:46 | Q-PROC-OUTPUT-SHOWN | docs_drift_summary.json's claim schema (text, doc_citation, code_citation, status enum, verification_label) is described in prose across two steps but never shown as one fenced worked example. | ⚠ Add a single fenced JSON example of one claim object (text, doc_citation, code_citation, status, and verification_label where applicable) showing the full field set for the 'contradicted' case -- the richest branch, since it is the only one requiring code_citation -- rather than one fenced example per status enum value. Cover the other branches ('confirmed', 'unverifiable', and the skip_verification 'unverified' label) with a one-line note that they use the same shape minus code_citation/with a different status string, instead of four near-duplicate fenced blocks. This satisfies Q-PROC-OUTPUT-SHOWN's 'one fenced instance of the shape' without the redundant token cost of near-identical examples differing only in an enum value. (conflicts with Q-CLARITY-CONCISE) | sonnet |
plugins/befund/skills/befund-docs-drift/SKILL.md:46 | Q-CLARITY-EXAMPLE | A claim's full field set (text, doc_citation, code_citation, status) is only described piecemeal in prose across Steps 1 and 3, with no fenced example of a single contradicted claim or of the DOCS_DRIFT.md report shape. | Add one fenced example showing a single contradicted claim's full field set as it would appear in docs_drift_summary.json. | sonnet |
plugins/befund/skills/befund-lint-audit/SKILL.md:33 | Q-PROC-OUTPUT-SHOWN | The required "Rules not checked this run" section of LINT_AUDIT.md is only described by name and by the rule that populates it (rules_skipped); no fenced example shows what that section should actually contain (rule text, reason skipped, or just an id list), leaving its shape non-obvious. | Add a short fenced markdown example of the "Rules not checked this run" section showing one skipped rule with its identifying text and skip reason. | sonnet |
plugins/befund/skills/befund-lint-audit/SKILL.md:48 | Q-STEPS-PREDICATE | The branch skipping symbol-index build is gated on a subjective judgment ('may not be worth it') dressed up with an approximate number, not an observable predicate the model can check deterministically. | ⚠ Do not convert this into a hard numeric gate. references/parallel-safe-research-protocol.md's 'Scale guidance' section -- the exact source this SKILL.md paraphrases, and a doc synced verbatim/locked across plugins via rrt artifacts -- states explicitly: 'For a repository well under roughly 50 tracked files, the index-build's own overhead may exceed what a direct Grep would cost. This is judgment guidance for the calling skill or agent to weigh, not an enforced gate -- the indexer itself makes no such size check.' Hard-coding 'skip when tracked files < 50' contradicts that stated design intent and would require the SKILL.md to invent a check the underlying script deliberately doesn't perform. If anything is worth changing, make the deferral to that guidance more explicit inline (e.g. cite the reference by name here too) rather than replacing prose judgment with a fabricated threshold. (conflicts with Q-PROC-FREEDOM) | human |
plugins/befund/skills/befund-lint-audit/SKILL.md:74 | Q-CANN-NEGATIVE | befund-lint-audit's own body acknowledges the adjacent-job boundary with befund-idiom-fix ('never auto-fix a violation found here'), but this disambiguation lives only in the body's Read-only constraint section, not in the frontmatter description a router matches against -- a user asking to 'audit and fix our conventions' has no description-level signal that fixing is idiom-fix's job. | Move or duplicate a short negative trigger into the description itself, e.g. 'reports violations only -- for mechanical fixes see befund-idiom-fix'. | sonnet |
plugins/befund/skills/befund-preflight/SKILL.md:61 | Q-STEPS-INPUTS | Step 2's verdict rule for befund-ci-topology keys off a "git-presence sub-check" of git_remotes_ci, but Step 1 only defines git_remotes_ci as one combined pass/partial/fail check (git dir + CI file together) and never records a separate git-presence sub-result for Step 2 to read. | Prefer the second branch of the suggested fix (rewrite Step 2, not add a new field) since Step 1 already treats git_remotes_ci as a single pass/partial/fail check and no CLI command backs it (unlike languages, which calls detect-languages) -- adding a separate sub-check would require inventing new machine-readable state with nothing to validate it. Concretely: in Step 1, define what each status means for git_remotes_ci ('fail' = no .git directory; 'partial' = .git present but no CI config found; 'pass' = both present), then in Step 2 change the bullet to 'befund-ci-topology is Not-ready if git_remotes_ci's status is fail (no .git directory found)' -- removing the invented 'git-presence sub-check' phrase entirely. This is a same-scope prose edit to both steps, no schema or script change. | sonnet |
plugins/befund/skills/befund-preflight/SKILL.md:71 | Q-PROC-OUTPUT-SHOWN | preflight_summary.json's shape (a 6-item checks list plus a verdicts map keyed by skill id, values from a 3-value enum) is described entirely in prose across Steps 1-3 with no fenced example instance shown, even though the enum values and per-check fields are non-obvious. | Add a fenced JSON example of preflight_summary.json after Step 3, but per Q-PROC-OUTPUT-SHOWN show both branches, not only a fully-passing instance: one checks/verdicts pair where everything is pass/Ready, and one entry demonstrating a gap (e.g. a git_remotes_ci fail driving befund-ci-topology to Not-ready, or an extension-only language detection driving Ready-with-gaps). A single all-pass example would leave the degraded case exactly as undocumented as it is now. | sonnet |
plugins/befund/skills/befund-status/SKILL.md:25 | Q-CLARITY-TERMS | Step 1 introduces the term 'sidecar JSON file' for the same on-disk per-skill output that the very next step's CLI call and the rest of the file call an 'artifact' (--artifact <path1>), splitting one concept across two terms within the same file. | The file actually uses three terms for one concept, not two: 'sidecar JSON file' (line 23), 'reporting sidecar' (line 53), and 'artifact' (used throughout, including the --artifact CLI flag and the description). A literal replace of only the quoted 'sidecar JSON file' phrase leaves 'reporting sidecar' at line 53 as a lingering second term, so the inconsistency survives the fix. Replace both 'sidecar JSON file' (line 23) and 'reporting sidecar' (line 53) with 'artifact' (e.g. '...a reporting artifact exists but...') so the file uses only 'artifact' for this concept. | haiku |
plugins/befund/skills/befund-status/SKILL.md:64 | Q-PROC-OUTPUT-SHOWN | findings_dashboard_data.json's shape is only described in prose ("only the keys actually present, plus staleness and the transform-brief recommendation") with no fenced instance showing how present, structural, staleness, and recommend_transform_brief nest together, even though Step 1-3 establish several distinct fields that must combine into one JSON object. | Add a fenced JSON example of findings_dashboard_data.json showing not just a populated present/structural skill with staleness and recommend_transform_brief, but also the empty branch Q-PROC-OUTPUT-SHOWN calls for -- e.g. the shape when no finding-producing skill has run yet (present: {}) versus when one has, per the status-no-fabrication rule already stated in Step 1 (omit keys entirely rather than emitting placeholders). | sonnet |
plugins/befund/skills/befund-transform-brief/SKILL.md:89 | Q-PROC-OUTPUT-SHOWN | transform_brief_summary.json's phase schema (phase_number, decision, open_questions, work_items) is described only in prose with no fenced example, even though the nested shape of a ranked work_items entry and an Open Questions entry is non-obvious. | Add one fenced JSON example of a single phase object with a populated work_items entry and the empty open_questions branch shown. | sonnet |
plugins/befund/skills/befund-ui-audit/SKILL.md:39 | Q-MEANING-ASSUMED | Step 3 branches on skip_verification, but Step 0 here only runs check-enabled --repo <repo_root> --skill befund-ui-audit and never states that this call returns a skip_verification setting (unlike befund-stage-map's Step 0, which explicitly says "Carry the returned output_dir and skip_verification through every later step") -- the variable is used with no stated source. | Add a sentence to Step 0 stating that check-enabled's JSON output carries skip_verification, mirroring the wording already used in befund-stage-map's Step 0. | sonnet |
plugins/befund/skills/befund-ui-audit/SKILL.md:51 | Q-MEANING-CONTRACT | Step 4 resolves UI_AUDIT.md's output path and moves straight to the read-only constraint without ever instructing that it be written or describing what it should contain, unlike befund-portfolio which states its report's row-by-row shape explicitly. | Add a sentence in Step 4 describing UI_AUDIT.md's content (e.g. one section per finding category) and an explicit instruction to write it with the Write tool. | sonnet |
nit (2)
| where | rule | claim | fix | tier |
|---|---|---|---|---|
plugins/lehre/agents/pattern-investigator.md:24 | Q-PROC-CLAUDEMD-DUP | The 'burned repeatedly by guards that exist and are never called' rationale for preferring find_referencing_symbols over grep is copied almost word-for-word from the root CLAUDE.md, which already loads automatically. | ⚠ Don't point the reader at 'CLAUDE.md' as the suggested fix does ('see CLAUDE.md on find_referencing_symbols'): pattern-investigator is a lehre-plugin agent dispatched against arbitrary brownfield codebases the user is documenting conventions for, not only against werkstoff itself. A pointer to 'CLAUDE.md' assumes the target repo carries this exact rationale in its own CLAUDE.md, which is a hidden, unstated precondition that will usually be false. Also don't delete the paragraph outright -- some standalone justification for preferring find_referencing_symbols over grep is needed precisely because most target repos won't have werkstoff's CLAUDE.md to fall back on. Instead compress it to a short, self-contained line that drops werkstoff's specific incident framing ('burned repeatedly') without citing an external file: e.g. 'grep for a name finds prose mentions; find_referencing_symbols finds actual call sites -- treat them as different signals.' (conflicts with Q-MEANING-ASSUMED) | sonnet |
plugins/befund/skills/befund-autopilot/SKILL.md:33 | Q-OTHER-STATEFUL | The "(unchanged)" parenthetical is a leftover amendment-log marker comparing this rule to an earlier revision the reader has no access to, with nothing that keeps the comparison current or any reason for it to remain now that the surrounding text is simply the current rule. | Delete the "(unchanged)" parenthetical, leaving: "Rule autopilot-stage-map-first: whichever branch above applies, stage-map's outputs...". Confirmed no other file references this parenthetical or depends on it, so removal is a clean, safe trim. | haiku |
5. Mechanical findings (lint)
| rule | count | files |
|---|---|---|
M-DESC-WHENONLY | 16 | plugins/befund/skills/befund-arch-health/SKILL.md, plugins/befund/skills/befund-autopilot/SKILL.md, plugins/befund/skills/befund-ci-topology/SKILL.md, plugins/befund/skills/befund-code-idiom/SKILL.md +12 |
M-BASH-WILDCARD | 6 | plugins/andon/skills/andon-loop/SKILL.md, plugins/andon/skills/andon-preflight/SKILL.md, plugins/andon/skills/andon-propose/SKILL.md, plugins/andon/skills/andon-status/SKILL.md +2 |
M-AGENT-TOOLS-SHAPE | 4 | plugins/andon/agents/andon-adjudicator.md, plugins/andon/agents/andon-challenger.md, plugins/andon/agents/andon-defender.md, plugins/andon/agents/andon-verifier.md |
M-CMD-DESC-LEN | 1 | plugins/passung/commands/passung-scan.md |
M-DESC-LEN | 1 | plugins/cli-scaffold/agents/cli-scaffold-verifier.md |
M-DESC-PERSON | 1 | plugins/zirkel/skills/zirkel-calibrate-format/SKILL.md |
M-DESC-XML | 1 | plugins/cli-scaffold/agents/cli-scaffold-verifier.md |
M-DUP-CONTENT | 1 | plugins/andon/references/parallel-safe-research-protocol.md |
M-REF-TOC | 1 | plugins/lehre/references/ruleset-schema.md |
M-SKILL-VOICE | 1 | plugins/zirkel/skills/zirkel-explore-branches/SKILL.md |
6. Fix backlog by model tier
Each entry names the cheapest model that can apply it without judgement it lacks. The lint findings are all haiku-tier by construction.
haiku (44)
| plugin | file | rules | action |
|---|---|---|---|
| andon | plugins/andon/references/okf-ledger-schema.md | Q-MEANING-CONTRACT | Fix the settings table: change the output_dir row to 'Where PREFLIGHT.md, etc. live.' (drop the ANDON_BOARD.md claim) and append to the ledger_dir row that the rendered ANDON_BOARD.html lives there, matching andon-status/SKILL.md line 76 and build_board_html.py. |
| passung | plugins/passung/commands/passung-align.md | Q-STEPS-GATE | Extend the no-approved-brief gate to name its remedial command — append '— run /passung-brief $1 to produce and get one approved' — matching how brief.md and verify.md name theirs (the file currently never mentions /passung-brief at all). |
| zirkel | plugins/zirkel/skills/zirkel-map-relationships/SKILL.md | ROUTING-COLLISION | Append a negative trigger to the description naming befund-stage-map and repo-architecture phrasings ('map the architecture of this repo', import graphs, module boundaries), leaving the existing positive triggers word-for-word intact; befund-stage-map needs no change. |
| zirkel | plugins/zirkel/workflows/solve.js | Q-MEANING-ASSUMED, Q-MEANING-CONTRACT | Read priorClarify, priorExplore, requestedBranches and maxBranchCount from parsedArgs rather than the raw args everywhere they are used (lines ~140 and ~144), matching how rawTask, multipleApproaches and successCriteria already do it, so prior-run reuse and branch-count overrides survive a JSON-string args payload. |
| zirkel | plugins/zirkel/workflows/solve.js | Q-MEANING-CONTRACT | Move phasesRun.push('Revise') (line 211) inside the criteria.length >= 3 block so phases_run stops claiming Revise ran when the whole revise block was skipped and revised is null; leave the phase('Revise') log call where it is. |
| zeugnis | plugins/zeugnis/skills/zeugnis-assertion-audit/SKILL.md | ROUTE-COLLISION-ANDON-VERIFY | Drop or requalify the description's trailing enforcement clause 'Verification is mandatory and cannot be skipped by any setting' (a note to the executing model, already stated in the body, and the only landing spot for generic verify-this-is-correct prompts) and add a negative trigger pointing adversarial change-verification at andon-verify. |
| zeugnis | plugins/zeugnis/skills/zeugnis-contract-drift/SKILL.md | ROUTE-COLLISION-DOCS-DRIFT | Insert a negative trigger into the frontmatter description — the body's 'never prose documentation, only structural machine-checkable declarations' rule is invisible to the router while the word 'docstrings' reads as 'docs' — e.g. 'not for checking prose docs (CLAUDE.md/README/ADR) against code — use befund-docs-drift', preserving every existing trigger phrase. |
| cupertino | plugins/cupertino/agents/handbook-drift-auditor.md | Q-PROC-TOOLS-ROLE | Drop Glob from the agent's tools: frontmatter, leaving Read, Grep, Bash — target files are always named in the dispatch and never discovered, so the grant dangles the exact scope creep the body refuses. |
| lehre | plugins/lehre/skills/lehre-codify/SKILL.md | `R-COLLIDE:lehre-codify | cupertino-handbook-draft, R-COLLIDE:lehre-codify |
| befund | plugins/befund/agents/{convention-auditor,business-rules-miner,arch-health-auditor,idiom-auditor,ui-auditor}.md | Q-PROC-TOOLS-ROLE | Remove Bash from the tools line of all five read-only auditor agents (leaving Read, Glob, Grep) -- none of their responsibilities, triggers or refusals names a shell command, and three of them explicitly state they are read-only; pure deletion with no follow-on edits. |
| befund | plugins/befund/skills/befund-lint-audit/SKILL.md | Q-CLARITY-AMBIG | Restore the three words dropped from the staleness sentence -- 'if missing or its source_fingerprint no longer matches the repository, run ...' -- rather than inventing a new gloss; the full definition already lives in references/parallel-safe-research-protocol.md, which this step links. |
| befund | plugins/befund/skills/{befund-docs-drift,befund-lint-audit,befund-idiom-fix,befund-ui-audit,befund-extract-rules,befund-transform-execute}/SKILL.md + plugins/befund/agents/idiom-remediator.md | Q-CANN-NEGATIVE | Append one negative-trigger clause to each description naming the sibling that owns the excluded territory: docs-drift excludes CI/CD doc claims (befund-ci-topology) and structural contract/docstring declarations (zeugnis-contract-drift); lint-audit reports only, fixes go to befund-idiom-fix, and handbook-sourced rules to cupertino-handbook-check; idiom-fix consumes code_idiom_summary.json modernization rows, not cupertino-handbook-fix's mechanical handbook findings; ui-audit is a static pass over existing UI, not cupertino-council/cupertino-review design work; extract-rules mines what the code decides, not lehre-codify's how-it-is-written doctrine; transform-execute applies one authorized MODERNIZATION_BRIEF.md phase, not andon-loop's repo-wide ledger pass; idiom-remediator is dispatch-only and is not handbook-remediator or conformance-remediator. Keep existing trigger phrasing verbatim and watch YAML quoting -- a frontmatter parse failure loads the skill with an empty description. |
| andon | plugins/andon/agents/andon-adjudicator.md, plugins/andon/agents/andon-challenger.md, plugins/andon/agents/andon-defender.md, plugins/andon/agents/andon-verifier.md | M-AGENT-TOOLS-SHAPE | Convert the frontmatter tools: YAML list to the canonical comma-separated string form in all four tribunal agents, preserving each agent's existing tool set exactly. |
| andon | plugins/andon/skills/andon-loop/SKILL.md | Q-CANN-NEGATIVE | Append one sentence to andon-loop's description distinguishing it from befund-transform-execute by artifact -- a repo-wide ledger pass, not a single authorized MODERNIZATION_BRIEF.md phase -- with the mirror sentence added on the befund side; pure insertion, no rewrite of existing trigger text. |
| andon | plugins/andon/skills/andon-verify/SKILL.md | Q-CANN-NEGATIVE | Append a 'not for X -- use Y' clause pointing at zeugnis-assertion-audit (judging whether an existing test suite would catch mutations) with the mirror clause on zeugnis's side, leaving both sets of existing trigger clauses verbatim; this also inoculates the reverse 'are my tests any good, adversarially' case. |
| cli-scaffold | plugins/cli-scaffold/commands/cli-scaffold.md | M-BASH-WILDCARD | Replace the unscoped Bash grant in allowed-tools with the specific commands the command actually runs (the python3 invocations of scripts/lang_router.py and scripts/verify_scaffold.py). |
| cli-scaffold | plugins/cli-scaffold/skills/cli-architecture/SKILL.md | Q-MEANING-PROMISE | Do NOT renumber 12 to 13 — scripts/constants.py asserts LANGUAGE_COUNT = 12 at import and states POSIX sh 'does not add to LANGUAGE_COUNT'. Instead disambiguate all three mentions (description, line 38, and the '## The 12 languages and 3 paradigms' heading) to read '12 languages plus the POSIX sh dialect', so the prose stops appearing to enumerate 13 under a count of 12. |
| cli-scaffold | plugins/cli-scaffold/skills/cli-scaffold-compiled/SKILL.md | Q-MEANING-PROMISE | Promote the buried parenthetical '(dispatched here by scaffold-cli)' into its own sentence stating this is not the entry point and that a raw user request goes to scaffold-cli first, so a router stops scoring this skill against bare prompts like 'scaffold a new command-line tool in Rust'; scope and doctrine loading are unchanged. |
| cli-scaffold | plugins/cli-scaffold/skills/cli-scaffold-interpreted/references/ruby.md | Q-CLARITY-OPTIONS | Replace the two-option sentence at line 22 with a stated default matching perl.md and posix-sh.md: declare completion.supported: false in the manifest, with a hand-written bash-completion file named as an optional addition on top of that default rather than an equal alternative. |
| passung | plugins/passung/commands/passung-canonize.md, plugins/passung/commands/passung-scan.md | Q-CLARITY-TERMS | Rename scan.md's [convention-pattern] argument-hint and its prose reference to [dimension-pattern], matching canonize.md — scan.md's own headings and JSON schema already speak of dimensions, so the alternative would leave it self-inconsistent. |
| passung | plugins/passung/commands/passung-map.md | Q-STEPS-NUMBERED | Number the '## Build the matrix data' and '## Render' headings as Step 1 / Step 2, matching align.md, canonize.md and verify.md, since the body already says one is 'written in the previous step'. |
| passung | plugins/passung/commands/passung-map.md | ROUTING-COLLISION | Add a precondition clause and a negative trigger to map.md's frontmatter description only — state that it renders an inventory /passung-scan must already have produced, and point discovery prompts ('where does this repo do the same thing two different ways?') at /passung-scan; scan.md needs no change and the scan -> map order stays as is. |
| zirkel | plugins/zirkel/references/parallel-safe-research-protocol.md | Q-CLARITY-AMBIG | Replace the undefined 'the run coordinator' at line 83 with this file's own established phrase from line 72, 'the calling skill or agent', rather than coining a third variant. |
| zirkel | plugins/zirkel/skills/zirkel-calibrate-format/SKILL.md | M-DESC-PERSON | Rewrite the description in third person, removing the first-person 'I can' phrasing. |
| zirkel | plugins/zirkel/skills/zirkel-explore-branches/SKILL.md | Q-CLARITY-CONCISE, M-SKILL-VOICE | Trim the line-34 sentence to keep the actionable skip-step-0 threshold and the pointer to 'Scale guidance' in references/parallel-safe-research-protocol.md while dropping the duplicated rationale, and rewrite the one second-person line into the third-person voice the other skills use. |
| zirkel | plugins/zirkel/workflows/solve.js | Q-CLARITY-CONCISE | Append the separate 'Scoped task:' line to the Decompose prompt only when explore is set, since without a strategic fork approach is already clarify.scoped_task and the same text is sent twice. |
| zeugnis | plugins/zeugnis/agents/agentic-reliability-auditor.md | Q-MEANING-PROMISE | Insert "commands/, " into the frontmatter description's file-scope list so it matches the body's actual scope (skills/, agents/, commands/, workflows/*); the description is 484 of 1024 chars, so the addition is safe. |
| zeugnis | plugins/zeugnis/agents/assertion-auditor.md | Q-MEANING-ASSUMED | Reword Suggest mode to name its dispatch-prompt input like the two modes above it: 'given a confirmed-weak-mutation finding in your dispatch prompt, draft a replacement or additional assertion that WOULD catch it.' |
| zeugnis | plugins/zeugnis/agents/dependency-auditor.md | Q-CLARITY-EXAMPLE | Add a third object to the fenced output-contract example covering the typosquat-adjacent category (the only one of the three promised categories with no worked instance), e.g. {"severity": "Medium", "title": "declared dependency 'python-dateutil-utils' is one character from popular 'python-dateutil'", "evidence": "requirements.txt:14", "category": "typosquat-adjacent", "fixability": "advisory"}. |
| cupertino | plugins/cupertino/agents/handbook-remediator.md | Q-MEANING-CONTRACT | In the Refuse bullet (line 25) change 'report it as skipped' to 'report it as blocked' so the instruction matches the declared status enum applied|blocked, with the reason carried in the existing change field — do not add a third enum value. |
| cupertino | plugins/cupertino/agents/handbook-remediator.md | routing-collision | Append to the description that dispatch comes only from cupertino-handbook-fix (not a bare fix request) and that language-idiom findings belong to idiom-remediator and doctrine-rule violations to conformance-remediator; make the reciprocal insertion on those siblings' descriptions. |
| cupertino | plugins/cupertino/agents/handbook-verifier.md | Q-CLARITY-EXAMPLE | Change the output template's "compliant": true to "compliant": true|false so the non-compliant branch the body requires is visible, matching the enum style of the sibling agents' schemas. |
| cupertino | plugins/cupertino/skills/cupertino-elevate/SKILL.md | Q-CLARITY-TERMS | Replace both occurrences of 'commodity function' (step 1's bold lead-in and the Output Format arrow chain) with 'commodity feature' so one term is used throughout the file. |
| cupertino | plugins/cupertino/skills/cupertino-handbook-draft/SKILL.md | routing-collision | Append a negative-trigger clause to the description naming lehre-codify for prescriptive 'what rules should this project follow' requests, and stating this skill records what the project already does per dimension into a prose handbook — a pure insertion, existing wording unchanged. |
| cupertino | plugins/cupertino/skills/cupertino-handbook-fix/SKILL.md | routing-collision | Append a negative trigger naming befund-idiom-fix as the owner of modernization/idiom findings and stating this skill requires .cupertino/handbook_check_<domain>_summary.json; keep the description a valid double-quoted YAML scalar (inner quotes single) so lint-frontmatter.py still parses it. |
| lehre | plugins/lehre/agents/conformance-remediator.md | `R-COLLIDE:conformance-remediator | handbook-remediator, R-COLLIDE:conformance-remediator |
| lehre | plugins/lehre/references/ruleset-schema.md | Q-CLARITY-AMBIG | Change 'Five kinds' to 'Six kinds' so the prose count matches the six-row check.kind table above it and 'judgement' is unambiguously inside the closed vocabulary. |
| lehre | plugins/lehre/references/ruleset-schema.md | M-REF-TOC | Add a table of contents within the first 40 lines of this 231-line reference file. |
| lehre | plugins/lehre/skills/lehre-gauge/SKILL.md | Q-CLARITY-TERMS | Rename the per-rule count label in the output block from 'hits'/'hit' to 'violations'/'violation', including the singular '1 hit' on the no-utils-dumping-ground row, so one concept uses one term. |
| lehre | plugins/lehre/skills/lehre-validate/SKILL.md | Q-CLARITY-CONCISE | Delete the first Rules bullet ('Close on a clean result only…') outright — the intro paragraph carries the rationale and step 6 carries the procedural gate; the remaining three bullets need no rewording. |
| befund | plugins/befund/skills/befund-status/SKILL.md | Q-CLARITY-TERMS | Replace both 'sidecar JSON file' (line 23) and 'reporting sidecar' (line 53) with 'artifact', the term the CLI flag and the rest of the file already use -- fixing only the first leaves the second term in place. |
| befund | plugins/befund/skills/{befund-arch-health,befund-autopilot,befund-ci-topology,befund-code-idiom,befund-complexity-score,befund-docs-drift,befund-extract-rules,befund-idiom-fix,befund-lint-audit,befund-portfolio,befund-preflight,befund-stage-map,befund-status,befund-transform-brief,befund-transform-execute,befund-ui-audit}/SKILL.md | M-DESC-WHENONLY | Rewrite all 16 descriptions to lead with what the skill does, then the when-clause, instead of opening with the 'This skill should be used when...' template; preserve every existing quoted trigger phrase verbatim. |
| passung | plugins/passung/commands/passung-scan.md | M-CMD-DESC-LEN | Shorten the 160-character command description so it renders cleanly in /help, keeping its two explicit exclusions intact. |
| befund | plugins/befund/skills/befund-autopilot/SKILL.md | Q-OTHER-STATEFUL | Delete the leftover '(unchanged)' amendment marker at line 33, leaving 'Rule autopilot-stage-map-first: whichever branch above applies, stage-map's outputs...'; nothing references it. |
sonnet (132)
| plugin | file | rules | action |
|---|---|---|---|
| cupertino | plugins/cupertino/workflows/handbook-fix.js | Q-MEANING-ASSUMED | Gate the incoming findings once, before clustering: throw a named error if args.findings is not an array (line 67), throw naming the offending finding if any entry lacks a non-empty string .rule (line 77), and fold verification.verdict === 'confirmed' into the existing mechanical === true predicate (line 65), extending skippedCount and its log message to report non-mechanical and not-independently-confirmed counts separately. |
| andon | plugins/andon/agents/andon-verifier.md, plugins/andon/agents/andon-adjudicator.md | Q-STEPS-INPUTS | Assign Tier 1 structural-evidence gathering to the Verifier: state in andon-verifier.md's 'What to produce' that for structural claims it runs the Kythe/SCIP/LSIF index query and reports the tier and any contradiction as one of its facts, then update andon-adjudicator.md's refusals and 'What to produce' to say the tier/non_overridable flag arrives as a field of the Verifier's report -- not as a capability the Adjudicator exercises (it holds only Read/Grep/Glob). |
| andon | plugins/andon/references/okf-ledger-schema.md | Q-MEANING-CONTRACT | Under the evidence doc-type list only (around line 58), document 'Optional: lane -- one of fast, slow. Not currently validated; used only to emit the lane: tag.' so the orphaned lane:fast|slow tag has a stated source; do not add the field to the gap doc-type section, since build_tags_for_doc() reads it for evidence docs only. |
| andon | plugins/andon/skills/andon-loop/SKILL.md | Q-STEPS-INPUTS | Add one sentence at the top of Phase 5 or Phase 6 telling the orchestrator to tally gaps closed since the last pass boundary and to build the wire-status array from each inter-stage wire's latest evidence-doc verdict, cross-referencing andon-status/SKILL.md line 35's existing derivation rule rather than restating it, so Phase 6's check-convergence arguments have a named source. |
| andon | plugins/andon/skills/andon-preflight/SKILL.md | Q-MEANING-PROMISE | Extend the description's no-writes carve-out (line 3) to name PREFLIGHT.md and preflight_summary.json, the two files Step 3 actually writes to output_dir, or move that writing responsibility elsewhere. |
| andon | plugins/andon/skills/andon-propose/SKILL.md | Q-MEANING-ASSUMED, Q-STEPS-PREDICATE | Replace step 2's 'andon-preflight reported it present' gate with a direct on-disk existence check of .claude/house-rules.md (or house_rules_path), falling back to codebase-only defaults only when the file is actually absent, so the documented direct-invocation path does not silently skip a real house-rules file. |
| cli-scaffold | plugins/cli-scaffold/agents/cli-scaffold-verifier.md | M-DESC-LEN, M-DESC-XML, Q-MEANING-PROMISE, Q-CLARITY-TERMS | Rewrite the frontmatter description: drop the embedded <example> blocks (they violate M-DESC-XML and are the largest cut available), bring it from 1361 to under 1024 chars, say 'reports findings as either fixable or needs-human-judgment' instead of 'gaps', and either add a short clause covering standalone user-initiated verification or drop the third example so the stated trigger matches the demonstrated scope. |
| cli-scaffold | plugins/cli-scaffold/agents/cli-scaffold-verifier.md | Q-STEPS-GATE | Add one sentence after step 2's exit-code list: on exit 2 (usage/scope error), do not proceed to step 3 — report the usage/scope error to the calling skill and stop; do not add a second branch in step 3 or 4. |
| cli-scaffold | plugins/cli-scaffold/agents/cli-scaffold-verifier.md | Q-STEPS-TERMINATION, Q-STEPS-GATE | Add exactly one bullet to step 4 instructing the agent to report the HALT / MAX_FIX_ITERATIONS-exhausted outcome to the calling skill as its own named result, alongside pass and gaps. |
| cli-scaffold | plugins/cli-scaffold/agents/cli-scaffold-verifier.md | Q-MEANING-ASSUMED | State in step 2 whether a report path is printed on the exit-0 pass branch, or scope step 3's 'read the report JSON' to the branch where a path is actually emitted. |
| cli-scaffold | plugins/cli-scaffold/skills/cli-scaffold-compiled/SKILL.md | Q-MEANING-ASSUMED | In all three paradigm skills (cli-scaffold-compiled, cli-scaffold-interpreted, cli-scaffold-shell SKILL.md), state that 'the resolved language/dialect' is expected to arrive from scaffold-cli's dispatch, and on direct invocation either stop and name scaffold-cli as the entry point, or re-run scripts/lang_router.py and follow through only when its paradigm matches this skill — mirroring scaffold-cli's own AMBIGUOUS and UNSUPPORTED handling and refusing on a paradigm mismatch rather than scaffolding anyway. |
| cli-scaffold | plugins/cli-scaffold/skills/scaffold-cli/SKILL.md | Q-STEPS-TERMINATION | Bound step 1's AMBIGUOUS branch: allow one clarification retry, and if the router's second run also exits AMBIGUOUS or the reply matches none of the printed options, fall back to the UNSUPPORTED branch's existing behaviour (list the supported options via lang_router.py's _supported_list() and ask for an exact match). |
| passung | plugins/passung/agents/align-executor.md | Q-MEANING-ASSUMED, Q-STEPS-GATE | In the 'Read these first' section, give CANON.json a STOP clause parallel to the existing PLAYBOOK.md one ('if CANON.json does not exist, STOP, report that the canonical form for this module has not been determined, and edit no file'), and replace the bare 'refuse' on the undecided-dimension gate with the same shape ('report that the dimension is undecided and do not edit any file for that module'). |
| passung | plugins/passung/commands/passung-align.md | Q-MEANING-ASSUMED | Before the workflow-call block, state that the required per-unit deps is derived by inspecting each remaining module's actual references onto the canonical form the pilot introduced (imports, shared types/utilities), not read from CANON.json, whose documented divergentSites schema is { module, files, count } and carries no deps data. |
| passung | plugins/passung/commands/passung-align.md | Q-STEPS-GATE | In Step 1, immediately after running the pilot module's own tests, add a gate: if the pilot's tests fail, do not write PLAYBOOK.md — revise the canonical-form choice or escalate to the human who approved the brief, because Step 2's fan-out must not start from an unproven pilot. |
| passung | plugins/passung/commands/passung-align.md, plugins/passung/commands/passung-verify.md | Q-CLARITY-AMBIG | Bind the argument-hint's [dimension] to $2 in the opening sentence of both files (as scan.md and canonize.md already do: 'If a [X] was given ($2)…'), stating the default when omitted, and pass dimension: "$2" in each Workflow call instead of the unbound placeholder "<dimension>". |
| passung | plugins/passung/commands/passung-canonize.md | Q-MEANING-ASSUMED | At the top of Step 2, instruct reading analysis/$1/PREFLIGHT.md's Check 5 verdict if present and, when it is red or the file is absent, skipping the maturity signal, weighting by frequency only and capping that dimension's Pattern Card confidence — reusing preflight's own already-stated fallback wording so the two files state one contract. |
| passung | plugins/passung/commands/passung-verify.md | Q-MEANING-ASSUMED | Add a step before the Workflow call that enumerates the modules /passung-align touched by reading analysis/$1/ALIGN_NOTES.md and passes them as a units: [{ name, path }] arg (no deps field — verify's fan-out is independent per module, not dependency-ordered), since the workflow script has no filesystem access. |
| passung | plugins/passung/workflows/scan.js | Q-MEANING-CONTRACT | Delete the dead toolReport path — the schema field (line 70), the toolReports array and its population (lines 130, 133) and the returned key (line 191) — because the finder prompt never instructs the agent to run a linter/formatter and repo-wide grep shows no consumer outside scan.js; only add a lint-run instruction to the prompt instead if a downstream consumer is actually planned. |
| passung | plugins/passung/workflows/scan.js, plugins/passung/workflows/verify.js, plugins/passung/workflows/canonize.js | Q-MEANING-CONTRACT | Add an injectionSuspects field (array of strings) to every schema whose agent call embeds the shared UNTRUSTED block — scan.js VERDICT_SCHEMA, verify.js CHECK_SCHEMA and RECHECK_SCHEMA, canonize.js PANEL_SCHEMA and SITES_SCHEMA — using that one name everywhere (rename the code's existing v.injectionSuspected read at verify.js:135 to match rather than adding a second singular field), and wire each into the existing injectionFlags array, replacing verify.js's dead for (const s of []) loop with a real push. |
| passung | plugins/passung/workflows/verify.js | Q-MEANING-ASSUMED | Extend meta.whenToUse to name analysis/<area>/ALIGN_NOTES.md as a required prior-pass artifact alongside the existing args list, since the Check-phase prompt already assumes it exists. |
| zirkel | plugins/zirkel/agents/branch-proposer.md | Q-PROC-OUTPUT-SHOWN, Q-MEANING-ASSUMED | Add two 3-4 line fenced JSON examples matching the schemas explore-branches.js already enforces ({name, description} for Propose; {feasibility, impact, risk, biggest_blocker} lowercase integers 1-10 for Score), and state under Score that scoring is deliberately judged from the branch name and description alone — the dispatch prompt at lines 87-93 never passes the original scoped problem. |
| zirkel | plugins/zirkel/agents/instruction-candidate.md | Q-PROC-TOOLS-ROLE, Q-PROC-OUTPUT-SHOWN | Drop the tools: line granting Read/Glob/Grep — none of the Draft, Score or Critique jobs gathers information beyond the dispatch prompt, unlike the siblings that justify the same grant in-body — and add a short fenced example of the Critique checklist ({item, verdict, reason} entries plus final_prompt) showing the all-pass case as the normal shape. |
| zirkel | plugins/zirkel/agents/reasoning-path.md | Q-MEANING-ASSUMED | Name the input in one sentence after the opening paragraph, mirroring branch-proposer.md and instruction-candidate.md: a problem statement (optionally with an image or diagram, and candidate answers for the backward-from-options strategy) plus the assigned strategy, before 'the input' and 'candidate answers' are used. |
| zirkel | plugins/zirkel/skills/zirkel-clarify-scope/SKILL.md | Q-MEANING-PROMISE | Correct the two code-enforcement claims against what validate_clarify() actually does: reword the intro so only known_facts and flagged_uncertainties are described as guard-validated (scoped_task and success_criteria have no code-level check), and move the presentation-time bullet out of '## The rules (enforced in code)' or mark it as a non-guard-checkable discipline obligation. |
| zirkel | plugins/zirkel/skills/zirkel-decompose-chain/SKILL.md | Q-MEANING-CONTRACT | State in the Output section that stage/wave output is not separately persisted — it is captured only by zirkel-solve's single end-of-pipeline state write, and zirkel-solve's Decompose step performs no prior-run reuse check unlike Clarify and Explore; do not add a Persist step nothing reads back. |
| zirkel | plugins/zirkel/skills/zirkel-draft-revise/SKILL.md | Q-STEPS-TERMINATION | Add the escalation step 4 is named for: after the second cycle, stop looping and report which criteria remain below threshold rather than presenting the draft as resolved — the behaviour zirkel's README already documents under 'Choices made where the spec was silent' (#6). |
| zirkel | plugins/zirkel/skills/zirkel-optimize-instruction/SKILL.md | Q-STEPS-GATE, Q-PROC-OUTPUT-SHOWN | Give the step-3 candidates-guard call a bounded remedial path (regenerate only the missing or duplicate framing and re-validate; after 2 attempts stop and report which framings could not be produced), and add one fenced example of the assembled Output — candidates table, winner, critique and a sample final prompt. |
| zirkel | plugins/zirkel/skills/zirkel-summarize-trace/SKILL.md | Q-MEANING-ASSUMED | Add a single '## Input' section naming .zirkel/runs/<id>/state.json as the source of dag.stages, explore_ran and the per-section content, and stating what to do when it is missing — one section, not two, covering both undeclared-input findings. |
| zirkel | plugins/zirkel/skills/zirkel-summarize-trace/SKILL.md | Q-MEANING-PROMISE | Reword the description's unconditional 'exactly 7 sections' to '7 sections (6 if Explore did not run)', matching the body's own omission rule and Output line. |
| zirkel | plugins/zirkel/workflows/explore-branches.js | Q-MEANING-CONTRACT | Invert risk before summing (total = feasibility + impact + (SCORE_MAX + SCORE_MIN - risk)), leave the ascending a.risk - b.risk tie-break unchanged, and add a 'higher = more dangerous' description to SCORE_SCHEMA.properties.risk and the scoring prompt; do not instead redocument risk as 'higher = safer', which would contradict the comparator. |
| zirkel | plugins/zirkel/workflows/explore-branches.js | Q-STEPS-GATE | After filtering nulls in the Score step, log the dropped branch names when scored.length < branches.length and throw only when scored.length < 2, matching the soft degradation the Propose step already accepts rather than requiring exact equality. |
| zirkel | plugins/zirkel/workflows/optimize-instruction.js | Q-STEPS-GATE | Mirror the Generate step's pattern in the Score step: .filter(Boolean) on the parallel() result then if (scored.length !== CANDIDATE_COUNT) throw, so a dropped dispatch fails by name instead of throwing an opaque TypeError inside .then((s) => ({ ...c, score: s.passed })). |
| zirkel | plugins/zirkel/workflows/reason-verify.js | Q-MEANING-ASSUMED | Make the existing channel explicit rather than adding a schema field: change the multimodal instruction at line 49 to say the visual is described in the task text, and add one sentence to zirkel-reason-verify's SKILL.md args line stating that when hasImageOrDiagram is true, task must include a textual description of the image or diagram. |
| zirkel | plugins/zirkel/workflows/solve.js | Q-MEANING-PROMISE, Q-CLARITY-AMBIG | Rename or otherwise decouple the four MODES labels from the sibling skill names (Execute makes one generic agent() call per stage and never runs reason-verify.js's 3-attempt majority vote), then define each decoupled label inline as an informal self-report category so the choice at line 197 rests on a stated criterion rather than the name alone — sequence the definition after the rename, not before. |
| zirkel | plugins/zirkel/workflows/solve.js | Q-MEANING-CONTRACT | Document both return shapes together in meta (or a comment above the Clarify gate): the PAUSED_AWAITING_USER shape with status/reason/scoped_task/blocking_uncertainties/next, and the COMPLETE shape with status/scoped_task/explore/stage_plan/waves/composed_result/revised/phases_run. |
| zeugnis | plugins/zeugnis/skills/zeugnis-agentic-reliability/SKILL.md | Q-STEPS-GATE | Do NOT claim the script exits non-zero on a miscategorized finding — agentic_reliability.py drops it via continue and still returns 0; instead add a return path after step 4 telling the dispatching agent to scan stderr for WARNING: dropping invalid finding lines and re-dispatch Find mode with the category constraint restated rather than trusting the reported finding count. |
| zeugnis | plugins/zeugnis/skills/zeugnis-code-change/SKILL.md | Q-MEANING-PROMISE | Change step 3's dependency_audit line to call scripts/dependency_audit.py <repo_root> --skip-verification so this domain matches the skill's stated single-check, un-verified framing — keep the script call (it performs the real registry lookups) and keep any dependency-auditor dispatch scoped as a supplement, not a replacement. |
| zeugnis | plugins/zeugnis/skills/zeugnis-cycle/SKILL.md | Q-STEPS-GATE | Split step 4's non-zero-exit handling: check for cycle_engine.py plan-next-pass's specific cap-reached signal (exit code/stderr) before taking the benign 'stop and wrap up' path, and name an explicit surface-the-error path for any other non-zero exit (bad repo_root, corrupted ledger, invalid --mode). |
| zeugnis | plugins/zeugnis/skills/zeugnis-cycle/SKILL.md | Q-CLARITY-AMBIG | Do NOT add a fifth outcome literal (lib/ledger.py's apply_outcome is an unguarded if/elif over fixed/blocked/drafted/reopened and would silently no-op); instead state in steps 5-6 that a propose-mode-described fixable finding is recorded with outcome "blocked" and a blockReason such as "not applied: cycle is in propose mode", and add a sentence that convergence in propose mode must not be read as 'nothing needs attention'. |
| cupertino | plugins/cupertino/references/parallel-safe-research-protocol.md | Q-CLARITY-AMBIG | Define 'the run coordinator' and 'the assigned Git worktree' in plugin-agnostic terms in the canonical source tools/symbol-indexer/parallel-safe-research-protocol.md (never in the synced cupertino copy) and run rrt artifacts --regenerate so every plugin's copy stays byte-identical. |
| cupertino | plugins/cupertino/skills/cupertino-council/SKILL.md | Q-MEANING-ASSUMED, Q-CANN-NEGATIVE, routing-collision | Disclose the backwards gate once in the body — a PreToolUse hook blocks this skill until cupertino-backwards has run and set the backwards-done marker; run cupertino-backwards first on denial — mirroring cupertino-focus's wording, and add negative-trigger clauses to the description saying it is not for a not-yet-scoped new feature (use cupertino-backwards) nor for reviewing UI that already exists (use befund-ui-audit, or cupertino-review for a whole-project pass). |
| cupertino | plugins/cupertino/skills/cupertino-council/SKILL.md | Q-STEPS-GATE | Give step 4's tension-order validation a failure path: if the resolved order violates the fixed precedence, return to step 3 and re-resolve the offending tension before proceeding, matching step 4 of cupertino-focus and cupertino-backwards. |
| cupertino | plugins/cupertino/skills/cupertino-elevate/SKILL.md | Q-STEPS-TERMINATION | Bound the status-flip retry loop in step 4: after two revisions that still fail the check, stop and tell the user this feature may not be a good elevate candidate instead of looping back to step 3 indefinitely. |
| cupertino | plugins/cupertino/skills/cupertino-handbook-check/SKILL.md | Q-STEPS-GATE | Rewrite step 6 so the single flag-cleanup instruction covers both paths — clear handbook-check-active when done and immediately, before stopping, if step 4 or step 5 fails or is interrupted — and leave step 3 as the plain 'set active' step with no second cleanup sentence. |
| cupertino | plugins/cupertino/skills/cupertino-handbook-check/SKILL.md | Q-CANN-NEGATIVE, routing-collision | Edit the description to disambiguate by artifact: remove the generic trigger 'check this against our standards' (measured to collide with befund-lint-audit, which owns CLAUDE.md/house-rules.md prompts), state that this skill reads .cupertino/<domain>-handbook.md, and name cupertino-handbook-draft as the skill to run first when no handbook exists. |
| cupertino | plugins/cupertino/skills/cupertino-handbook-draft/SKILL.md | Q-STEPS-GATE | State the re-verification gate's failure outcome explicitly at line 20 — a candidate rule that fails re-verification is discarded and its dimension is written as scaffolded rather than analyzed — matching handbook-check's 'dropped, not reported' clarity. |
| cupertino | plugins/cupertino/skills/cupertino-handbook-fix/SKILL.md | Q-STEPS-GATE | After step 5 add an explicit branch stating that an errored, interrupted, or verifier-rejected fix run still runs step 6 to clear handbook-fix-active before reporting failure, with the escape hatch of telling the user to clear it manually if the state file itself cannot be written — matching cupertino-review's 'success, early stop, or the user cancels' pattern. |
| cupertino | plugins/cupertino/skills/cupertino-review/SKILL.md | Q-CLARITY-AMBIG, Q-STEPS-GATE | Move the 'Clear every flag when you finish' bash block out of 'Before you start' into a new subsection placed after 'Render the flow diagram before cleanup' so 'below' is physically true, and add immediately after the build_review_flow_html.py command: if it exits non-zero, stop and report before any flag cleanup, since clearing would discard the only remaining copy of every stage's persisted content. |
| cupertino | plugins/cupertino/workflows/handbook-check.js | Q-STEPS-GATE, Q-MEANING-ASSUMED | Make silent drops visible: after the existing rules guard (line 69) loop over rules and throw cupertino-handbook-check: rules[<i>] is missing a non-empty 'rule' field for any entry without one, and before the perRule.filter(Boolean) line (119) count null slots directly (perRule.filter(r => r == null).length) and log how many rules' find-step failed — do not compare perRule.length to rules.length, which pipeline() semantics make a permanent no-op. |
| lehre | plugins/lehre/agents/conformance-remediator.md | Q-MEANING-ASSUMED | Add a Rules bullet mirroring the existing unverified-finding refusal: refuse a finding whose dispatch does not state which approved brief phase it came from, so both declared preconditions are enforced rather than only verification. |
| lehre | plugins/lehre/agents/pattern-investigator.md | Q-PROC-TOOLS-ROLE, Q-MEANING-ASSUMED, Q-PROC-CLAUDEMD-DUP | Rewrite the 'How to survey' serena paragraph to match the granted tools (Read, Glob, Grep, Bash) — e.g. grep for the symbol name then Read each match to tell a real call site from a prose mention — dropping all references to serena/find_symbol/find_referencing_symbols and the werkstoff-specific 'burned repeatedly' framing copied from the root CLAUDE.md; do not add serena to tools:, since this agent is dispatched against arbitrary target repos. |
| lehre | plugins/lehre/agents/spec-fidelity-auditor.md | Q-MEANING-ASSUMED | Split the missing-input fallback rather than adding one blanket refusal: if no recorded intent can be produced, say so and decline to verdict at all; if intent exists but no owns/must not know lines are given, still check every intent clause and report the must-not-know section as 'UNVERIFIABLE — no owns/must-not-know lines given', preserving the per-clause N/A design the worked example already uses. |
| lehre | plugins/lehre/skills/lehre-brief/SKILL.md | Q-MEANING-CONTRACT | Add approved_phases: [] to the LEHRE_BRIEF.md frontmatter example (lines 46-53), state in Rules/step 6 that lehre-brief itself rewrites approved_phases and status when it resumes after the user approves in chat, and change lehre-conform's step 1 Brownfield bullet to read that exact field ('proceed only if the current phase number appears in approved_phases'). |
| lehre | plugins/lehre/skills/lehre-codify/SKILL.md | Q-STEPS-GATE | In step 5, name the failure path for the rule-critic dispatch: if it errors or is unreachable, stop and report — do not write the ruleset unreviewed. |
| lehre | plugins/lehre/skills/lehre-codify/SKILL.md | Q-STEPS-TERMINATION | Bound the fix-then-revalidate loop in step 7: after one failed fix attempt, stop and show the validator error to the user instead of retrying indefinitely. |
| lehre | plugins/lehre/skills/lehre-conform/SKILL.md | Q-MEANING-ASSUMED | Extend step 1's greenfield bullet: keep the existing flow (name the unit, then run lehre_cli.py status — status is the only source of the unit list and takes no unit argument), but after status, if the named unit does not appear among the printed rows or the ruleset has no units at all, say so and stop rather than proceeding to step 2; do not add a pre-status gate, since no artifact exists to gate on. |
| lehre | plugins/lehre/skills/lehre-decompose/SKILL.md | Q-STEPS-GATE | Give step 4's 'reject any ordering that needs a cycle' gate a destination: if no acyclic ordering exists, return to step 3 and re-derive the seams, or surface the conflict to the user. |
| lehre | plugins/lehre/skills/lehre-pin/SKILL.md | Q-STEPS-GATE | Move the lehre-validate precondition out of the trailing Rules list into a step 0 that checks .lehre/units/<unit-id>.done exists (or runs lehre_cli.py status) before step 1 emits the CI check, and on absence stops and names lehre-validate as the skill to run first — naming the concrete artifact/command, as lehre-validate's step 4 and lehre-pin's own step 3 do. |
| lehre | plugins/lehre/skills/lehre-pin/SKILL.md | Q-STEPS-INPUTS | State in step 5 that the seams tests target are read from .lehre/ruleset.json (or wherever the decomposition is recorded), the way lehre-validate names its source, so the input is named before it is consumed. |
| befund | plugins/befund/agents/complexity-surveyor.md | Q-MEANING-CONTRACT, Q-CLARITY-AMBIG | Make the output contract self-consistent: generalize tool_used from the closed radon|lizard|none enum to an open string (the body's must-try list ends in 'etc.'), and remove 'or 0' from the must-refuse line so the single -1 sentinel used everywhere else is the only one. |
| befund | plugins/befund/agents/transform-executor.md | Q-PROC-TOOLS-ROLE | Close the delete/move capability gap from the output side rather than granting Bash (which would break the agent's stated 'only Write/Edit-capable agent' minimal-blast-radius design): change responsibility 3 to list any file needing removal in a new files_to_delete output field for a human or andon-verify, and add that field to the Output format block. |
| befund | plugins/befund/agents/transform-executor.md | Q-MEANING-ASSUMED | State where MODERNIZATION_BRIEF.md comes from -- its expected path, or that the relevant phase content is supplied verbatim in the dispatch prompt -- and what to report when it cannot be found. |
| befund | plugins/befund/agents/{idiom-auditor,idiom-remediator}.md + plugins/befund/skills/befund-code-idiom/SKILL.md | Q-MEANING-CONTRACT | idiom-remediator clusters by (file, kind) but idiom-auditor's schema emits only category/file/line/description/severityNote; add a kind field (an idiom-pattern slug) to idiom-auditor's Output format prose and both fenced examples, and state in befund-code-idiom Step 2 that modernization findings MUST carry kind. |
| befund | plugins/befund/skills/befund-ci-topology/SKILL.md | Q-MEANING-PROMISE | The bundled workflow audits commit signing but the description promises only redundancy, mirror risk and doc-vs-config drift; append 'and commit-signing inconsistencies' to that list (the description is 354/1024 chars, so no rework is needed). |
| befund | plugins/befund/skills/befund-idiom-fix/SKILL.md | Q-STEPS-GATE | Extend Step 5's verify-dispatch-handoff rule with the andon-not-installed fallback: if neither andon:andon-verify nor andon:andon-loop is installed, report plainly that the remediator edits are already applied to the working tree but unverified, and stop -- no self-check substitute. |
| befund | plugins/befund/skills/befund-lint-audit/SKILL.md | Q-MEANING-CONTRACT, Q-STEPS-GATE | In Step 4, state LINT_AUDIT.md's full content and instruct writing it with the Write tool (matching befund-preflight's Step 4), and state the remediation when validate-artifact rejects -- populate rules_skipped from the capped list and re-run validate-artifact before writing. |
| befund | plugins/befund/skills/befund-portfolio/SKILL.md | Q-STEPS-INPUTS | Add a per-repo get-settings --repo <repo> call before Step 3 and read output_dir from it, so 'its configured output_dir' is a value this skill actually produces; do not use check-enabled, whose require_enabled() would hard-fail the sweep on any repo where befund is disabled. |
| befund | plugins/befund/skills/befund-stage-map/SKILL.md | Q-MEANING-CONTRACT | Add one or two sentences to Step 6 saying what stage_map_summary.json contains (a run summary -- stage/wire counts and a timestamp -- used only as a presence flag by befund-status, per staleness.py and status.py), and do NOT route it through validate-artifact, which has no stage_map_summary validator. |
| befund | plugins/befund/skills/befund-stage-map/SKILL.md + plugins/befund/skills/befund-transform-brief/SKILL.md | Q-MEANING-CONTRACT, Q-STEPS-GATE | Reconcile stage_map.json's producer/consumer contract: state plainly in stage-map Step 6 whether it is always written, and add to transform-brief Step 7 the single default 'if stage_map.json does not exist, create it here containing only the flows field, leaving stage/wire population to befund-stage-map'. |
| befund | plugins/befund/skills/befund-transform-brief/SKILL.md | Q-MEANING-ASSUMED, Q-STEPS-GATE | Add absent-input branches for the two optional inputs: a missing arch_health_summary.json is treated as zero findings for every stage (so every phase defaults to Keep(1:1)) with a note in the brief that arch-health did not run, and a missing business_rules_summary.json skips P0-blocker flagging and records it as unavailable -- one degrade rule each, no second halt condition. |
| befund | plugins/befund/skills/{befund-lint-audit,befund-ui-audit}/SKILL.md | Q-STEPS-INPUTS, Q-MEANING-ASSUMED | State in each Step 0 that check-enabled's stdout JSON carries the resolved settings later steps consume -- lint_max_rules for lint-audit, skip_verification for ui-audit -- mirroring the wording already used in befund-preflight and befund-stage-map. |
| andon | plugins/andon/agents/andon-adjudicator.md, plugins/andon/agents/andon-challenger.md, plugins/andon/agents/andon-defender.md, plugins/andon/agents/andon-verifier.md | Q-PROC-OUTPUT-SHOWN, Q-CLARITY-EXAMPLE | Add exactly ONE fenced worked example per agent under its 'What to produce' section -- adjudicator: a mixed pass/fail/neither criterion set plus the overall green/red/unknown line; challenger: one objection with file:line, defect and withdrawal condition; defender: one satisfied criterion with file:line evidence and one explicit-gap criterion; verifier: a claim through command to fenced masked output to a reproduces/contradicts verdict, plus a one-line unverifiable case. One example each satisfies both rules; do not add a second. |
| andon | plugins/andon/references/okf-ledger-schema.md | Q-PROC-OUTPUT-SHOWN | Add one fenced example of a complete gap doc (status: open with a filled proposal object), copied from the existing fixture plugins/andon/scripts/fixtures/sample_ledger/gaps/ingest-retry-storm-on-429.md rather than invented, so the schema shows the artifact shape every write-doc call must satisfy. |
| andon | plugins/andon/skills/andon-loop/SKILL.md | Q-STEPS-INPUTS | In Phase 4 only, pass the gap doc's recorded verify_strategy/verify_strategy_rationale to andon-verify as read-only audit context to echo verbatim into the evidence doc ('proposed strategy: X, actual routed strategy: Y') so mismatches are visible in the ledger; leave andon-verify's Step 1 routing completely unchanged so 'never skip, never default' does not acquire an exemption clause. |
| andon | plugins/andon/skills/andon-loop/SKILL.md, plugins/andon/skills/andon-preflight/SKILL.md, plugins/andon/skills/andon-propose/SKILL.md, plugins/andon/skills/andon-status/SKILL.md, plugins/andon/skills/andon-verify/SKILL.md | M-BASH-WILDCARD | Replace the unscoped ['Bash'] grant in each of the five skills' allowed-tools with command-scoped entries covering the andon_core.py / build_board_html.py invocations each skill actually makes. |
| andon | plugins/andon/skills/andon-preflight/SKILL.md | Q-STEPS-GATE | State that any non-zero exit / AndonError from validate-write-path in Step 3 means the write must not proceed: stop, report the error message verbatim as 'output_dir appears misconfigured', and do not write PREFLIGHT.md -- explicitly no path recomputation or retry, since WRITE_SCOPE_OUTSIDE is a caller misconfiguration a fresh guess cannot fix. |
| andon | plugins/andon/skills/andon-preflight/SKILL.md | Q-STEPS-GATE | Require validate-write-path <output_dir>/preflight_summary.json <repo_root> <output_dir> before writing the JSON output, matching the gate already required for the structurally identical markdown write. |
| andon | plugins/andon/skills/andon-status/SKILL.md | Q-CANN-NEGATIVE | Append a clause to the 339-char description naming the adjacent sibling explicitly, e.g. '...without wanting a new pass run (not andon-loop, which advances the ledger)', matching the convention andon-preflight already follows of naming its siblings. |
| andon | plugins/andon/skills/andon-verify/references/oracle-gap-techniques.md | Q-CLARITY-EXAMPLE | Add a 3-4 line fenced example directly under 'Trust-ledger entry (required)' showing filled-in Quantity checked / Technique used / Significant-digit count values for a synthetic-recovery check, since the exact phrasing (metamorphic tolerance vs significant-digit count) is not obvious from the prose bullets. |
| cli-scaffold | plugins/cli-scaffold/agents/cli-scaffold-verifier.md | Q-PROC-OUTPUT-SHOWN, Q-CLARITY-EXAMPLE | Add a single fenced example of the report-back shape covering both the empty pass case and a gaps case with one fixable and one needs-human-judgment finding (rule_id, disposition, detail, evidence) — one block, not two. |
| cli-scaffold | plugins/cli-scaffold/skills/cli-scaffold-compiled/SKILL.md | Q-PROC-OUTPUT-SHOWN | At step 5, add one fenced JSON instance of the verifier's gap report showing the findings array with a disposition: fixable entry and a disposition: needs-human-judgment entry, so the step's branching logic has a concrete shape to key off. |
| cli-scaffold | plugins/cli-scaffold/skills/cli-scaffold-compiled/SKILL.md | Q-CLARITY-TERMS | Across cli-scaffold-compiled, cli-scaffold-interpreted and cli-scaffold-shell SKILL.md step 5 in one pass, replace only the colloquial item-level uses of 'gap(s)' ('still has fixable gaps', 'surface the remaining gaps') with 'finding(s)', matching verify_scaffold.py's actual findings key; leave every occurrence of the literal verdict gaps (VERDICT_GAPS) unchanged. |
| passung | plugins/passung/agents/pattern-extractor.md | Q-PROC-OUTPUT-SHOWN | Rewrite the Output format section to name both dispatch contracts: show one fenced JSON instance matching canonize.js's PATTERNS_SCHEMA (the Method A/workflow path, where the calling session renders the markdown card), plus a one-line note that under Method B the agent outputs the markdown card in the format /passung-canonize defines — keep that as a pointer, do not duplicate the card template. |
| passung | plugins/passung/commands/passung-brief.md, plugins/passung/commands/passung-preflight.md, plugins/passung/commands/passung-status.md, plugins/passung/agents/passung-critic.md, plugins/passung/agents/equivalence-verifier.md, plugins/passung/agents/pattern-analyst.md | Q-PROC-OUTPUT-SHOWN, Q-CLARITY-EXAMPLE | Add one short fenced worked instance of each file's own output where only prose describes it — brief.md a filled Canon Summary row plus one phase entry, preflight.md a few status-table rows and the verdict line, status.md the inventory table plus the three verdict lines, passung-critic.md one complete finding plus the closing 'If I could only change one thing' sentence, equivalence-verifier.md a PASS and a PASS-WITH-GAPS verdict, pattern-analyst.md one variant-cluster entry with a Confidence & Gaps footer covering both the filled and the nothing-to-flag case (a single example there, not two). |
| passung | plugins/passung/workflows/align.js | Q-PROC-OUTPUT-SHOWN | Add a JS comment below the RESULT_SCHEMA literal showing two worked result objects — a passing unit (testsRan:true, aligned:true, filesChanged/testCommand populated) and the no-test-run branch (testsRan:false, aligned:false, testFailures explaining why no command could run). |
| passung | plugins/passung/workflows/align.js | Q-CLARITY-TERMS | Standardize on 'unit' across the whole file — the top-of-file description/docstring and the promptFor text both say 'module' while the args contract, schema and result lists (units/remainingUnits/failedUnits/blockedUnits) say 'unit'. |
| passung | plugins/passung/workflows/canonize.js | Q-PROC-OUTPUT-SHOWN | Show one worked instance of PATTERNS_SCHEMA (a derived-majority card with basisFrequency/basisMaturity/basisRecency and a populated divergentSites[]) as an examples entry in the JSON Schema so it reaches the pattern-extractor, with a one-line note that openQuestion is required only when provenance is needs-human-decision. |
| passung | plugins/passung/workflows/canonize.js | Q-MEANING-CONTRACT | Extend meta.whenToUse to state that the workflow also returns a top-level divergentSites catalog from the Divergent-sites phase, which the calling session hands to /passung-align, alongside the Pattern Cards it already documents. |
| passung | plugins/passung/workflows/scan.js | Q-PROC-OUTPUT-SHOWN | Insert two worked dimension-card examples (one inScope:true with variants, one inScope:false with outOfScopeReason) into the finder agent's prompt template string built at lines 113-118, before ${UNTRUSTED} is interpolated — not as a source comment beside DIMENSIONS_SCHEMA, which the agent never sees. |
| passung | plugins/passung/workflows/verify.js | Q-STEPS-NUMBERED | Break the Check-phase prompt's four chained actions (read the diff, run tests, check coverage gaps, check doc drift) into a plain numbered checklist without FIRST/THEN gating language, since they are independent checks feeding one verdict rather than a dependency chain like scan.js's. |
| zirkel | plugins/zirkel/skills/zirkel-investigate-dynamically/SKILL.md | Q-STEPS-TERMINATION, Q-CLARITY-CONCISE | Add a stall predicate with escalation under Discipline — if a step's Reasoning names an unknown no available Action can close, stop, report the blocking unknown and hand off to zirkel-ground-evidence or zirkel-verify-assumptions — without introducing any iteration cap, and delete the Discipline bullet that restates the earlier Action rule verbatim. |
| zirkel | plugins/zirkel/skills/zirkel-negotiate-tradeoffs/SKILL.md | Q-PROC-OUTPUT-SHOWN | Add a small fenced markdown-table example of the trade-off matrix (branch | gives up | hybrid preserves) alongside the existing score-guard JSON, since the numeric JSON does not represent the qualitative matrix at all. |
| zirkel | plugins/zirkel/skills/zirkel-reason-verify/SKILL.md | Q-STEPS-GATE | After 'The guard requires exactly 3 attempts covering all three strategies.', state the failure path: a non-zero exit names the missing or duplicated strategy on stderr, re-dispatch only that attempt once and re-validate, and on a second rejection stop and report the guard's error instead of proceeding to a majority vote. |
| zirkel | plugins/zirkel/skills/zirkel-summarize-trace/SKILL.md | Q-PROC-OUTPUT-SHOWN | Fill at least one section body of the report example with real content instead of '…', and show a fenced instance of the required completeness score table (or define its columns and scoring criteria), so both non-obvious artifacts are demonstrated rather than named. |
| zirkel | plugins/zirkel/skills/zirkel-verify-assumptions/SKILL.md | Q-PROC-OUTPUT-SHOWN | Add one compact fenced JSON example of the still_unresolved outcome shape after the existing confidence_raised example, since validate_verify_run requires no confidence or citations fields for that kind and it is the branch with the special refusal template. |
| zirkel | plugins/zirkel/workflows/solve.js | Q-STEPS-TERMINATION | Surface the non-converged Revise case in the return value (e.g. a revise_converged: false flag or distinct status) instead of only log()-ing at cycle === MAX_REVISION_CYCLES and returning an unconditional status COMPLETE. |
| zirkel | plugins/zirkel/workflows/solve.js | Q-MEANING-CONTRACT | Add required: ['criterion'] with criterion: {type: 'string'} to CLARIFY_SCHEMA's success_criteria item schema, so the Revise phase's c.criterion ?? c fallback cannot silently stuff a whole object where a string is expected. |
| zirkel | plugins/zirkel/workflows/solve.js | Q-CLARITY-AMBIG | Replace 'the warning marker' in the Clarify prompt (line 108) with the literal symbol or prefix the agent should emit, since no marker is defined anywhere in the file. |
| zirkel | plugins/zirkel/workflows/solve.js | Q-CLARITY-EXAMPLE | Add a fenced example of a valid two-stage stages array to the Decompose prompt — one dependsOn: [] entry stage plus a dependent second stage — so the acyclic/no-dangling-reference contract computeWaves() enforces is pattern-matchable, not prose-only. |
| zeugnis | plugins/zeugnis/agents/zeugnis-remediator.md | Q-PROC-OUTPUT-SHOWN | Replace the inline backticked JSON with ellipsis placeholders by a fenced json block showing both branches with concrete values — one 'applied' instance (real findingId/file/summary) and one 'blocked' instance (real reason) — matching the pattern in assertion-auditor.md and contract-auditor.md. |
| zeugnis | plugins/zeugnis/agents/contract-auditor.md, plugins/zeugnis/agents/assertion-auditor.md | Q-PROC-OUTPUT-SHOWN | Check zeugnis-cycle's writer script for the shape actually expected when nothing is found, then add one line after each file's fenced example stating it (e.g. 'If no mismatches are found, return an empty list: []'); both agents have the identical missing-empty-branch gap and must get the same shape. |
| zeugnis | plugins/zeugnis/skills/zeugnis-assertion-audit/SKILL.md, plugins/zeugnis/skills/zeugnis-contract-drift/SKILL.md | Q-STEPS-INPUTS | In both skills, qualify step 3's symbol-index hand-off as conditional on the optional step-2 build ('the symbol index (if built)', matching the existing '(if any)' qualifier) and state what the agent is told when no index exists. |
| zeugnis | plugins/zeugnis/skills/zeugnis-cycle/SKILL.md | Q-STEPS-PREDICATE | Define 'fresh' in step 4 as the check scripts/status_dashboard.py already implements (summary sidecar mtime not older than git -C <repo_root> log -1 --format=%ct, i.e. staleRelativeToLatestCommit) rather than a commit/fingerprint field, which no domain *_summary.json stores. |
| zeugnis | plugins/zeugnis/skills/zeugnis-status/SKILL.md | Q-PROC-OUTPUT-SHOWN | Add one short fenced JSON example after step 2 matching status_dashboard.py's real stdout (domains map of {skill, hasRun, lastRunEpoch, staleRelativeToLatestCommit, findingCount}, ledgerPresent, cycleConverged, suggestion {action, reason}), showing one populated branch and the action: null empty branch. |
| cupertino | plugins/cupertino/agents/handbook-dimension-analyst.md | Q-MEANING-ASSUMED | Add the zero-marker case alongside the existing multiple-DIMENSION handling: refuse and report the dispatch as malformed when no DIMENSION: line is present, rather than inferring one. |
| cupertino | plugins/cupertino/skills/cupertino-council/SKILL.md, plugins/cupertino/skills/cupertino-focus/SKILL.md, plugins/cupertino/skills/cupertino-reveal/SKILL.md, plugins/cupertino/skills/cupertino-prototype/SKILL.md, plugins/cupertino/skills/cupertino-review/SKILL.md, plugins/cupertino/skills/cupertino-handbook-fix/SKILL.md | Q-PROC-OUTPUT-SHOWN | Add one compact fenced worked instance of each file's named output — council's filled Council Brief table plus a Design Identity sentence; focus's cut-list only (item, disposition, stated cost — never a filled grid with invented axes); reveal's complete seven-part write-up; prototype's five-field arrow chain including both discard and promote fates; review's stage-ordered skeleton with a side-by-side stage-3 attribution line and an explicit-skip line; handbook-fix's accept/fail table plus the zero-mechanical-findings branch. |
| cupertino | plugins/cupertino/skills/cupertino-elevate/SKILL.md | Q-CANN-NEGATIVE | Extend the excluded-case sentence in the description to name cupertino-backwards alone as the destination for a brand-new capability — not a backwards-or-council choice, since cupertino-council is itself blocked by the same backwards-done hook. |
| cupertino | plugins/cupertino/skills/cupertino-handbook-draft/SKILL.md | Q-OTHER-STATEFUL | Reword the '## Exceptions & waivers' promise only (add no waiver-writing step anywhere): say the section is empty to start and populated only by manual, out-of-band edits, because no cupertino skill writes to it. |
| cupertino | plugins/cupertino/skills/cupertino-handbook-draft/SKILL.md, plugins/cupertino/skills/cupertino-handbook-check/SKILL.md, plugins/cupertino/agents/handbook-drift-auditor.md | Q-PROC-OUTPUT-SHOWN | Show each JSON artifact's shape inline, minimum fields only: handbook-draft's .cupertino/<domain>-handbook_summary.json with one analyzed dimension (with evidence) and one scaffolded dimension (with note); handbook-check's sidecar with one populated finding (rule, file, line, severity, mechanical) and the {"findings": []} case; and a single {"findings": []} block under drift-auditor's existing Find-mode schema (add it once, not twice). |
| cupertino | plugins/cupertino/skills/cupertino-longevity/SKILL.md | Q-PROC-FREEDOM | Add a one-line 1/5 anchor for each of the six scored dimensions, phrased so 5 always means most evolution-ready (explicitly handling cost-named dimensions like 'Dependency exit costs'), since the sum feeds a hard mechanical threshold. |
| cupertino | plugins/cupertino/skills/cupertino-reveal/SKILL.md | Q-STEPS-TERMINATION | Bound the validator-fail retry: after two failed validations, stop and report the blocker to the user rather than an open-ended 'cut to one idea and present again'. |
| cupertino | plugins/cupertino/skills/cupertino-reveal/SKILL.md | Q-MEANING-CONTRACT | State where the built reveal code lands — written into the shipped artifact's actual source tree, or the named alternative when that is not possible — instead of only requiring a fenced code block. |
| cupertino | plugins/cupertino/skills/cupertino-review/SKILL.md | Q-STEPS-PREDICATE | Reword step 7 (unbox) to mirror steps 5 and 6 exactly: name the observable predicate for 'if applicable', require reporting the stage as explicitly skipped with a reason rather than omitting it, and persist {"skipped": true, "reason": "<why>"} to unbox-output. |
| cupertino | plugins/cupertino/workflows/handbook-fix.js | Q-STEPS-INPUTS | Keep verifying every item in c.items unconditionally (verification must stay blind to the remediator), but attach each item's matching remediation.results status — applied/blocked/missing — to its verification result so reporting can separate 'attempted and still non-compliant' from 'never attempted'. |
| lehre | plugins/lehre/agents/spec-decomposer.md | Q-PROC-OUTPUT-SHOWN | Add a compact (~8-10 line) second fenced instance after the existing example showing the one-unit answer the rules explicitly sanction — one unit, a 'why separate: n/a — this is the whole concern' line and a short 'not decomposed further' note — without duplicating the seams/candidate-rules scaffolding. |
| lehre | plugins/lehre/agents/spec-fidelity-auditor.md | Q-PROC-OUTPUT-SHOWN | Add a second short fenced example beside the existing NOT FAITHFUL one showing the common case: all intent clauses PASS and the verdict line reading FAITHFUL — safe to close. |
| lehre | plugins/lehre/agents/spec-fidelity-auditor.md | Q-CANN-NEGATIVE | Name the owner of the excluded territory in the negative-trigger line: style/naming/structure findings are rules — lehre-gauge dispatches violation-auditor for those (not rule-critic, which reviews candidate rulesets pre-write). |
| lehre | plugins/lehre/agents/violation-auditor.md | Q-CLARITY-TERMS | Replace the two 'finding' occurrences in the 'Every finding carries file:line…' bullet with 'violation', matching the opening sentence and frontmatter; the CERTAIN/ARGUABLE split is unaffected since the hedge lives in the ARGUABLE heading, not the noun. |
| lehre | plugins/lehre/skills/lehre-brief/SKILL.md | Q-CLARITY-TERMS | Standardize on 'violation' at all three drifting sites — line 14 ('remembered findings'), line 29 ('per finding'), line 32 ('Label each item') — matching lehre-gauge's vocabulary and line 3's existing 'violation set', so no residual fourth term is left beside the fixed ones. |
| lehre | plugins/lehre/skills/lehre-codify/SKILL.md | Q-STEPS-INPUTS | Insert a 7a sub-step that serializes the candidate rules — plus any pre-existing rules/units already in .lehre/ruleset.json, per the amend-don't-rewrite rule — into the file per references/ruleset-schema.md before the existing validate command, so validate no longer consumes an artifact no step produced. |
| lehre | plugins/lehre/skills/lehre-gauge/SKILL.md | Q-PROC-OUTPUT-SHOWN, Q-CLARITY-EXAMPLE | Extend the Output format section with the two undemonstrated shapes: a one-entry judgement section between advisory and UNEVALUATED in the populated example showing a rule's asks question plus the violation-auditor's answer, and a short second fenced example of the clean sweep (0 violations, 0 unevaluated, no rule sections). |
| lehre | plugins/lehre/skills/lehre-pin/SKILL.md | Q-PROC-WHATWHEN | Replace the description's quoted literal user-phrasing list with a short clause naming router-matchable concepts (CI pinning, durable enforcement, regression tests) so it matches paraphrases rather than exact utterances. |
| befund | plugins/befund/agents/convention-auditor.md | Q-MEANING-PROMISE | The description promises ADR-sourced conventions that the body never mentions; add ADRs to the source enumeration in core responsibility 1 and to the Must-refuse sentence rather than narrowing the description. |
| befund | plugins/befund/agents/idiom-remediator.md | Q-MEANING-PROMISE | The description's first sentence says 'a single, already-verified finding' and 'that one mechanical rewrite' while its own second sentence and the body describe a multi-finding cluster; rewrite the first sentence to say the agent applies a cluster of same-file, same-kind mechanical rewrites. |
| befund | plugins/befund/agents/stage-mapper.md | Q-CLARITY-TERMS | Settle on 'wire' as the single term without deleting the file's only inline definition: reword line 18 to 'one candidate wire -- a proposed connection between two stages -- to confirm by reading...', and change 'every edge as [from_stage, to_stage]' in Output format to 'every wire'. |
| befund | plugins/befund/agents/{complexity-surveyor,arch-health-auditor,transform-executor,idiom-remediator,stage-mapper}.md | Q-PROC-OUTPUT-SHOWN, Q-CLARITY-EXAMPLE | Bring these five agents' Output format sections up to the fenced-instance pattern the other auditors already use: a filled complexity-surveyor instance, a third layering-violation instance for arch-health-auditor with members ordered production-stage first, a concrete Merge/Split instance for transform-executor, one applied:true plus one applied:false instance for idiom-remediator, and a single combined stage-mapper example covering both the stages/wires/deadEnds find pass and the wire/verified/evidence verify pass (one example only, not two). |
| befund | plugins/befund/skills/befund-ci-topology/SKILL.md | Q-STEPS-PREDICATE | Reword the Workflow-tool-vs-agent branch to the phrasing this repo already uses unchanged in zirkel-solve and passung-scan -- 'When the Workflow tool is available in this session, prefer dispatching...' -- rather than inventing a single-file variant of the convention. |
| befund | plugins/befund/skills/befund-preflight/SKILL.md | Q-STEPS-INPUTS | Remove the invented 'git-presence sub-check' phrase: define in Step 1 what each git_remotes_ci status means (fail = no .git; partial = .git but no CI config; pass = both), then in Step 2 key the befund-ci-topology verdict off that status directly. Prose edit only, no new schema or CLI command. |
| befund | plugins/befund/skills/befund-ui-audit/SKILL.md | Q-MEANING-CONTRACT | In Step 4, describe UI_AUDIT.md's content (one section per finding category) and explicitly instruct writing it with the Write tool, instead of jumping from path resolution straight to the read-only constraint. |
| befund | plugins/befund/skills/{befund-arch-health,befund-docs-drift,befund-complexity-score,befund-lint-audit,befund-preflight,befund-transform-brief,befund-status}/SKILL.md | Q-PROC-OUTPUT-SHOWN, Q-CLARITY-EXAMPLE | Add one fenced worked instance per skill of the artifact it writes, showing the degraded branch as well as the populated one and without re-printing shapes the dispatched agent file already shows: arch-health's {"findings": []} wrapper, docs-drift's contradicted claim object, complexity-score's measured+unmeasured stage pair, lint-audit's 'Rules not checked this run' markdown section, preflight's checks/verdicts pair with one gap, transform-brief's phase object with populated work_items and empty open_questions, and status's findings_dashboard_data.json with present:{} versus a populated entry. |
| befund | plugins/befund/skills/{befund-arch-health,befund-lint-audit}/SKILL.md | Q-PROC-DISCLOSURE, Q-STEPS-PREDICATE | Keep the inline symbol-index steps but make the deferral explicit: in arch-health Step 2 change '(single-flight lock makes concurrent callers safe)' to add '-- see references/parallel-safe-research-protocol.md' (matching lint-audit's Step 3 verbatim), and cite that same reference at lint-audit's ~50-file scale sentence; do NOT convert the scale guidance into a numeric gate, which the reference and the indexer deliberately do not enforce. |
opus (2)
| plugin | file | rules | action |
|---|---|---|---|
| zirkel | plugins/zirkel/workflows/solve.js | Q-MEANING-CONTRACT, Q-STEPS-GATE | Design and implement a real gate for each stage's declared output_contract in the Execute wave loop — today the contract string is only echoed into the next prompt and the produced output is stored unverified — specifying the failure resolution (one bounded retry, or contract_violation: true surfaced in the returned results) rather than adding an unresolved check. |
| befund | plugins/befund/skills/befund-preflight/SKILL.md | Q-CANN-NEGATIVE | Rescope the trigger set: drop the unqualified literal 'run preflight', which four sibling preflights (andon, zeugnis, lehre, befund) have equal claim to, keep the befund-qualified triggers and the autopilot CHECK-phase entry point, and add a pointer naming the siblings. This is a scope-narrowing rewrite of a trigger list, not an appended clause; no pipeline order changes. |
human (5)
| plugin | file | rules | action |
|---|---|---|---|
| andon | plugins/andon/skills/andon-loop/SKILL.md | Q-MEANING-PROMISE | Decide whether andon-loop is meant to support scoped/filtered runs at all: the frontmatter argument-hint promises a stage-or-gap filter that no phase reads. Deleting the hint is a one-line edit; wiring a real filter through Phase 2's scan and Phase 5's cursor advance changes the skill's input contract and cursor state machine, so a human must pick the branch before it is routed to haiku (delete) or sonnet (wire). |
| passung | plugins/passung/commands/passung-brief.md | Q-CLARITY-AMBIG | Decide the intended pilot semantics — whether the pilot always lands inside Phase 1 as ordered by blast radius (smallest first), or targets whichever phase has the most divergent sites and is promoted ahead of that ordering — then state the chosen rule unambiguously; a model guessing between the two would encode the wrong execution order into every brief. |
| zirkel | plugins/zirkel/skills/zirkel-map-relationships/SKILL.md | Q-MEANING-PROMISE | Decide the pipeline question: either rewrite the description to drop the 'zirkel-solve stage' clause and describe standalone multi-hop use only, or open a separate change adding map-relationships as a fifth stage mode across zirkel-solve's Execute section, the stage-dispatch guard schema and solve.js — a description edit alone cannot make the promised handoff real. |
| befund | plugins/befund/skills/befund-extract-rules/SKILL.md | Q-MEANING-CONTRACT | Step 4 resolves output paths for DATA_OBJECTS.md and business_rules.json, but validators.py has no entry for either and business-rules-miner never emits their shape; a maintainer must decide whether these are vestigial resolve-output-path calls to remove or an intended artifact whose schema must be documented -- do not let an automated fix guess. |
| andon | plugins/andon/references/parallel-safe-research-protocol.md | M-DUP-CONTENT | Decide and record whether the byte-identical copy of this reference across andon, zirkel, zeugnis, cupertino and befund is intentional (each plugin ships independently versioned and self-contained) or drift waiting to happen; if intentional, note it so the lint result stops reading as a defect. |
7. Cross-plugin synthesis
Cross-plugin collisions
The router proxy scored 0.971 accuracy on 70 known-answer prompts (68/70 in top-3), and the run's own note reads router proxy accurate; collisions are measured. These are measured collisions, not hints — each row below is a pair the proxy actually surfaced together, judged against the two components' source files and the handoff graph.
| Components | Verdict | Winner | Rationale |
|---|---|---|---|
cupertino-handbook-check / befund-lint-audit | add-negative-trigger | befund-lint-audit | Disjoint loops; only discriminator is the artifact (.cupertino/<domain>-handbook.md vs .claude/house-rules.md), and cupertino literally claims the generic trigger "check this against our standards". |
cupertino-handbook-draft / lehre-codify | add-negative-trigger | — (prompts split) | Descriptive per-dimension prose handbook vs prescriptive authority-cited .lehre/ruleset.json; neither description says which side of descriptive-vs-prescriptive it sits on. |
zeugnis-contract-drift / befund-docs-drift | add-negative-trigger | befund-docs-drift | The word "docstrings" reads as "docs" to a router, and zeugnis's prose-exclusion rule lives only in the body the router never sees. |
lehre-codify / befund-extract-rules | add-negative-trigger | — (underspecified) | How code is written vs what the code decides; both descriptions are purely positive, so the generic word "rules" is all a router has. |
passung-map / passung-scan | add-negative-trigger | passung-scan | Consecutive pipeline stages: map renders an inventory scan must already have produced, but its description states no precondition. |
zirkel-map-relationships / befund-stage-map | add-negative-trigger | befund-stage-map | A domain-agnostic traversal technique whose unqualified "dependency path" phrasing catches repo import-graph prompts stage-map owns verbatim. |
befund-arch-health / befund-stage-map | intended-handoff | befund-stage-map | arch-health's description states it reads stage_graph.json; Step 0 hard-gates on it and degrades to Ready-with-gaps. Firing both in order is correct. |
andon-verify / befund-transform-execute | intended-handoff | befund-transform-execute | transform-execute's verify-dispatch-handoff rule forbids self-verifying and dispatches andon-verify itself. |
andon-loop / andon-verify | intended-handoff | andon-loop | andon-loop is the sole ledger writer; andon-verify's own body says "never write to the ledger — andon-loop persists it". |
andon-loop / befund-transform-execute | add-negative-trigger | andon-loop | Repo-wide iterative ledger pass vs exactly one authorized MODERNIZATION_BRIEF.md phase; neither description names the other's artifact. |
cupertino-handbook-fix / befund-idiom-fix | add-negative-trigger | cupertino-handbook-fix | Both self-describe as "the mechanical-subset applier"; disjoint summary JSONs and mode gates, but neither says which artifact it does not consume. |
andon-verify / zeugnis-assertion-audit | add-negative-trigger | andon-verify | zeugnis's trailing enforcement clause "Verification is mandatory and cannot be skipped by any setting" is the only landing spot for a generic verify-this-adversarially prompt. |
cupertino-council / befund-ui-audit | add-negative-trigger | befund-ui-audit | Generative pre-code vs static pass over existing UI; "before we ship" places the prompt after code exists, and neither description excludes the other. |
zeugnis-status / befund-status | leave | — | Disjoint artifact sets, both descriptions already lead with their plugin name; the prompt names no pipeline. |
andon-status / befund-status | leave | — | Same; cross-referencing would grow N² across 4+ status siblings and still not disambiguate a bare "status" prompt. |
andon-status / zeugnis-status | leave | — | Same; both are read-only and required to say "never run" rather than fabricate, so a misroute costs a sentence. |
zeugnis-preflight / befund-preflight | leave | — | Both correctly plugin-scoped on disk; "the pipeline" is a definite reference with no antecedent. |
andon-preflight / befund-preflight | rescope | — | Asymmetric defect: andon scopes every trigger by name while befund claims the bare literal "run preflight", which four preflights have equal claim to. |
andon-preflight / zeugnis-preflight | leave | — | Family-wide ambiguity, not pairwise; a mutual clause fixes 1 of ~20 pairs. |
cli-scaffold-compiled / scaffold-cli | intended-handoff | scaffold-cli | Declared bidirectional dispatch edge; scaffold-cli owns language resolution in code, which direct paradigm dispatch skips. |
andon-challenger / passung-critic | leave | — | Same genus (adversarial read-only review), different objects (wire contract vs Pattern Card); the probe prompt names no object. |
handbook-remediator / idiom-remediator | add-negative-trigger | — (neither should fire) | Shared contract shape, different finding streams; neither says it is dispatch-only. |
conformance-remediator / handbook-remediator | add-negative-trigger | — (neither should fire) | Verbatim-identical abstract job description ("apply one already-verified mechanical fix at the cited location and nothing else") with no domain named. |
conformance-remediator / idiom-remediator | add-negative-trigger | — (neither should fire) | Same; different upstream contracts, different verifiers (lehre-validate vs andon-verify), no discriminator in either description. |
Thirteen pairs resolve by appending negative triggers, four are intended handoffs that need no change, six are prompt-side ambiguity, one is a rescope. No pair produced a merge verdict — no two components in the workshop do the same job.
Nit (rubric F6): passung ships its user-facing surface as commands (plugins/passung/commands/consistency-{scan,map,canonize,brief,align,verify,preflight,status}.md) rather than skills, unlike the other eight plugins; the one intra-plugin collision above is therefore between two command descriptions.
Structural duplication
Remediator agents — three implementations of one contract. plugins/cupertino/agents/handbook-remediator.md, plugins/befund/agents/idiom-remediator.md and plugins/lehre/agents/conformance-remediator.md collide pairwise in all three combinations, and every judgement found the same cause: an identical abstract job description with no domain and no dispatch-only clause. Recommendation: keep all three — the upstream finding shapes, mode gates and verifiers are genuinely disjoint — and apply the reciprocal negative triggers plus an explicit "only ever dispatched by <orchestrator>" clause already specified in the cupertino, lehre and befund backlogs.
Fix skills. cupertino-handbook-fix and befund-idiom-fix both self-describe as the mechanical-subset applier that refuses the judgment-requiring rest. Recommendation: reciprocal negative triggers naming the consuming artifact (.cupertino/handbook_check_<domain>_summary.json vs code_idiom_summary.json modernization rows), and correct docs/orchestration/README.md's "mechanical rewrites" phrasing for befund-idiom-fix, which the judgement identifies as the upstream source of the shared word.
Adversarial critics. plugins/andon/agents/andon-challenger.md and plugins/passung/agents/passung-critic.md share the genus but not the object. Recommendation: leave both unchanged — the judgement is explicit that a cross-reference would be noise, since a router that cannot pick on this prompt has nothing on which to act on a pointer either.
Status and preflight families. Four status skills (andon-status, zeugnis-status, befund-status, plus /passung-status) and four preflights (andon-preflight, zeugnis-preflight, lehre-preflight, befund-preflight) produced six leave verdicts and one rescope. Recommendation: do not cross-reference — the ambiguity is family-wide and the clause list would grow N² and rot on the next plugin, the same drift shape lint-release-wiring.py exists to catch. Do apply the one asymmetric fix: plugins/befund/skills/befund-preflight/SKILL.md must drop the unqualified literal trigger "run preflight" (opus-tier, the only trigger-set rewrite in the corpus), and resolve unqualified prompts at dispatch time by which pipeline's artifacts exist on disk.
The duplicated reference file. references/parallel-safe-research-protocol.md is byte-identical across andon, zirkel, zeugnis, cupertino and befund (lint's only duplicate cluster). The cupertino backlog names the canonical source — tools/symbol-indexer/parallel-safe-research-protocol.md, synced by rrt artifacts --regenerate — while the andon backlog files the same fact as an undecided nit. Recommendation: record the sync relationship once so the lint result stops reading as a defect, and edit only the canonical file (the zirkel, cupertino and befund backlogs each carry a pending edit to this text — all three must land upstream, not in a plugin copy).
Backlog by model tier
183 backlog entries across nine plugins.
| Tier | Entries | Kinds of fix | Plugins dominating |
|---|---|---|---|
| haiku | 44 | Negative-trigger and precondition clause insertions into descriptions; term renames (hits→violations, commodity function→commodity feature, [convention-pattern]→[dimension-pattern]); frontmatter shape conversions (M-AGENT-TOOLS-SHAPE, M-DESC-PERSON); pure tool-grant deletions; two solve.js one-line variable-source corrections; TOC and count-word fixes | zirkel 7, cupertino 7, lehre 6, befund 6, passung 5, zeugnis 5 |
| sonnet | 132 | The bulk: missing gate failure paths, absent-input branches, unbounded retry loops, undeclared inputs, schema/contract reconciliation, and every Q-PROC-OUTPUT-SHOWN fenced worked instance | zirkel 24, cupertino 21, befund 21, lehre 18, passung 17, andon 13, cli-scaffold 9, zeugnis 9 |
| opus | 2 | Scope-narrowing rewrites and unresolved design: befund-preflight's trigger-set rescope, and a real output_contract gate for plugins/zirkel/workflows/solve.js's Execute wave loop | befund 1, zirkel 1 |
| human | 5 | Branch decisions a model must not guess: andon-loop's stage-or-gap filter (delete vs wire); passung-brief.md's pilot ordering semantics; zirkel-map-relationships's pipeline claim (drop the clause vs add a fifth stage mode); befund-extract-rules's vestigial-vs-intended DATA_OBJECTS.md/business_rules.json artifacts; whether the five-way reference duplication is intentional | andon 2 |
Severity skews the same way as tier: exactly one blocker in the corpus (plugins/cupertino/workflows/handbook-fix.js), and every plugin's verdict is needs-work with the summaries agreeing that no component needs redesign or merging.
Ten actions
- Gate
plugins/cupertino/workflows/handbook-fix.js's incomingfindingsbefore clustering — the corpus's only blocker, where a missing arg is indistinguishable from zero findings and unvalidated.ruleyieldsRULE: undefinedclusters. - Wire the dead injection-reporting contract in
plugins/passung/workflows/{scan,verify,canonize}.js, replacingverify.js:145'sfor (const s of [])— the exact code-that-silently-does-nothing shape this repo's own CLAUDE.md catalogues. - Close the lehre human-approval hole end to end: define
approved_phasesinplugins/lehre/skills/lehre-brief/SKILL.md, read it inlehre-conform, and add the missing brief-approval refusal toplugins/lehre/agents/conformance-remediator.md. - Read
priorClarify/priorExplore/requestedBranches/maxBranchCountfromparsedArgsand movephasesRun.push('Revise')inside the criteria gate inplugins/zirkel/workflows/solve.js— two haiku edits that stop prior-run reuse vanishing and stop the contract claiming a phase that never ran. - Make cupertino's gating state flags clear on every path in
plugins/cupertino/skills/{cupertino-handbook-check,cupertino-handbook-fix,cupertino-review}/SKILL.md, including a failure gate before review's flag cleanup discards the only copy of stage content. - Apply the thirteen negative-trigger insertions in one pass across the descriptions named in the collisions table (eleven haiku, two sonnet) — the cheapest fix in the corpus and the one that resolves every measured non-handoff collision.
- Correct the two false contracts in
plugins/andon/references/okf-ledger-schema.md(theANDON_BOARD.mdoutput_dir claim, and the undocumented source of thelane:fast|slowtag). - Fix tool grants that contradict stated roles: drop
Bashfrom the five read-only befund auditors, dropGlobfromplugins/cupertino/agents/handbook-drift-auditor.md, closeplugins/befund/agents/transform-executor.md's delete/move gap via afiles_to_deleteoutput field, and rewriteplugins/lehre/agents/pattern-investigator.md's serena paragraph to match its actualRead, Glob, Grep, Bashgrant. - Make
plugins/andon/skills/andon-preflight/SKILL.mdhonest about the two files Step 3 writes, and gate the JSON write the way the structurally identical markdown write already is. - Route the five human-tier decisions to a maintainer before any automated pass runs —
andon-loop's filter argument,passung-brief.md's pilot semantics,zirkel-map-relationships's pipeline claim,befund-extract-rules's unclaimed artifacts, and the reference-file duplication — since each has two defensible branches and a guessed one encodes the wrong contract downstream.
Instrument
Calibration ran twice and improved on both axes that matter — recall 0.76 → 0.80 with the worst per-angle recall rising from 0.40 to 0.60, at the cost of one false positive appearing in round 2 (zero in round 1) — and the sealed-set recall of 0.786 sits between the two calibration rounds, so the instrument behaved on unseen material roughly as it did on the calibration set. The pipeline raised 464 raw findings, of which 216 survived verification and 28 were extras beyond the seeded set, meaning slightly over half of what the finders proposed did not survive a second look; the backlogs above contain only the verified 216, and the 0.786 sealed recall means roughly one finding in five of the kind this instrument looks for was missed. Read this report as a high-precision, incomplete inventory: what it lists is checked, and a plugin's absence from a section is not evidence of its health — with a per-angle floor of 0.60, any single angle's silence is the weakest signal in the report.
Appendix — coverage
| batch | files | raw | verified | meaning | contract | clarity | step-logic | procedure | cannibalization | other |
|---|---|---|---|---|---|---|---|---|---|---|
| andon:skills | 16 | 18 | 12 | 3 | 2 | 1 | 4 | 1 | 1 | 0 |
| andon:agents | 4 | 20 | 9 | 0 | 0 | 4 | 1 | 4 | 0 | 0 |
| cli-scaffold:skills | 18 | 26 | 10 | 4 | 0 | 4 | 1 | 1 | 0 | 0 |
| cli-scaffold:agents | 1 | 12 | 9 | 2 | 0 | 2 | 4 | 1 | 0 | 0 |
| cli-scaffold:commands | 1 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| passung:agents | 5 | 19 | 3 | 1 | 0 | 1 | 1 | 0 | 0 | 0 |
| passung:commands | 8 | 26 | 13 | 3 | 0 | 4 | 3 | 3 | 0 | 0 |
| passung:workflows | 4 | 22 | 12 | 1 | 6 | 1 | 1 | 3 | 0 | 0 |
| zirkel:skills:a | 8 | 19 | 8 | 2 | 1 | 3 | 2 | 0 | 0 | 0 |
| zirkel:skills:b | 7 | 21 | 11 | 4 | 0 | 0 | 2 | 5 | 0 | 0 |
| zirkel:agents | 3 | 10 | 5 | 2 | 0 | 0 | 0 | 3 | 0 | 0 |
| zirkel:workflows | 4 | 16 | 10 | 3 | 0 | 4 | 3 | 0 | 0 | 0 |
| zeugnis:skills | 9 | 19 | 8 | 1 | 0 | 1 | 5 | 1 | 0 | 0 |
| zeugnis:agents | 5 | 13 | 5 | 2 | 0 | 1 | 0 | 2 | 0 | 0 |
| cupertino:skills:a | 9 | 22 | 14 | 2 | 0 | 2 | 5 | 4 | 0 | 1 |
| cupertino:skills:b | 7 | 24 | 11 | 0 | 1 | 1 | 4 | 5 | 0 | 0 |
| cupertino:agents | 4 | 13 | 3 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
| cupertino:workflows | 3 | 11 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| lehre:skills:a | 6 | 18 | 11 | 1 | 1 | 4 | 4 | 1 | 0 | 0 |
| lehre:skills:b | 4 | 20 | 4 | 0 | 0 | 1 | 2 | 1 | 0 | 0 |
| lehre:agents | 8 | 16 | 6 | 0 | 0 | 1 | 0 | 4 | 1 | 0 |
| befund:skills:a | 12 | 24 | 11 | 1 | 1 | 1 | 2 | 4 | 1 | 1 |
| befund:skills:b | 8 | 26 | 21 | 3 | 4 | 2 | 7 | 4 | 1 | 0 |
| befund:agents:a | 6 | 18 | 8 | 2 | 0 | 3 | 0 | 3 | 0 | 0 |
| befund:agents:b | 5 | 22 | 11 | 2 | 1 | 2 | 0 | 6 | 0 | 0 |
Completeness critic re-ran these cells: cli-scaffold:skills × cannibalization, cupertino:skills:a × cannibalization, zirkel:skills:b × cannibalization, cupertino:workflows × contract, zirkel:workflows × contract, cupertino:agents × procedure, passung:agents × procedure, lehre:agents × meaning.