diff --git a/.gitignore b/.gitignore index d027e142a..9461a821a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Dependencies node_modules/ __pycache__/ +.pytest_cache/ .venv/ venv/ diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index cc24fcfbe..439a7b4d7 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -14,6 +14,7 @@ // Build, dependency, and cache trees — not authored markdown. "**/node_modules/**", "**/.venv/**", + "**/.pytest_cache/**", "**/bin/**", "**/obj/**" ], diff --git a/README.md b/README.md index 9086afdb3..c127921c0 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ user opts in with `/plugin enable`; an existing install is never flipped by cata ## Finding your way - Not sure which skill to invoke? Start at the [skill cheat sheet](docs/SKILL-CHEAT-SHEET.md). A - scan-and-go map from what you're doing to the skill to use. + scan-and-go map from what you are doing to the skill that does it. - [Plugin catalog](docs/CATALOG.md). Every plugin by category, generated from the manifests and kept in sync by CI. New plugins clear the per-plugin migration gate in [`docs/MIGRATION-PLAYBOOK.md`](docs/MIGRATION-PLAYBOOK.md). diff --git a/docs/MIGRATION-PLAYBOOK.md b/docs/MIGRATION-PLAYBOOK.md index 5bdbd7f8d..f659ef137 100644 --- a/docs/MIGRATION-PLAYBOOK.md +++ b/docs/MIGRATION-PLAYBOOK.md @@ -1470,269 +1470,31 @@ in-repo server**, picking one: - **Defer** — hold the cutover until the server has a cross-surface distribution path (e.g. repoint the other surfaces at the plugin's on-disk bundle, or a shared build), then re-scope. -## Shared code across plugins — decision record (2026-07-04) - -Decided when four plugins carried byte-identical `hooks/hook-utils.sh` copies — the Rule-of-Three -threshold below, exceeded. The mechanism is **single source of truth at authoring time, plain copies -at runtime**: - -- `lib/hook-utils.sh` is the only copy to edit. `scripts/sync-hook-utils.sh` propagates it into every - carrying plugin; a plugin opts in by committing an initial `hooks/hook-utils.sh` copy. -- CI (`hook-utils-sync` lane) fails a PR when any plugin copy drifts from the source, and when the lib - changed but a carrying plugin's manifest version did not — the plugin `version` is the update cache - key, so an unbumped plugin never delivers the change to consumers. -- Runtime is untouched: each installed plugin stays self-contained under cache isolation, with no - cross-plugin coupling and no change to the one-plugin install UX. - -Alternatives weighed (docs verified 2026-07-03): - -- **Dependency plugin carrying the lib — rejected as not viable.** A hook sees only its own - `${CLAUDE_PLUGIN_ROOT}` / `${CLAUDE_PLUGIN_DATA}`; no variable or documented mechanism exposes a - *dependency's* install path, and cache directories are per-version (with a commit-SHA suffix for - tag-resolved dependencies), so computing the path is unsupported by design - (; - ). **Recheck trigger:** Claude Code - ships a documented dependency-path variable — that would also allow sharing the lib beyond this - marketplace. -- **Marketplace-internal symlinks — deferred.** Documented mechanism: a symlink from a plugin to a - file elsewhere in the same marketplace is dereferenced at install, copying the target's content into - the cache — native SSOT with no sync script - (). - Deferred because such symlinks are *skipped* for `--plugin-dir` / local-path - installs (breaking the local development loop above) and are fragile to author and clone on Windows, - the primary environment on both the authoring and consuming side. **Recheck trigger:** the dev - loop stops depending on `--plugin-dir`, the documented `--plugin-dir` / local-path handling changes - so marketplace symlinks are no longer skipped (the upstream premise this deferral rests on), or the - Windows constraint lifts. -- **Copies with only a byte-identity CI gate — subsumed.** The chosen shape is that gate plus a - canonical source and one sync script, removing the edit-×N-by-hand step at negligible cost. - -The lib's unit tests live beside the source as one consolidated suite (`lib/hook-utils.test.sh`, -run by the same CI lane) rather than as per-plugin copies — byte-identity of the copies means -testing the source covers them. Plugins keep only their own black-box hook contract tests. - -### Vendored Node packages — the `file:` + `--install-links` convention - -Shared **Node** source (not a shell lib) is vendored as a plain package tree and consumed through -npm's `file:` link, because a cache-isolated plugin cannot reference a package outside its own -directory: - -- The vendored package is a self-contained runtime-source copy (its own test suite, build config, - and `node_modules` omitted). A consumer `package.json` depends on it with `"@scope/name": - "file:"`. -- The skill's `setup-deps.mjs` installs it into `${CLAUDE_PLUGIN_DATA}` with - `npm install --omit=dev --install-links ` — `--install-links` packs the `file:` - package as a real install (copied source) rather than a symlink back into the plugin cache, so it - survives cache isolation. Install is idempotent: a stored fingerprint hashes `package.json` **and - the entire vendored tree** (the packages install from source, not by version, so a source change - with no manifest bump must still reinstall). -- Runtime resolves bare specifiers (`@scope/name/subpath`) from `${CLAUDE_PLUGIN_DATA}/node_modules` - via an ESM resolve-hook (`run.mjs` → `register-hook.mjs`/`resolve-hook.mjs`), never a hardcoded - path into the plugin cache. - -### Intra-plugin sharing — one committed copy, no sync script - -When the second consumer is **another skill in the same plugin** (not another plugin), the -cross-plugin machinery collapses: put the vendored source once at the plugin root (`vendor/`), and -point every consuming skill's `file:` link and `setup-deps.mjs` fingerprint at that single copy -(`file:../../../vendor/*` from `skills//extraction/`). No `sync-*.sh` propagation and no -byte-drift CI gate are needed — there is only one committed copy, so nothing can drift. The -invariant that **replaces** the byte-drift gate is delivery-by-version: editing the shared source -obligates a plugin `version` bump, since the version is the update cache key. (`knowledge`'s -`repo-analysis` + `video-digestion`, shared by its `video-digest` and `course-digest` skills, is the -reference instance.) Reach for the cross-plugin shape above only once a *second plugin* genuinely -needs the same source. - ## What to wait on / avoid for now - Don't pre-build cross-plugin `dependencies` graphs until two plugins genuinely share a need. - Don't abstract a shared library before a second consumer exists (Rule of Three); at the threshold, - the shared-code decision record above is the settled shape — extend it rather than re-deciding. + [ADR 0019](adr/0019-share-code-across-plugins-by-vendoring-with-a-sync-gate.md) is the settled + shape — extend it rather than re-deciding. - Don't rely on any mechanism not confirmed from current docs this session — if a customization need has no proven native path yet, record it here as a gap and keep the workaround in the consumer's repo until the native mechanism is verified. -## Deferred surfaces — decision record (2026-07-12) - -Three general-purpose surfaces in the harvest-source repo (`melodic-software/medley`) are held out of this -wave's plugin migration deliberately, each with an explicit -[recheck trigger](conventions/upstream-drift/README.md) — recorded here so the deferral -is a decision, not a silent omission. The medley side carries a thin pointer back to this record at each -surface (the workflow-engine authoring rule, the `onboard` skill, and the `gh-bot.sh` bot-identity -convention), so a contributor who touches a deferred surface finds the trigger without leaving that repo. - -- **Workflow engines** (`code-review.js`, `codebase-review.js`, `deep-research.js`, - `research-deep-fanout.js`, `skills-audit.js`, `skills-evals.js`, `skills-remediate.js`): deferred - 2026-07-12 as not a plugin component, so these may be removed entirely rather than migrated. - Re-verified 2026-07-27: the no-native-slot premise no longer holds — plugins now ship workflow - scripts via a `workflows/` directory - () or the `workflows` - manifest field (), and a - plugin workflow runs plugin-namespaced - () — but the deferral - stands on the usage question alone. **Recheck trigger:** the engines survive the next usage review - (still earning their keep) → migrate through the native plugin `workflows/` slot, verifying each - engine script fits the documented workflow-script shape, with a smoke test specced for that - dispatch path before packaging. -- **`onboard` skill:** repo-specific today — its phase gates encode this repo's exact runtime, linter, and - tooling pins. **Recheck trigger:** a second repo needs environment-prerequisite auditing → extract a - generic core through the extensibility-contract seams (the convention-resolution ladder infers or asks - for the per-repo pins), leaving repo specifics in tracked config rather than baked into the skill. -- **`tools/github-auth` (`gh-bot.sh`):** hardcodes the org's bot App / installation identity. **Recheck - trigger:** a second repo needs bot-actor GitHub operations → parameterize org / App / installation - through the seams (`userConfig` scalars, `sensitive` for the key) instead of standing up a second - hardcoded wrapper. - -## Unused official plugin components — decision record (2026-07-12) - -The three unused official plugin components raised as adoption candidates on this date, evaluated -against the enforcement hierarchy (default **REJECT** unless the value is concrete and not already -covered by an existing mechanism). This is that evaluation, not an index of every component the -marketplace does not use — the [component-stances table](PLUGIN-PHILOSOPHY.md#component-stances) is -that index, and it carries a stance for components never raised here. Facts verified fresh -2026-07-12 per `CLAUDE.md` "Fresh-docs mandate". Verdict for all three: **REJECT now**, each with an -explicit recheck trigger — no implementation issues emitted (zero accepted). - -- **Monitors** (`monitors/monitors.json` / `experimental.monitors`) — **REJECT.** Both candidates are - either already covered or not concrete: a PR/CI watch duplicates `/source-control:pull-request monitor` - and a consumer's channel-mode PR watch (no gap), and a claude-ops collector-health watch carries no - concrete recurring pain that outweighs adopting an `experimental.*` component whose manifest schema may - change between releases (and which is skipped on the hosts / telemetry-disabled configs where the - Monitor tool is unavailable). **Recheck trigger:** monitors leave the `experimental` key AND a concrete - recurring in-session watch need surfaces for a shipped plugin, scoped via the documented `when: - "on-skill-invoke:"` monitor field so it starts only on demand rather than at session - start. Upstream: - , - . -- **`bin/`** (executables added to the Bash tool `PATH`) — **REJECT** as a marketplace-wide adoption. - Plugin-owned scripts already ship via `${CLAUDE_PLUGIN_ROOT}/scripts/` invoked by full path (the - established pattern — see "Shared tools and scripts seam" above); `bin/` adds only bare-command-on-`PATH` - invocation, which risks name collisions with the consumer's own commands, so it earns its place only - where a script is meant to be run as a bare command by the consumer. The one live candidate — the - knowledge plugin's extraction tooling — is owned by its publish issue #1373; the `bin/`-vs-`scripts/` - call belongs there, not duplicated here. **Recheck trigger:** a shipped plugin has a script the consumer - invokes as a bare command (not an internal helper). Upstream: - . -- **`subagentStatusLine`** (plugin `settings.json`) — **REJECT.** Purely cosmetic: it re-formats the - subagent panel row with no functional capability, so it does not clear the default-REJECT bar; its - richest inputs (per-row model + context-window size for a context percentage) additionally require a - recent Claude Code minimum. Candidate home was claude-ops. **Recheck trigger:** a concrete operational - need for custom subagent-row data during orchestration, not a presentation preference. Upstream: - , - . - -## Knowledge-corpus consuming repo + integration flow — decision record (2026-07-13) - -The `knowledge` plugin's ingest artifacts (transcripts, keyframes, source media, syntheses) get a -single dedicated consuming home rather than living in any one product repo, so a session can analyze -the whole corpus and fit relevant findings into *any* target repo. Decided with the owner in an -interview session against medley EPIC #1273 / wave-2 map #1369 (issue #1393); recorded here because -the wave's codification requirement puts convention decisions in tracked docs, not issue comments. - -- **Repo:** `melodic-software/knowledge-corpus`, private, org-owned. Organization ownership was - chosen because source media is retained and storage/bandwidth usage belongs with the shared corpus, - not a personal account. Created pure-IaC via the `melodic-software/github-iac` governed registry - (no ad-hoc `gh`, no import/drift window); the repo comes into being at the Pulumi deploy. -- **Media retention + LFS:** retain source video, keyframes, and any input useful for re-scraping or a - fresh analysis — the corpus is the durable substrate for re-runnable synthesis, not just derived - text. LFS-backed: a `.gitattributes` tracking media globs (mp4/mov/webm/png/jpg/jpeg/gif/pdf/epub/ - mp3/wav) plus pushed LFS objects. Git LFS is **not** expressible on the pulumi-github v6.14.0 - `Repository` resource → it is content-side, landing via a follow-up content PR to the repo, not - governed in IaC. Basis: the provider schema at the pinned tag — - , - where `github:index/repository:Repository` declares 48 properties and 39 input properties, none - matching `lfs`, and the document contains no case-insensitive `lfs` match at all (fetched and - probed 2026-07-29; re-run the same fetch against the then-pinned tag when the trigger below fires). - **Recheck trigger:** a pulumi-github - release notes LFS support on `Repository`, or the pinned provider version moves past v6.14.0 → - re-derive the IaC-vs-content-side call. GitHub's quotas, metering, and prices change; - verify the current account allowance, budget, and overage behavior in the - [official Git LFS billing documentation](https://docs.github.com/en/billing/concepts/product-billing/git-lfs) - before changing retention or ownership policy. -- **Artifact landing:** no consuming-repo name is baked into the plugin (contract v2.1 seam 1 + the - convention-resolution ladder), so it serves any consumer unchanged. Which pipeline lands where — and - which honor `library_dir` vs write elsewhere — is fast-moving plugin-seam state; the `knowledge` - plugin's own skill docs are the SSOT, not recapped here. -- **Integration flow — first-class capability:** the value step is analyze-here → fit-into-any-target. - Shape decided = a knowledge-plugin **`apply`/`integrate` skill** (a repeatable, invocable capability - seam-consistent with contract v2.1), NOT a documented manual workflow (which would rely on operator - memory and codify nothing). Full spec — target-repo scan, relevance ranking, how integrations are - proposed/applied — is decomposed to a dedicated `design(knowledge-integration)` issue under #1369 - per the one-session sizing rule, rather than half-built inline. -- **Scope boundary:** the songwriting-corpus (Pat Pattison EPUBs) destination is owned by #1402, not - decided here. How existing artifacts consolidate into this repo is operational — see #1393. - -## `skill-quality` retrofit scope — decision record (2026-07-13) - -The `skill-quality` plugin shipped only the generic static contract checker (`check-skill.sh`, seventeen -model-free checks) plus the `evals.schema.json` validation asset. Its held-back scope is resolved here as -**terminal exclusions** — decided out of the plugin for good, each with a permanent home, **not** deferrals -with a recheck trigger. (Contrast the "Deferred surfaces" record above, where the medley surface is held -*pending* a trigger; these are held *out*.) - -- **A/B eval runner** (`tools/evals/run-skill-comparison.sh`): a headless `claude -p` skill-body A/B - comparison driver — spins throwaway worktrees, runs fixture trials per arm, scrubs transcripts. **Not a - plugin component; stays medley-owned in `tools/evals/`.** It is a *dynamic authoring experiment* harness, - a distinct concern from this plugin's *static QA gate* (one cohesive capability per plugin — see the - design charter), with a single consumer and ~29 KB of worktree / hub-safety / platform path-scrub - surface that would be marketplace upkeep for that one consumer. **No recheck trigger:** a genuine - second-consumer demand is a fresh publish issue, not standing debt. -- **Contract libs** (`tools/skill-contract/`: portability, encapsulation, script-contract, dispatcher): - enforce medley-**invented** regimes — the skill public-surface / encapsulation contract, BEHAVIOR.md - symmetry, unit-anatomy, the cleanliness-regime script contract, and a medley-specific identifier - deny-list. **Permanent home is medley** (a de-couple-from-source-repo gate they cannot pass — every scan - scope, exemption, and identifier is this repo's). The narrow genuinely-generic seams (machine-path / - escape-path scanning, an encapsulation deep-cite regex, a "new script ships `--help` + a sibling test" - assertion, a deny-list scan *mechanism* whose data is per-consumer) are net-new versus the shipped - checks but have **no second consumer**; extracting them now is speculative generality / a pre-Rule-of- - Three abstraction (`melodic-software/standards` `conventions/engineering/simpler-code.md`). The plugin - can grow a machine-path check the day a real consumer needs one — as its own issue. -- **Checker hardening** (block-scalar description unfolding, an unquoted-`Use when:` warning, a - `CHECK_SKILL_BASE_REF` post-commit audit ref for the git-backed checks, and a line-1 frontmatter-fence - requirement): the one worker-executable slice — **landed** with this record. - -## Convention-seam ratification & the shared-identity limitation — decision record (2026-07-23) - -Recorded from the #1187 audit (triggered when the operator did not recall ratifying the -`consumer-config-layering` → `config-cascade` seam). All 12 `docs/conventions/*` seams are -**PR-introduced** across the repo's whole history (established from git history), so none was silently -accreted. In-doc issue/PR citation is the intended ratification signal but is **inconsistent** across -the surfaces today — some seams cite their ratifying issue in the README/CHANGELOG (`config-cascade`'s -exception class → #649), others (`hook-precision`, `seam-phrasing`) carry no in-doc reference, so an -operator auditing from the durable convention surface alone cannot always find it. Converging every -seam on an in-doc citation is a follow-up, not asserted here as already-true. - -**The limitation, stated precisely — two provenance layers, only one collapses.** Distinguish: - -- **Git commit metadata** (author, committer, `Co-authored-by` trailers) **does** carry a distinct - identity — this very record's commit is authored by `Codex `; other agents commit - under their own identity (e.g. a `Co-authored-by: Claude …` trailer). So at the commit layer, agent - work is often *visible*. But it is **soft, not proof**: an agent can set its git author to anything, - so absence of an agent identity does not prove a human authored it. -- **GitHub gh-account actions** — PR author, PR review, merge, and the account a commit is *attributed - to* — **all collapse to `kyle-sexton`** (the account `gh` is scoped to), whether the human or an - agent-as-Kyle acted. At *this* layer no in-repo signal distinguishes human ratification from agent - accretion. - -So the gap is specifically at the **GitHub-account / review-and-merge layer**, which is exactly where -"ratification" is recorded — and it is a **repo-wide property**, not a defect of any one seam. - -**Decision — decline forgery-prone gates; they are theater.** A `CODEOWNERS` rule or a `human-ratified` -label requiring a `kyle-sexton` review does **not** distinguish anything at the account layer: an agent -satisfies the same gate under the same identity. Commit signing already runs (`required_signatures`) -but under the shared key, so it does not separate either, and commit-author metadata is spoofable as -above. Standing up such a gate would manufacture *false* assurance — worse than naming the limitation. -So none is added. - -**The only real distinguisher (flagged, not imposed).** Cryptographic separation requires an identity -agents do **not** hold — a distinct human-only GitHub account and/or a signing key kept off the agent -runners, with branch protection requiring that identity's review on `docs/conventions/**`. That is an -infrastructure change with real operator cost. **Recheck trigger:** the operator wants provable human -ratification, or a second human contributor joins (at which point identity separation exists naturally). - -**Interim posture.** Ratification stays **trust-based and visible**: a convention-seam change **should -cite** a ratifying issue/PR in-doc (the norm going forward — converging existing seams on it is the -follow-up above), and the operator's explicit engagement on that thread (as in the #163434 session) is -the ratification signal. The audit trail — issue, review comments, commit-author metadata where it -carries an agent identity, and this record — is the durable account, in place of an account-layer -assurance the shared GitHub identity cannot provide. +## Decision records + +Decisions that shaped this playbook live in [`adr/`](adr/), one file each, and are not restated +here. Read the record when you are about to reopen the decision it settled, not while following the +playbook: + +- [ADR 0019](adr/0019-share-code-across-plugins-by-vendoring-with-a-sync-gate.md), sharing code + across plugins (2026-07-04). +- [ADR 0020](adr/0020-defer-three-medley-surfaces-with-explicit-recheck-triggers.md), deferred + surfaces (2026-07-12). +- [ADR 0021](adr/0021-reject-the-three-unused-official-plugin-components.md), unused official plugin + components (2026-07-12). +- [ADR 0022](adr/0022-consume-the-knowledge-corpus-from-a-separate-repository.md), knowledge-corpus + consuming repo and integration flow (2026-07-13). +- [ADR 0023](adr/0023-scope-skill-quality-to-the-generic-static-checker.md), `skill-quality` + retrofit scope (2026-07-13). +- [ADR 0024](adr/0024-decline-forgery-prone-human-ratification-gates.md), convention-seam + ratification and the shared-identity limitation (2026-07-23). diff --git a/docs/PLUGIN-PHILOSOPHY.md b/docs/PLUGIN-PHILOSOPHY.md index b63032aec..e7dcbce19 100644 --- a/docs/PLUGIN-PHILOSOPHY.md +++ b/docs/PLUGIN-PHILOSOPHY.md @@ -212,7 +212,7 @@ re-deriving a row. | [Skills](https://code.claude.com/docs/en/skills) | Primary surface | The default unit of capability. Newer frontmatter — `paths`, `context: fork` (+ `agent`), `arguments`, skill-scoped `hooks` with `once` — adopted case-by-case through the adoption gate. | 2026-07-17 | | [`commands/`](https://code.claude.com/docs/en/plugins-reference) | Prohibited | Officially merged into skills; docs direct "use `skills/` for new plugins". Existing flat commands migrate to skill directories. | 2026-07-17 | | [Agents](https://code.claude.com/docs/en/sub-agents) | Adopt on need | Plugin agents do not support `hooks`, `mcpServers`, or `permissionMode` (security restriction) — design within that limit rather than working around it. | 2026-07-17 | -| [Workflows](https://code.claude.com/docs/en/workflows) | Adopt on need | Native and not experimental: a script in `workflows/`, or wherever the `workflows` manifest field points (that field replaces the default scan), runs as a plugin-namespaced `/plugin:name` command. Availability, not maturity, is the constraint — workflows are paid-plan-gated, a consumer can switch them off (`disableWorkflows`, `CLAUDE_CODE_DISABLE_WORKFLOWS`), and an org can disable them fleet-wide in managed settings; so, as with `bin/`, never make a workflow the only path to a capability. Not "Wait": the [deferred workflow engines](MIGRATION-PLAYBOOK.md#deferred-surfaces--decision-record-2026-07-12) are a named candidate carrying a live trigger, so the gap is identified rather than hypothetical. None ship in this fleet today. | 2026-07-27 | +| [Workflows](https://code.claude.com/docs/en/workflows) | Adopt on need | Native and not experimental: a script in `workflows/`, or wherever the `workflows` manifest field points (that field replaces the default scan), runs as a plugin-namespaced `/plugin:name` command. Availability, not maturity, is the constraint — workflows are paid-plan-gated, a consumer can switch them off (`disableWorkflows`, `CLAUDE_CODE_DISABLE_WORKFLOWS`), and an org can disable them fleet-wide in managed settings; so, as with `bin/`, never make a workflow the only path to a capability. Not "Wait": the [deferred workflow engines](adr/0020-defer-three-medley-surfaces-with-explicit-recheck-triggers.md) are a named candidate carrying a live trigger, so the gap is identified rather than hypothetical. None ship in this fleet today. | 2026-07-27 | | [Hooks](https://code.claude.com/docs/en/hooks) | Adopt on need | Exec form (`args`) is mandatory wherever `${user_config.*}` appears — shell form errors since v2.1.207; otherwise read the `CLAUDE_PLUGIN_OPTION_` mirror. Windows exec form spawns real executables only (no `.cmd`/`.bat` shims): use `"command": "node", "args": [...]`, a `${CLAUDE_PLUGIN_ROOT}`-rooted path, or shell form with `"shell": "bash"` — never a bare `bash`/`sh` (WSL relay) or `python`/`python3` (WindowsApps alias stub), whose launch fails non-blockingly and leaves a guard hook silently enforcing nothing. Prose cannot self-verify, so `scripts/check-hook-exec-form.sh` turns that rule into a mechanical check across hook configs and skill/agent frontmatter alike. | 2026-07-17 | | [MCP servers](https://code.claude.com/docs/en/mcp) | Adopt on need | Clears the plugin-acceptance security review for egress and trust delegation. Also the only component type that can cost a consumer their prompt cache: every other kind only appends to the request, while enabling or disabling a plugin that provides an MCP server forces a full re-read whenever the server's tools load into the prefix instead of being deferred by tool search ([actions that invalidate the cache](https://code.claude.com/docs/en/prompt-caching#actions-that-invalidate-the-cache), verified 2026-08-10). | 2026-08-10 | | [LSP servers](https://code.claude.com/docs/en/plugins-reference) | Adopt on need | Consumer must have the language-server binary; declare the prerequisite per the failure-behavior rules. | 2026-07-17 | @@ -342,6 +342,21 @@ A bare `context/…`-style path is reserved for a skill's OWN supporting files; the citing skill's directory, so a cross-skill citation written that way points at a file that is not there. +This permission stops at the plugin boundary. It exists because a plugin is the unit that ships: +one `plugin.json`, one version, one marketplace entry, and skills that always travel together, so +a citation between two skills in the same plugin cannot arrive at an absent file. **Do not path-cite +into a skill in a different plugin.** Plugins install independently, so that path can genuinely be +missing at runtime; cite the other plugin's skill by its `/plugin:skill` invocation instead, or +promote the shared content to a convention doc both plugins can cite. The same limit applies to +anything outside `plugins/`: `docs/**` and `.claude/rules/**` cite skills by slash invocation, never +by path. + +Heading anchors are never a citation target, in either direction. A heading is body structure, and +renaming one is exactly the refactor a skill must stay free to make. + +The full public-surface contract this narrows is +`/docs-hygiene:audit-encapsulation`'s, which audits against it. + ## Setup is explicit and repeatable A plugin requires a `setup` skill iff it has (a) a consumer-project configuration surface, (b) an @@ -543,7 +558,7 @@ prerequisite-absence rules are one slice of that contract, specialized here for Hooks follow the event's official control contract. Use a blocking result only when the event can still be blocked and the hook is enforcing a policy. Advisory hooks surface a visible non-blocking diagnostic. -Do not swallow errors or claim success when the promised result was not produced. +Surface every error, and report the result the run actually produced. ## Convention registry diff --git a/docs/adr/0018-treat-the-plugin-as-the-encapsulation-boundary-for-skill-citation.md b/docs/adr/0018-treat-the-plugin-as-the-encapsulation-boundary-for-skill-citation.md new file mode 100644 index 000000000..a90947fce --- /dev/null +++ b/docs/adr/0018-treat-the-plugin-as-the-encapsulation-boundary-for-skill-citation.md @@ -0,0 +1,228 @@ +# Treat the plugin, not the skill, as the encapsulation boundary for path citation + +- Status: accepted +- Date: 2026-08-26 + +## Context + +Two documents in this repository gave skill authors opposite instructions about citing a path +inside another skill, and both were being followed. + +`docs/PLUGIN-PHILOSOPHY.md:337-342` prescribes the citation and regulates only its path form: + +```text +Apply the same anchoring rule to bundled assets: one skill citing another skill's supporting file +writes the full `${CLAUDE_PLUGIN_ROOT}/skills//` form, optionally paired with a +relative markdown link target for browsing on GitHub +``` + +`plugins/docs-hygiene/skills/audit-encapsulation/context/public-surface-contract.md:31` rules the +citation itself out, whatever form the path takes: + +```text +A skill's `scripts/` directory is its declared entry surface. Harness surfaces, CI workflows, git hooks, and automation registries MAY path-cite `scripts/` entry scripts directly. **Sibling skills may NOT** — skill-to-skill stays slash-only. That outbound half of the asymmetry is out of scope for this inbound audit; a consuming repo that wants it enforced wires its own outbound gate. +``` + +and at `:25` defines every non-public file inside a skill as private, naming `context/`, +`reference/`, `actions/`, `evals/`, `templates/`, `*.schema.json`, and heading anchors. + +A repo-wide encapsulation audit resolved 11,641 path citations across the tracked markdown corpus, +dropped 7,903 self-citations, and adjudicated the remaining 407 non-self citations into private +surfaces down to 89 violations across 35 skills in 25 plugins. Thirty of the 89 are sibling-skill +reaches written in the form the philosophy document prescribes. + +That makes the 89 not a call-site defect. Rewriting them while the doctrine that produced them +stands would leave the repository self-contradicting, and the citations would come back on the next +authoring pass. The question is which document is wrong. + +## Decision + +**The plugin, not the skill, is this repository's unit of distribution, and the encapsulation +boundary follows the unit that ships.** + +The contract's rationale is rip-and-paste portability, stated at the skill-directory level in +`plugins/docs-hygiene/skills/audit-encapsulation/context/public-surface-contract.md:27`: + +```text +This guarantees skills are rip-and-paste portable: moving `.claude/skills//` into another repo carries every implementation detail with it; nothing outside the skill depends on internal layout. +``` + +Nothing in this repository ships that way. Measured on the tree: 71 plugin directories, 71 +`plugins/

/.claude-plugin/plugin.json` manifests carrying exactly one `version` each, 71 +`.claude-plugin/marketplace.json` entries each sourced at `./plugins/`, 235 skill directories, +**zero** per-skill manifests, and **no `.claude/skills/` directory in this repository at all**. A +consumer enables a plugin. Two skills in one plugin cannot be separated by any installation a +consumer can perform, so a citation between them cannot arrive at an absent file. + +Four clauses follow, and all four are load-bearing. + +1. **Intra-plugin citation into a sibling skill's private surface is legal here.** "Intra-plugin" + means the citing file sits under `plugins/

/` and the cited skill is `plugins/

/skills//` + for the same `

`. It covers sibling skill bodies, plugin-level `context/`, `reference/` and + `agents/` docs, and plugin READMEs. +2. **Cross-plugin path citation into another plugin's skill privates remains a violation.** Plugins + install independently, so the cited path can genuinely be absent at runtime. This is the case the + contract is actually about. The same limit applies to anything outside `plugins/`: `docs/**` and + `.claude/rules/**` cite skills by slash invocation, never by path. +3. **A citation that does not resolve from the base its own form implies is a defect, whatever its + form and wherever it sits.** This clause is not decoration. See correction 1. +4. **Heading anchors stay private even intra-plugin.** An anchor binds body structure rather than + file layout, and renaming a heading is exactly the refactor the contract protects. The + distribution-unit argument does not reach it. + +Measured against the 89: **55 dissolve, 34 remain.** The 34 are 24 cross-plugin, 8 non-resolving +intra-plugin, and 2 heading anchors. + +## What the measurement corrected, and why a reader needs it + +The ruling was drafted first and then measured against all 89 violations, with the classification +pass instructed to say if the evidence contradicted it. It did, on four points. The conclusion +survived; several of the arguments given for it did not. They are recorded here because a reader who +sees only the conclusion will re-make them. + +### Correction 1. The bare-relative clause was factually wrong and is withdrawn + +The draft ruling kept 49 findings alive on the grounds that a bare relative cross-skill path "stays +a defect on the philosophy doc's own reasoning". It does not. The doctrine condemns one specific +shape, at `docs/PLUGIN-PHILOSOPHY.md:341-342`: + +```text +A bare `context/…`-style path is reserved for a skill's OWN supporting files; it resolves against +the citing skill's directory, so a cross-skill citation written that way points at a file that is +not there. +``` + +That is `context/x.md` written with **no** `../` prefix from inside a skill. **Zero of the 89 +violations use that shape.** The 33 relative intra-plugin citations all compute a correct `../` path +and all 33 resolve on disk, verified individually. Applying the doctrine's breakage claim to them is +a category error. + +What is genuinely broken is a shape the draft never named: 8 citations in `plugin-root` form, +`skills//` written from a plugin-level `reference/` directory, where the implied base is +the plugin root but the real base is the citing file's own directory. Clause 3 exists to catch +those. + +### Correction 2. The contract's licence to relax is weaker than the draft claimed + +The draft cited +`plugins/docs-hygiene/skills/audit-encapsulation/context/public-surface-contract.md:3` as +authorizing the ruling. The full sentence is: + +```text +A consuming repo may layer its own conventions on top, but the surfaces and carve-outs below are what the detector implements. +``` + +The second clause reasserts the detector against whatever is layered. The sentence permits the +ruling; it does not authorize it, and quoting half of it overstated the case. This decision rests on +the distribution-unit evidence above, not on that sentence. It is a **deliberate narrowing of a +stated guarantee**, not a correct reading of the contract, and it is recorded as one. + +### Correction 3. The blast radius is 65, not 30 + +The draft said the decision blocks 30 violations, the sibling-skill-reach class, and leaves 59 +unaffected. Both halves are wrong. Only 26 of the 30 sibling reaches are intra-plugin; the other 4 +cross a plugin boundary and are untouched. And "intra-plugin" as defined also reaches 16 plugin +README citations and 23 plugin-level doc citations that the draft counted among the unaffected. +Recomputed split: **65 INTRA, 24 CROSS.** + +The 16 plugin READMEs are the substantive thing this decision does, and the draft never discussed +them. The contract names READMEs explicitly as external consumers. Under the distribution-unit +reasoning they are not external, because a plugin's README ships with the plugin. That consequence +is accepted deliberately here rather than absorbed unnoticed. + +### Correction 4. The corpus contains no instance of the harm the contract predicts + +Across 89 adjudicated violations and 35 leaked skills, **no cited path is missing from disk**. Both +heading anchors resolve. The schema file exists. No skill in this repository has yet renamed a +private file out from under an external citation. + +This is recorded rather than argued from, because it cuts both ways: it weakens the urgency of +remediating the 34 that survive, and it equally weakens any claim that the 55 dissolved citations +were doing damage. + +### The real defect class the audit found instead + +**Ten of the 89 citations do not resolve from the base their own form implies.** The target file +exists; the address written for it does not reach it. All ten are bare code spans, so none renders +as a broken link and nothing greps red, but an agent told to open the path fails on all ten. Eight +of the ten are intra-plugin, which is the case this decision legalises: **proximity did not prevent +them.** + +The sharpest single piece of evidence is inside one plugin. `plugins/discovery` cites the same three +targets twice, in two plugin-level docs, one form working and one not. + +`plugins/discovery/reference/parent-contract.md:15`, which resolves: + +```text +| `${CLAUDE_PLUGIN_ROOT}/skills/explore/reference/dispatch.md` | explore-only: the collision rule, the six-dimension cost of a re-dispatch, that family's ladder | +``` + +`plugins/discovery/reference/topic-docs.md:88`, which does not: + +```text +`skills/explore/reference/dispatch.md`, `skills/research/context/dispatch.md` and +``` + +Same plugin, same targets, one anchored and correct, one bare and unresolvable. That is why clause 3 +is binding rather than advisory: legalising the intra-plugin case without also requiring a +resolvable form produces drift inside a single plugin, and already has. + +Two of the ten sit in the doctrine document itself, at `docs/PLUGIN-PHILOSOPHY.md:596` and `:1071`, +writing a path with no resolvable base in the same file whose lines 341-342 forbid exactly that. The +first is a live Convention-registry row other plugins consult: + +```text +| Fresh-eyes declaration pattern contract | `skill-quality` plugin (`skills/check/reference/fresh-eyes-declarations.md`) | +``` + +The plugin name carries the base in prose. The path alone resolves against nothing, and this +repository ships two plugins with a `check` skill (`skill-quality` and `instruction-placement`), so +the token is genuinely ambiguous. Both lines are defective under any reading of this decision. + +## Where the convention is written down, and where it is not + +The classification pass recommended a repo-level `docs/conventions/` entry, on the grounds that +`public-surface-contract.md` ships inside a plugin to other repositories and claims applicability +"to any repo with `.claude/skills/`", so editing it would export this repository's relaxation to +everyone who installs `docs-hygiene`. + +That recommendation was **not** taken literally, and the departure is recorded here so it is chosen +rather than inherited. The relaxation was written into both documents instead: + +- `docs/PLUGIN-PHILOSOPHY.md` states the cross-plugin limit it previously omitted, which is how 30 + call sites came to read blanket permission into it, and states the anchor limit. +- `public-surface-contract.md` gained a conditional carve-out rather than a relaxation. It is gated + on the consuming repo actually being built that way (one manifest and one version per plugin, no + per-skill manifest, no installation path that separates two skills in one plugin) and on that repo + having declared the convention. A repo that has declared nothing gets the unrelaxed contract. + +The gate is what keeps the export from happening, and it is the whole reason the carve-out is +acceptable in a shipped file. A future edit that removes the gate re-opens correction 2's objection +in full. + +## Consequences + +- **55 citations dissolve with no edit**, including all 16 plugin READMEs. **34 remain**, none of + them applied as of this record. They are inventoried, with `path:line` and citation form, in + [`docs/specs/docs-hygiene-sweep-unapplied-remediations.md`](../specs/docs-hygiene-sweep-unapplied-remediations.md). +- **The detector was not changed.** + `plugins/docs-hygiene/skills/audit-encapsulation/scripts/detect.sh` and the skill's filter + taxonomy are untouched, so a raw run still surfaces all 65 dissolved citations as candidates. The + relaxation lives in the contract prose the agent applies when classifying, which is exactly what + that contract's own line 3 warns about: the surfaces below are what the detector implements. + Anyone re-running the audit will re-see the 65 and must apply this record to dismiss them. + Encoding the carve-out mechanically is open work, not done work. +- **This decision does not license the form.** An intra-plugin citation is legal and still has to + resolve. The anchored `${CLAUDE_PLUGIN_ROOT}/skills//` form is what makes it resolve + from any base; 57 intra-plugin citations do not use it today, 8 of which are broken because of it. + Normalising the other 49 is tidy-up, not a defect. +- **Anchors are under-counted and the clause is therefore under-enforced.** Only 2 heading-anchor + violations were found, both written as `#fragment`. Citations that pin a section by quoting its + title in prose bind body structure just as tightly and break just as silently, and are not + mechanically detectable. `plugins/source-control/skills/worktree/SKILL.md` publishes an anchor as + the plugin fleet's canonical address for one invariant, which is an argument for a narrow anchor + carve-out that this record declines to open. +- **Re-opens if** this repository ever ships or versions a skill independently of its plugin, or + adds a `.claude/skills/` tree, since both premises of the distribution-unit argument would fail. + It does not re-open on a request to relax cross-plugin citation: that is the case the contract is + about and the evidence here does not touch it. diff --git a/docs/adr/0019-share-code-across-plugins-by-vendoring-with-a-sync-gate.md b/docs/adr/0019-share-code-across-plugins-by-vendoring-with-a-sync-gate.md new file mode 100644 index 000000000..52b3e89eb --- /dev/null +++ b/docs/adr/0019-share-code-across-plugins-by-vendoring-with-a-sync-gate.md @@ -0,0 +1,77 @@ +# Share code across plugins by vendoring with a sync gate, not a shared package + +- Status: accepted +- Date: 2026-07-04 + +## Decision + +Decided when four plugins carried byte-identical `hooks/hook-utils.sh` copies — the Rule-of-Three +threshold below, exceeded. The mechanism is **single source of truth at authoring time, plain copies +at runtime**: + +- `lib/hook-utils.sh` is the only copy to edit. `scripts/sync-hook-utils.sh` propagates it into every + carrying plugin; a plugin opts in by committing an initial `hooks/hook-utils.sh` copy. +- CI (`hook-utils-sync` lane) fails a PR when any plugin copy drifts from the source, and when the lib + changed but a carrying plugin's manifest version did not — the plugin `version` is the update cache + key, so an unbumped plugin never delivers the change to consumers. +- Runtime is untouched: each installed plugin stays self-contained under cache isolation, with no + cross-plugin coupling and no change to the one-plugin install UX. + +Alternatives weighed (docs verified 2026-07-03): + +- **Dependency plugin carrying the lib — rejected as not viable.** A hook sees only its own + `${CLAUDE_PLUGIN_ROOT}` / `${CLAUDE_PLUGIN_DATA}`; no variable or documented mechanism exposes a + *dependency's* install path, and cache directories are per-version (with a commit-SHA suffix for + tag-resolved dependencies), so computing the path is unsupported by design + (; + ). **Recheck trigger:** Claude Code + ships a documented dependency-path variable — that would also allow sharing the lib beyond this + marketplace. +- **Marketplace-internal symlinks — deferred.** Documented mechanism: a symlink from a plugin to a + file elsewhere in the same marketplace is dereferenced at install, copying the target's content into + the cache — native SSOT with no sync script + (). + Deferred because such symlinks are *skipped* for `--plugin-dir` / local-path + installs (breaking the local development loop above) and are fragile to author and clone on Windows, + the primary environment on both the authoring and consuming side. **Recheck trigger:** the dev + loop stops depending on `--plugin-dir`, the documented `--plugin-dir` / local-path handling changes + so marketplace symlinks are no longer skipped (the upstream premise this deferral rests on), or the + Windows constraint lifts. +- **Copies with only a byte-identity CI gate — subsumed.** The chosen shape is that gate plus a + canonical source and one sync script, removing the edit-×N-by-hand step at negligible cost. + +The lib's unit tests live beside the source as one consolidated suite (`lib/hook-utils.test.sh`, +run by the same CI lane) rather than as per-plugin copies — byte-identity of the copies means +testing the source covers them. Plugins keep only their own black-box hook contract tests. + +### Vendored Node packages — the `file:` + `--install-links` convention + +Shared **Node** source (not a shell lib) is vendored as a plain package tree and consumed through +npm's `file:` link, because a cache-isolated plugin cannot reference a package outside its own +directory: + +- The vendored package is a self-contained runtime-source copy (its own test suite, build config, + and `node_modules` omitted). A consumer `package.json` depends on it with `"@scope/name": + "file:"`. +- The skill's `setup-deps.mjs` installs it into `${CLAUDE_PLUGIN_DATA}` with + `npm install --omit=dev --install-links ` — `--install-links` packs the `file:` + package as a real install (copied source) rather than a symlink back into the plugin cache, so it + survives cache isolation. Install is idempotent: a stored fingerprint hashes `package.json` **and + the entire vendored tree** (the packages install from source, not by version, so a source change + with no manifest bump must still reinstall). +- Runtime resolves bare specifiers (`@scope/name/subpath`) from `${CLAUDE_PLUGIN_DATA}/node_modules` + via an ESM resolve-hook (`run.mjs` → `register-hook.mjs`/`resolve-hook.mjs`), never a hardcoded + path into the plugin cache. + +### Intra-plugin sharing — one committed copy, no sync script + +When the second consumer is **another skill in the same plugin** (not another plugin), the +cross-plugin machinery collapses: put the vendored source once at the plugin root (`vendor/`), and +point every consuming skill's `file:` link and `setup-deps.mjs` fingerprint at that single copy +(`file:../../../vendor/*` from `skills//extraction/`). No `sync-*.sh` propagation and no +byte-drift CI gate are needed — there is only one committed copy, so nothing can drift. The +invariant that **replaces** the byte-drift gate is delivery-by-version: editing the shared source +obligates a plugin `version` bump, since the version is the update cache key. (`knowledge`'s +`repo-analysis` + `video-digestion`, shared by its `video-digest` and `course-digest` skills, is the +reference instance.) Reach for the cross-plugin shape above only once a *second plugin* genuinely +needs the same source. diff --git a/docs/adr/0020-defer-three-medley-surfaces-with-explicit-recheck-triggers.md b/docs/adr/0020-defer-three-medley-surfaces-with-explicit-recheck-triggers.md new file mode 100644 index 000000000..9c49cf852 --- /dev/null +++ b/docs/adr/0020-defer-three-medley-surfaces-with-explicit-recheck-triggers.md @@ -0,0 +1,35 @@ +# Defer three medley surfaces from the plugin migration, each with a recheck trigger + +- Status: accepted +- Date: 2026-07-12 + +## Decision + +Three general-purpose surfaces in the harvest-source repo (`melodic-software/medley`) are held out of this +wave's plugin migration deliberately, each with an explicit +[recheck trigger](../conventions/upstream-drift/README.md) — recorded here so the deferral +is a decision, not a silent omission. The medley side carries a thin pointer back to this record at each +surface (the workflow-engine authoring rule, the `onboard` skill, and the `gh-bot.sh` bot-identity +convention), so a contributor who touches a deferred surface finds the trigger without leaving that repo. + +- **Workflow engines** (`code-review.js`, `codebase-review.js`, `deep-research.js`, + `research-deep-fanout.js`, `skills-audit.js`, `skills-evals.js`, `skills-remediate.js`): deferred + 2026-07-12 as not a plugin component, so these may be removed entirely rather than migrated. + Re-verified 2026-07-27: the no-native-slot premise no longer holds — plugins now ship workflow + scripts via a `workflows/` directory + () or the `workflows` + manifest field (), and a + plugin workflow runs plugin-namespaced + () — but the deferral + stands on the usage question alone. **Recheck trigger:** the engines survive the next usage review + (still earning their keep) → migrate through the native plugin `workflows/` slot, verifying each + engine script fits the documented workflow-script shape, with a smoke test specced for that + dispatch path before packaging. +- **`onboard` skill:** repo-specific today — its phase gates encode this repo's exact runtime, linter, and + tooling pins. **Recheck trigger:** a second repo needs environment-prerequisite auditing → extract a + generic core through the extensibility-contract seams (the convention-resolution ladder infers or asks + for the per-repo pins), leaving repo specifics in tracked config rather than baked into the skill. +- **`tools/github-auth` (`gh-bot.sh`):** hardcodes the org's bot App / installation identity. **Recheck + trigger:** a second repo needs bot-actor GitHub operations → parameterize org / App / installation + through the seams (`userConfig` scalars, `sensitive` for the key) instead of standing up a second + hardcoded wrapper. diff --git a/docs/adr/0021-reject-the-three-unused-official-plugin-components.md b/docs/adr/0021-reject-the-three-unused-official-plugin-components.md new file mode 100644 index 000000000..9085f90d9 --- /dev/null +++ b/docs/adr/0021-reject-the-three-unused-official-plugin-components.md @@ -0,0 +1,42 @@ +# Reject the three unused official plugin components, each with a recheck trigger + +- Status: accepted +- Date: 2026-07-12 + +## Decision + +The three unused official plugin components raised as adoption candidates on this date, evaluated +against the enforcement hierarchy (default **REJECT** unless the value is concrete and not already +covered by an existing mechanism). This is that evaluation, not an index of every component the +marketplace does not use — the [component-stances table](../PLUGIN-PHILOSOPHY.md#component-stances) is +that index, and it carries a stance for components never raised here. Facts verified fresh +2026-07-12 per `CLAUDE.md` "Fresh-docs mandate". Verdict for all three: **REJECT now**, each with an +explicit recheck trigger — no implementation issues emitted (zero accepted). + +- **Monitors** (`monitors/monitors.json` / `experimental.monitors`) — **REJECT.** Both candidates are + either already covered or not concrete: a PR/CI watch duplicates `/source-control:pull-request monitor` + and a consumer's channel-mode PR watch (no gap), and a claude-ops collector-health watch carries no + concrete recurring pain that outweighs adopting an `experimental.*` component whose manifest schema may + change between releases (and which is skipped on the hosts / telemetry-disabled configs where the + Monitor tool is unavailable). **Recheck trigger:** monitors leave the `experimental` key AND a concrete + recurring in-session watch need surfaces for a shipped plugin, scoped via the documented `when: + "on-skill-invoke:"` monitor field so it starts only on demand rather than at session + start. Upstream: + , + . +- **`bin/`** (executables added to the Bash tool `PATH`) — **REJECT** as a marketplace-wide adoption. + Plugin-owned scripts already ship via `${CLAUDE_PLUGIN_ROOT}/scripts/` invoked by full path (the + established pattern — see "Shared tools and scripts seam" above); `bin/` adds only bare-command-on-`PATH` + invocation, which risks name collisions with the consumer's own commands, so it earns its place only + where a script is meant to be run as a bare command by the consumer. The one live candidate — the + knowledge plugin's extraction tooling — is owned by its publish issue #1373; the `bin/`-vs-`scripts/` + call belongs there, not duplicated here. **Recheck trigger:** a shipped plugin has a script the consumer + invokes as a bare command (not an internal helper). Upstream: + . +- **`subagentStatusLine`** (plugin `settings.json`) — **REJECT.** Purely cosmetic: it re-formats the + subagent panel row with no functional capability, so it does not clear the default-REJECT bar; its + richest inputs (per-row model + context-window size for a context percentage) additionally require a + recent Claude Code minimum. Candidate home was claude-ops. **Recheck trigger:** a concrete operational + need for custom subagent-row data during orchestration, not a presentation preference. Upstream: + , + . diff --git a/docs/adr/0022-consume-the-knowledge-corpus-from-a-separate-repository.md b/docs/adr/0022-consume-the-knowledge-corpus-from-a-separate-repository.md new file mode 100644 index 000000000..535979a16 --- /dev/null +++ b/docs/adr/0022-consume-the-knowledge-corpus-from-a-separate-repository.md @@ -0,0 +1,45 @@ +# Consume the knowledge corpus from a separate org-owned repository + +- Status: accepted +- Date: 2026-07-13 + +## Decision + +The `knowledge` plugin's ingest artifacts (transcripts, keyframes, source media, syntheses) get a +single dedicated consuming home rather than living in any one product repo, so a session can analyze +the whole corpus and fit relevant findings into *any* target repo. Decided with the owner in an +interview session against medley EPIC #1273 / wave-2 map #1369 (issue #1393); recorded here because +the wave's codification requirement puts convention decisions in tracked docs, not issue comments. + +- **Repo:** `melodic-software/knowledge-corpus`, private, org-owned. Organization ownership was + chosen because source media is retained and storage/bandwidth usage belongs with the shared corpus, + not a personal account. Created pure-IaC via the `melodic-software/github-iac` governed registry + (no ad-hoc `gh`, no import/drift window); the repo comes into being at the Pulumi deploy. +- **Media retention + LFS:** retain source video, keyframes, and any input useful for re-scraping or a + fresh analysis — the corpus is the durable substrate for re-runnable synthesis, not just derived + text. LFS-backed: a `.gitattributes` tracking media globs (mp4/mov/webm/png/jpg/jpeg/gif/pdf/epub/ + mp3/wav) plus pushed LFS objects. Git LFS is **not** expressible on the pulumi-github v6.14.0 + `Repository` resource → it is content-side, landing via a follow-up content PR to the repo, not + governed in IaC. Basis: the provider schema at the pinned tag — + , + where `github:index/repository:Repository` declares 48 properties and 39 input properties, none + matching `lfs`, and the document contains no case-insensitive `lfs` match at all (fetched and + probed 2026-07-29; re-run the same fetch against the then-pinned tag when the trigger below fires). + **Recheck trigger:** a pulumi-github + release notes LFS support on `Repository`, or the pinned provider version moves past v6.14.0 → + re-derive the IaC-vs-content-side call. GitHub's quotas, metering, and prices change; + verify the current account allowance, budget, and overage behavior in the + [official Git LFS billing documentation](https://docs.github.com/en/billing/concepts/product-billing/git-lfs) + before changing retention or ownership policy. +- **Artifact landing:** no consuming-repo name is baked into the plugin (contract v2.1 seam 1 + the + convention-resolution ladder), so it serves any consumer unchanged. Which pipeline lands where — and + which honor `library_dir` vs write elsewhere — is fast-moving plugin-seam state; the `knowledge` + plugin's own skill docs are the SSOT, not recapped here. +- **Integration flow — first-class capability:** the value step is analyze-here → fit-into-any-target. + Shape decided = a knowledge-plugin **`apply`/`integrate` skill** (a repeatable, invocable capability + seam-consistent with contract v2.1), NOT a documented manual workflow (which would rely on operator + memory and codify nothing). Full spec — target-repo scan, relevance ranking, how integrations are + proposed/applied — is decomposed to a dedicated `design(knowledge-integration)` issue under #1369 + per the one-session sizing rule, rather than half-built inline. +- **Scope boundary:** the songwriting-corpus (Pat Pattison EPUBs) destination is owned by #1402, not + decided here. How existing artifacts consolidate into this repo is operational — see #1393. diff --git a/docs/adr/0023-scope-skill-quality-to-the-generic-static-checker.md b/docs/adr/0023-scope-skill-quality-to-the-generic-static-checker.md new file mode 100644 index 000000000..2a8a17e77 --- /dev/null +++ b/docs/adr/0023-scope-skill-quality-to-the-generic-static-checker.md @@ -0,0 +1,33 @@ +# Scope `skill-quality` to the generic static checker and exclude the rest permanently + +- Status: accepted +- Date: 2026-07-13 + +## Decision + +The `skill-quality` plugin shipped only the generic static contract checker (`check-skill.sh`, seventeen +model-free checks) plus the `evals.schema.json` validation asset. Its held-back scope is resolved here as +**terminal exclusions** — decided out of the plugin for good, each with a permanent home, **not** deferrals +with a recheck trigger. (Contrast the "Deferred surfaces" record above, where the medley surface is held +*pending* a trigger; these are held *out*.) + +- **A/B eval runner** (`tools/evals/run-skill-comparison.sh`): a headless `claude -p` skill-body A/B + comparison driver — spins throwaway worktrees, runs fixture trials per arm, scrubs transcripts. **Not a + plugin component; stays medley-owned in `tools/evals/`.** It is a *dynamic authoring experiment* harness, + a distinct concern from this plugin's *static QA gate* (one cohesive capability per plugin — see the + design charter), with a single consumer and ~29 KB of worktree / hub-safety / platform path-scrub + surface that would be marketplace upkeep for that one consumer. **No recheck trigger:** a genuine + second-consumer demand is a fresh publish issue, not standing debt. +- **Contract libs** (`tools/skill-contract/`: portability, encapsulation, script-contract, dispatcher): + enforce medley-**invented** regimes — the skill public-surface / encapsulation contract, BEHAVIOR.md + symmetry, unit-anatomy, the cleanliness-regime script contract, and a medley-specific identifier + deny-list. **Permanent home is medley** (a de-couple-from-source-repo gate they cannot pass — every scan + scope, exemption, and identifier is this repo's). The narrow genuinely-generic seams (machine-path / + escape-path scanning, an encapsulation deep-cite regex, a "new script ships `--help` + a sibling test" + assertion, a deny-list scan *mechanism* whose data is per-consumer) are net-new versus the shipped + checks but have **no second consumer**; extracting them now is speculative generality / a pre-Rule-of- + Three abstraction (`melodic-software/standards` `conventions/engineering/simpler-code.md`). The plugin + can grow a machine-path check the day a real consumer needs one — as its own issue. +- **Checker hardening** (block-scalar description unfolding, an unquoted-`Use when:` warning, a + `CHECK_SKILL_BASE_REF` post-commit audit ref for the git-backed checks, and a line-1 frontmatter-fence + requirement): the one worker-executable slice — **landed** with this record. diff --git a/docs/adr/0024-decline-forgery-prone-human-ratification-gates.md b/docs/adr/0024-decline-forgery-prone-human-ratification-gates.md new file mode 100644 index 000000000..28cec2bb7 --- /dev/null +++ b/docs/adr/0024-decline-forgery-prone-human-ratification-gates.md @@ -0,0 +1,50 @@ +# Decline forgery-prone human-ratification gates and name the shared-identity limit + +- Status: accepted +- Date: 2026-07-23 + +## Decision + +Recorded from the #1187 audit (triggered when the operator did not recall ratifying the +`consumer-config-layering` → `config-cascade` seam). All 12 `docs/conventions/*` seams are +**PR-introduced** across the repo's whole history (established from git history), so none was silently +accreted. In-doc issue/PR citation is the intended ratification signal but is **inconsistent** across +the surfaces today — some seams cite their ratifying issue in the README/CHANGELOG (`config-cascade`'s +exception class → #649), others (`hook-precision`, `seam-phrasing`) carry no in-doc reference, so an +operator auditing from the durable convention surface alone cannot always find it. Converging every +seam on an in-doc citation is a follow-up, not asserted here as already-true. + +**The limitation, stated precisely — two provenance layers, only one collapses.** Distinguish: + +- **Git commit metadata** (author, committer, `Co-authored-by` trailers) **does** carry a distinct + identity — this very record's commit is authored by `Codex `; other agents commit + under their own identity (e.g. a `Co-authored-by: Claude …` trailer). So at the commit layer, agent + work is often *visible*. But it is **soft, not proof**: an agent can set its git author to anything, + so absence of an agent identity does not prove a human authored it. +- **GitHub gh-account actions** — PR author, PR review, merge, and the account a commit is *attributed + to* — **all collapse to `kyle-sexton`** (the account `gh` is scoped to), whether the human or an + agent-as-Kyle acted. At *this* layer no in-repo signal distinguishes human ratification from agent + accretion. + +So the gap is specifically at the **GitHub-account / review-and-merge layer**, which is exactly where +"ratification" is recorded — and it is a **repo-wide property**, not a defect of any one seam. + +**Decision — decline forgery-prone gates; they are theater.** A `CODEOWNERS` rule or a `human-ratified` +label requiring a `kyle-sexton` review does **not** distinguish anything at the account layer: an agent +satisfies the same gate under the same identity. Commit signing already runs (`required_signatures`) +but under the shared key, so it does not separate either, and commit-author metadata is spoofable as +above. Standing up such a gate would manufacture *false* assurance — worse than naming the limitation. +So none is added. + +**The only real distinguisher (flagged, not imposed).** Cryptographic separation requires an identity +agents do **not** hold — a distinct human-only GitHub account and/or a signing key kept off the agent +runners, with branch protection requiring that identity's review on `docs/conventions/**`. That is an +infrastructure change with real operator cost. **Recheck trigger:** the operator wants provable human +ratification, or a second human contributor joins (at which point identity separation exists naturally). + +**Interim posture.** Ratification stays **trust-based and visible**: a convention-seam change **should +cite** a ratifying issue/PR in-doc (the norm going forward — converging existing seams on it is the +follow-up above), and the operator's explicit engagement on that thread (as in the #163434 session) is +the ratification signal. The audit trail — issue, review comments, commit-author metadata where it +carries an agent identity, and this record — is the durable account, in place of an account-layer +assurance the shared GitHub identity cannot provide. diff --git a/docs/specs/docs-hygiene-sweep-unapplied-remediations.md b/docs/specs/docs-hygiene-sweep-unapplied-remediations.md new file mode 100644 index 000000000..df9c3d4bb --- /dev/null +++ b/docs/specs/docs-hygiene-sweep-unapplied-remediations.md @@ -0,0 +1,894 @@ +# docs-hygiene-sweep-unapplied-remediations + +The remediation set the repo-wide `docs-hygiene` sweep produced and had not applied when this record +was written. Every entry below is located by `path:line`, every line was verified against the +working tree on 2026-08-26, and the small high-value sets carry their verbatim source text and exact +replacement. + +This document exists so the work is resumable without re-auditing. The audit itself was the +expensive half: 1302 files, eight lanes, corpus-wide mechanical detectors substituting for a +fan-out that was not available, and a rejection rate high enough that re-deriving these findings +means paying for thousands of rejections again. See +[`docs-hygiene-sweep-yield-measurement.md`](docs-hygiene-sweep-yield-measurement.md) for those +denominators and for why re-running the prose lanes is not worth it. + +## Decay rule + +**This is a point-in-time record, stamped 2026-08-26, written while the sweep's own apply pass was +still running in another session.** Several in-file prose findings and several splits landed between +the first and last verification pass of this document, and are marked below as they stood at the +stamp. + +So the status column is the weakest thing here and the verbatim text is the strongest. **The check +is the text, never the status and never the line number.** If a finding's quoted source text is +still present at or near the cited path, the finding is open. If the replacement text is present +instead, it is done. A finding whose quote matches nothing in the file has been applied, superseded, +or moved by a split, and needs re-resolution rather than re-application. + +The inventory itself does not decay: what each lane found, what it declined, and what it could not +reach are the parts that cannot be re-derived without re-running the audit. + +## Contents + +- [Status at the stamp](#status-at-the-stamp) +- [How to apply anything in this document](#how-to-apply-anything-in-this-document) +- [Standing hazards, all lanes](#standing-hazards-all-lanes) +- [L2 splits: 21 files](#l2-splits-21-files) +- [L2 structure: 167 findings](#l2-structure-167-findings) +- [L3 deduplication: 13 clusters remediated, 13 refused](#l3-deduplication-13-clusters-remediated-13-refused) +- [L4 encapsulation: 34 violations](#l4-encapsulation-34-violations) +- [L5 noise: 10 findings](#l5-noise-10-findings) +- [L6 compression: 1 finding](#l6-compression-1-finding) +- [L7 write-for-agents: 13 findings](#l7-write-for-agents-13-findings) +- [L8 write-for-humans: 57 findings and 6 reclassifications](#l8-write-for-humans-57-findings-and-6-reclassifications) +- [Recall limits each lane declared](#recall-limits-each-lane-declared) + +## Status at the stamp + +Applied in the sweep's own change set before this record was written, and therefore **not** listed +below: + +- Both `L1` derivability outcomes. `plugins/repo-hygiene/skills/clean/reference/ecosystems.md` and + `plugins/ai-briefing/skills/generate/context/execution-flow.md` are deleted, the second only after + four behavioral rules were salvaged into its `SKILL.md`, and + `plugins/claude-ops/skills/known-issues/context/issue-templates.md` is converted to a pointer. +- Nine of the 30 `L2` split specs. +- `L6`'s finding C1, fixed at `scripts/sync-plugin-options-docs.py` and regenerated into 34 plugin + READMEs. +- Four detector defects, recorded in `plugins/docs-hygiene/CHANGELOG.md` 0.21.12 through 0.21.15. +- The two `E1` doctrine edits, now recorded as + [ADR 0018](../adr/0018-treat-the-plugin-as-the-encapsulation-boundary-for-skill-citation.md), and + the `E4` narrowing of `write-for-agents`'s own disclaimer. + +Everything else the sweep produced is below. + +**Landing during the writing of this record**, measured at the stamp rather than assumed. The apply +pass was consuming the in-file prose lanes and part of the split lane while this document was being +written, so these counts are a floor on what has since shipped, not a ceiling: + +| Lane | At the stamp | +|---|---| +| L2 splits | 8 of the 21 below had landed: `work-items` `setup`, `attend-queue`, `decompose`, `work-loop`, `work`, `triage`, plus `planning` `plan` and `interview` | +| L4 encapsulation | 0 of 34. Sampled 16 of the 34 citations at the stamp and all 16 were still open | +| L5 noise | 7 of 10 had landed. Still open: the two `babysit-prs` `negation` findings and the `babysit-prs` `plan-reference` finding | +| L6 compression | 0 of 1 | +| L7 write-for-agents | 9 of 13 had landed. Still open at the stamp: `H-1`, the `I-1` batch of 31 that the lane recommends declining, and the `write-for-agents` doctrine edit | +| L8 write-for-humans | Partly landed; not individually re-measured, because the class fixes (`Am1`, `M3`) are mechanical enough that a match against the quoted shape settles each site faster than a stale status column would | +| L2 structure, L3 | Not observed to move | + +## How to apply anything in this document + +1. **Match the quoted text, not the line number.** Line numbers here were verified on 2026-08-26 and + every one resolved at the time, but the apply pass and any split from the L2 section move lines + in the same file. +2. **Re-resolve after each dependency step.** The sweep's own ordering holds: deletions, then + splits, then deduplication, then citation rewrites, then one merged in-file prose pass. A + citation rewritten before a split targets a path about to move. +3. **One editor per file.** The L5, L6, L7 and L8 findings are all in-file prose edits; merge every + finding for one file into one edit rather than making four passes at it. +4. **`compress` cannot gate itself.** `docs-hygiene:compress` requires a semantic-diff subagent and + requires that the compressing context not be the verifying context. Any compression edit needs + that gate run from a context that can spawn one. + +## Standing hazards, all lanes + +- **Generated blocks.** Roughly 70 lines in each of 34 plugin READMEs sit between + `` and its `END` marker, emitted by + `scripts/sync-plugin-options-docs.py` and checked in CI by `plugin-options-docs-gate`. **Reject + any edit whose line falls inside such a block.** A hand edit there fails CI and reverts on the + next sync. Route it to the generator. +- **Fixtures are not prose.** 62 rows under `evals/fixtures/` and `scripts/fixtures/` are test data, + several deliberately defective specimens that anchor a passing eval. Editing one breaks the test + it exists for. Excluding them belongs in the manifest generator, not in each lane. +- **Mandated duplication.** Plugin contracts are carried inline at every adopting site on purpose, + because plugins ship without the marketplace repository + (`docs/conventions/untrusted-content/README.md:34`). Repetition there is portability. +- **Quoted material.** Several corpus files quote external authors verbatim. Compressing or + de-noising a quotation misattributes it. +- **Inline-floor rules.** The rate-limit-guard floors at + `plugins/source-control/skills/babysit-loop/SKILL.md`, + `plugins/work-items/skills/work-loop/SKILL.md` and + `plugins/work-items/skills/attend-queue/SKILL.md` are required byte-identical in the body by the + loop-lane convention. Do not split or dedup them. + +## L2 splits: 21 files + +Every spec below names the new spoke path and the line range that moves. `Extract` is the line count +that leaves the body. `Size` is what the audit measured; re-measured at the stamp, 13 of the 21 files +were byte-for-byte at that size and 8 had already been split by the apply pass (entries 2, 7, 8, 10, +11, 12, 18, 19). The line ranges below are the audit's, so on an already-split file read the range as +a description of what moved rather than as coordinates. + +| # | Hub | Tier | Size at audit | Extract | New spoke, and the range that moves | +|---:|---|---|---|---:|---| +| 1 | `plugins/discipline/skills/sweep-all/SKILL.md` | T2 | 469 L / 4,357 w | 300 | `reference/inheritance-preflight.md` (68 to 212); `reference/batched-pass.md` (214 to 366) | +| 2 | `plugins/work-items/skills/setup/SKILL.md` | T2 | 498 L / 6,153 w | 265 | `reference/autonomous-apply.md` (260 to 414, promoted to H1) | +| 3 | `plugins/session-flow/skills/find-handoff/SKILL.md` | T2 | 486 L / 5,813 w | 237 | `reference/rung-1-known-location.md` (83 to 184); `reference/rung-3-marker-detection.md` (193 to 318) | +| 4 | `plugins/autonomy/skills/setup/SKILL.md` | T2 | 494 L / 4,527 w | 224 | `context/guardrail-slice.md` (209 to 303); `context/routine-slice.md` (305 to 431) | +| 5 | `plugins/plugin-quality/skills/audit/SKILL.md` | T2 | 499 L / 5,579 w | 178 | `references/evidence-packet.md` (121 to 298, note the plural directory this plugin uses) | +| 6 | `plugins/overengineering/skills/delta/SKILL.md` | T2 | 480 L / 5,796 w | 149 | `context/baseline-model.md` (94 to 170); `context/run-states.md` (238 to 307) | +| 7 | `plugins/work-items/skills/attend-queue/SKILL.md` | T2 | 338 L / 3,400 w | 115 | `reference/telemetry-upsert.md` (163 to 277). Do **not** merge with the work-loop copy; that is L3's call. Do **not** split lines 278 to 319, the inlined rate-limit floor | +| 8 | `plugins/work-items/skills/decompose/SKILL.md` | T2 | 317 L / 3,580 w | 118 | Opt-in container lifecycle (194 to 260) and re-decompose (265 to 317); neither co-executes with the default first pass | +| 9 | `plugins/knowledge/skills/docpage-digest/SKILL.md` | T2 | 321 L / 3,312 w | 89 | `context/dual-verification.md` (163 to 251) | +| 10 | `plugins/work-items/skills/work-loop/SKILL.md` | T2 | 477 L / 4,816 w | 87 | `reference/admission-gate.md` (295 to 381) | +| 11 | `plugins/planning/skills/plan/SKILL.md` | T2 | 363 L / 6,338 w | 75 | `templates/plan-md-anatomy.md` (270 to 343). Keep separate from `context/plan-template.md`: that is the plan body template, this is the PLAN.md file skeleton | +| 12 | `plugins/work-items/skills/work/SKILL.md` | T2 | 264 L / 5,058 w | 122 | `context/claim-and-execute.md` (215 to 261); `context/selection.md` (105 to 179) | +| 13 | `plugins/discovery/agents/intent-tracer.md` | T2 | 341 L / 3,728 w | 71 | `plugins/discovery/reference/tool-honesty.md` (93 to 163). L3 owns the collapse of the three agents carrying the same rule | +| 14 | `plugins/education/skills/teach/SKILL.md` | T2 | 265 L / 4,538 w | 63 | `context/pedagogy.md` (134 to 196, H3s raised to H2) | +| 15 | `plugins/mutation-testing/skills/audit/SKILL.md` | T2 | 406 L / 4,360 w | 115 | `templates/report.md` (251 to 293); `context/execute.md` (123 to 194) | +| 16 | `plugins/discovery/skills/research/SKILL.md` | T2 | 234 L / 5,058 w | 49 | `context/routing.md` (23 to 71). Keep separate from `context/dispatch.md`, which owns the parent-side contract | +| 17 | `plugins/discovery/skills/explore/SKILL.md` | T2 | 224 L / 4,045 w | 52 | `context/routing.md` (21 to 72) | +| 18 | `plugins/planning/skills/interview/SKILL.md` | T2 | 302 L / 6,346 w | 37 | `tier-mismatch`, not a new spoke: lines 233 to 274 collapse into the existing `context/session-config.md`, which already declares itself the reference layer for that exact section | +| 19 | `plugins/work-items/skills/triage/SKILL.md` | T2 | 198 L / 4,070 w | 56 | `context/apply-outcome.md` | +| 20 | `prompts/loops/loop-lane-prompts.md` | T3 | 1,961 L | 775 | `prompts/loops/loop-lane-profile-claude-code-plugins.md` (1,187 to 1,961). The file declares itself repository-agnostic and 40% of it is one repository's filled instance | +| 21 | `docs/MIGRATION-PLAYBOOK.md` | T3 | 1,738 L | 266 | Six dated decision records (1,473 to 1,738) move to `docs/adr/`. **Renumber:** the spec was written for 0018 through 0023, and 0018 is now taken, so they land at 0019 through 0024 | + +`plugins/implementation/skills/implement-dispatch/SKILL.md` was measured and is **not** a finding: +118 lines but 3,420 words, roughly 29 words per line. It is recorded because a future addition +crosses the ceiling without the line count moving. + +The `MIGRATION-PLAYBOOK.md` split is the only one with heavy inbound citation. It is cited from many +plugin changelogs and from `docs/PLUGIN-PHILOSOPHY.md`. No citation found in the audit targets a +moved section by anchor, but re-check anchors before cutting. + +## L2 structure: 167 findings + +| Shape | Count | Applied | +|---|---:|---| +| `missing-toc` | 135 | none | +| `blind-pointer` | 22 | none | +| `deep-nesting` | 6 | none | +| `orphan-spoke` | 4 | 1 resolved by deletion, 1 is a no-treatment awareness row | + +### `missing-toc`, 135 + +**This one is re-derivable and should be re-derived rather than trusted.** It is a mechanical +predicate over each file's own headings, it is the one finding class in the sweep that is safely +scriptable, and it should be treated as one pass rather than 135 edits. + +The predicate: a file above 300 lines with no `## Contents` section, excluding changelogs, vendor +trees and fixtures. Re-running it on 2026-08-26 over the tracked corpus outside `docs/topics/` +returns **134** files. The audit's own count was 120 at a different exclusion boundary and before +nine splits landed, so use the live number. + +The target shape is the repo's own, at +`plugins/docs-hygiene/skills/audit-progressive-disclosure/context/tier-model.md:3-9`: a +`## Contents` heading under the H1 with one anchor link per H2. + +Concentration by group, as measured, for scoping: + +| Group | Files | Largest | +|---|---:|---| +| `I-songwriting` | 40 | `plugins/songwriting/context/pat-pattison/research/meter.md`, 1,922 L | +| `K-repo-docs` | 15 | `docs/MIGRATION-PLAYBOOK.md`, 1,738 L | +| `C-vcs-repo` | 11 | `plugins/source-control/skills/babysit-prs/reference/orchestration.md`, 962 L | +| `B-cc-config-ops` | 9 | `plugins/claude-config/skills/audit-instructions/reference/criteria.md`, 1,742 L | +| `E-session-behavior` | 9 | `plugins/session-flow/reference/save-point.md`, 566 L | +| `G-code-design` | 7 | `plugins/event-storming/skills/simulation/reference/agentic-simulation.md`, 1,023 L | +| `F-quality-verify` | 5 | `plugins/review/skills/quality-gate/context/close-out.md`, 415 L | +| `H-knowledge-research` | 5 | `plugins/knowledge/skills/docpage-digest/context/anthropic-docs-profile.md`, 420 L | +| `D-work-planning` | 4 | `plugins/work-items/tools/work-item-tracker/CONTRACT.md`, 769 L | +| `A-doc-quality` | 1 | `plugins/ai-slop/skills/audit/reference/catalog.md`, 901 L | +| `J-toolchain-platform` | 1 | `plugins/machine-health/skills/audit/references/windows/check-catalog.md`, 352 L | +| `M-repo-root` | 1 | `prompts/loops/loop-lane-prompts.md`, 1,961 L | + +Two directory-level index gaps, which are new files rather than splits and create no ordering +dependency: `plugins/songwriting/context/pat-pattison/research/` (51 files, no `README.md`) and +`docs/` (12 top-level files plus 6 subdirectories, no `README.md`). + +The 303 files in the 100-to-300-line band are **not** findings. The two official sources disagree at +that length, and the audit carried them as one awareness entry per group with no treatment. + +### `blind-pointer`, 22 + +Almost all one shape: a trailing index section that names what each spoke holds and never says when +to open it. The remediation is a rename plus a when-clause per row, not a new convention, because +the repository already contains three correct versions of the section: + +- `plugins/plugin-quality/skills/audit/SKILL.md:485`, `## Reference index. Load on demand`, with a + literal `Load when` table column. The best example in the corpus. +- `plugins/source-control/skills/commit/SKILL.md:377`, same heading. +- `plugins/claude-ops/skills/observability/SKILL.md:36`, `## Context ladder (read on demand)`. + +| Site | Heading | Rows | +|---|---|---:| +| `plugins/docs-hygiene/skills/{audit-derivability,audit-noise,audit-progressive-disclosure,compress,extract-ssot}/SKILL.md` | inline shared-fallback sentence | 5 | +| `plugins/docs-hygiene/skills/{audit-encapsulation,compress,extract-ssot}/SKILL.md` | `## Cross-references` | 3 | +| `plugins/claude-ops/skills/{changelog,lanes,morning-brief,observability,plugins}/SKILL.md` | `## Cross-references` | 5 | +| `plugins/bugs/skills/{scan,write}/SKILL.md` | `## Cross-references` | 2 | +| `plugins/ai-briefing/skills/generate/SKILL.md:140` | `## References` | 1 | +| `plugins/discovery/skills/research-deep/SKILL.md:120` | `## See also` | 1 | +| `plugins/source-control/skills/babysit-prs/SKILL.md:409` | `## References` | 1 | +| `plugins/kindle-dedrm/skills/manage/SKILL.md:154` | `## Cross-references` | 1 | +| `plugins/planning/skills/interview/SKILL.md` | spoke listed under `## What this skill does NOT do` | 1 | +| `plugins/work-items/skills/onboard-adapter/SKILL.md:203` | `## Related` | 1 | +| `docs/topics/ai-adoption-ladder/design/design-threads.md` | nine bare-name citations, awareness only | 1 | + +Three of L7's P3 pointer findings overlap this shape. Where both fire on one line, take the +blind-pointer rewrite, which is fuller, and drop the P3 fix rather than applying both. + +### `deep-nesting`, 6 + +| Path | Tier | Chain | +|---|---|---| +| `plugins/claude-config/skills/audit-pass/reference/terms.md` and `reference/finding-identity.md` | 2 | `plugins/claude-config/skills/audit-pass/SKILL.md:19` to `plugins/claude-config/skills/audit-pass/reference/run-contract.md:9` to leaf. Every other leaf opens by assuming `terms.md`, and it is the file furthest from the hub | +| `plugins/architecture/skills/improve/research/deepening/*.md` (5 files) | 2 | `plugins/architecture/skills/improve/SKILL.md:35` to `plugins/architecture/skills/improve/actions/deepening.md:26` to that skill's `research/deepening/scan-briefing.md`. The citing line calls the target load-bearing for scan quality | +| `plugins/session-flow/skills/retro/reference/ecosystem-improvement-catalog.md` | 2 | `plugins/session-flow/skills/retro/SKILL.md` to `plugins/session-flow/skills/retro/context/session.md:184` ("Load the catalog") to the catalog | +| `plugins/knowledge/skills/course-digest/reference/screenshot-strategy.md` | 2 | `plugins/knowledge/skills/course-digest/SKILL.md` to `plugins/knowledge/skills/course-digest/context/workflow.md:46` to the strategy | +| `plugins/claude-ops/skills/known-issues/context/issue-templates.md`, `context/output-templates.md` | 3 | Explicitly conditional offline snapshots. Alternates, not required reading. **No treatment** | +| `plugins/songwriting/context/pat-pattison/research/book-references.md` | 3 | Shared bibliography cited by its siblings. Legitimate cross-reference. **No treatment** | + +### `orphan-spoke`, 4 in a tree of 507 + +| Path | Status | +|---|---| +| `plugins/ai-briefing/skills/generate/context/execution-flow.md` | **Resolved.** Deleted in the sweep after four rules were salvaged into `SKILL.md` | +| `plugins/implementation/skills/implement/context/gotchas.md` | **Open.** Dead within its skill: its three siblings are cited from the mode table at `plugins/implementation/skills/implement/SKILL.md:43-45`, it is not, and `:210` of that same file carries a `## Gotchas` heading inline instead. Add the pointer and move the inline entries into the file | +| `plugins/songwriting/skills/suno/reference/suno-drift-audit-ledger.md` | **Open.** Maintenance ledger, unreferenced. Add a pointer under a new maintenance section, or move to plugin scope | +| `plugins/knowledge/skills/video-digest/extraction/liveness/LIVENESS.md` | **No treatment.** Co-located script README beside `run-source-liveness.js`, not a disclosure spoke | + +The count is 4 and not 132. `audit-progressive-disclosure`'s detector reported 132 before its +`md_links()` bug was fixed in 0.21.14; the 4 above were each verified by an independent repo-wide +reachability pass and by a targeted grep. + +## L3 deduplication: 13 clusters remediated, 13 refused + +Every remedy is an in-place fix against a file that already exists. **No cluster proposes a new SSOT +artifact**, and three that passed the Rule of Three were still resolved in place. Nothing here was +applied. + +### Remediated, N greater than or equal to 3 + +| Cluster | Instances | Existing owner | Remedy | +|---|---:|---|---| +| `lane-telemetry-upsert` | 3 | `plugins/claude-ops/skills/lanes/SKILL.md`, "Never pass a body as an `@path` string" | `name-an-owner` plus `normalize-wording`. Highest value in the lane: drifted, unowned, unguarded | +| `dynamic-context-git-preamble` | 44, 26 edited | none; proposes a `docs/PLUGIN-PHILOSOPHY.md` "Inline-template conventions" home | `normalize-wording` plus `edit-existing-rule`. One canonical fallback string corrects a live mislabel (`echo "clean"` on a failed `git status`) at 15 sites | +| `setup-probe-dont-recite` | 17 | `docs/PLUGIN-PHILOSOPHY.md` "Setup is explicit and repeatable" (clause absent) | `edit-existing-rule`. All 17 already cite that contract at document scope | +| `setup-headless-reconfigure-recipe` | 22, 6 edited | `docs/PLUGIN-PHILOSOPHY.md` "Configuration ownership and scope" | `normalize-wording` | +| `detector-findings-producer-preamble` | 4 | `docs/conventions/detector-findings/README.md` | `normalize-wording`. Removes two em dashes in `plugins/mutation-testing/skills/audit/context/persist-findings.md:1` and `:5` as a side effect | +| `songwriting-persistence-block` | 9 | `plugins/songwriting/context/pat-pattison/research/artifact-persistence.md` | `trim-to-citation`, the lane's only one, because it is the only cluster whose owner sits inside the same plugin as its call sites | +| `setup-never-writes-boundary` | 41, 34 edited | `docs/PLUGIN-PHILOSOPHY.md` "Configuration ownership and scope" | `normalize-wording` | + +### Remediated, N equal to 2 and N equal to 1 + +| Cluster | Sites | Remedy | +|---|---|---| +| `statusline-shim-durable-wiring` | `context-guard` / `rate-limit-guard` setup skills plus READMEs | `name-an-owner`. No owner declared and the scripts have already drifted | +| `toolchain-remote-resolution-snippet` | `toolchain:check` / `toolchain:lint` | `name-an-owner` | +| `prototype-throwaway-constraints` | `prototype:explore-directions` / `prototype:pressure-test` | `name-an-owner` | +| `github-read-only-posture` | `github:advise` / `github:audit` | `name-an-owner` | +| `marketplace-bootstrap-placeholders` | `dometrain` / `miro` setup skills | `edit-existing-rule` plus `normalize-wording` | +| `planning-setup-uncited-reconfigure-recap` | `plugins/planning/skills/setup/SKILL.md:120-141` | `normalize-wording` plus a provenance citation. The only setup skill of 51 carrying the reconfiguration block with no citation anywhere in the file | + +### Refused, 13 clusters and roughly 197 instances + +Recorded so nobody re-opens them. + +| Cluster | Instances | Refusal ground | +|---|---:|---| +| `plugin-lifecycle-artifact-protocol` | 6 | Registered cluster, CI check `validate-plugin-contracts.mjs` | +| `standards-contract-mirror` | 3 | Registered cluster, CI check `sync-standards-contract.sh` | +| `plugin-options-generated-block` | 34 | Generator-owned, `sync-plugin-options-docs.py` | +| `fleet-changelog-entries` | 64 | Per-plugin historical record | +| `untrusted-content-spine` | ~18 | Convention mandates inline carry, with a conformance sweep | +| `rate-limit-guard-floor-inline` | 4 | Declared inline-floor rule, provenance-only citation, byte-identical | +| `autonomy-routine-axis-scaffolding` | 10 | Every row cites its owner; per-identity table data; no drift | +| `discovery-agents-tool-honesty` | 3 | Locked by `plugins/discovery/agents/tool-honesty.test.sh` | +| `topic-docs-plugin-slices` plus the discovery/verification setup pair | 12 | Cited convention slices; the pair carries a recorded prior refusal | +| `formatter-readme-requirements` | 12 | EXPOSE surface, primary-source URL, no drift | +| `commit-convention-well-known-path` | 4 | Template-owned, byte-identical to the emitting template | +| `setup-write-vs-session-effect` | 18 | 17 of 18 already carry a document-scope citation | +| `songwriting-author-seam` | 9 | 9 of 9 already cite by exact heading | + +**Sequencing.** Roughly 45 `plugins/*/skills/setup/SKILL.md` files are touched by four sub-clusters +at once and must be worked one pass per file, not in parallel. `docs/PLUGIN-PHILOSOPHY.md` carries +three clusters' owner additions and must go first, because every later cluster cites it. + +## L4 encapsulation: 34 violations + +The other 55 of the audit's 89 dissolved under +[ADR 0018](../adr/0018-treat-the-plugin-as-the-encapsulation-boundary-for-skill-citation.md). These +34 do not. All 34 `path:line` citations were re-verified on 2026-08-26 and every one resolves to the +citing text the audit quoted. + +### Group 1. Cross-plugin and out-of-plugin, 24 + +Unchanged by ADR 0018. The remedy is `/plugin:skill ` routing, or promoting the cited +content to `plugins/

/reference/` or a `docs/conventions/` entry, which sit outside every skill +directory and are legal cite targets. + +| # | Citing `path:line` | Cited private surface | +|---|---|---| +| `V-review-01` | `docs/conventions/detector-findings/README.md:9` | `review/skills/fanout/context/default-mode.md` | +| `V-review-02` | `docs/conventions/detector-findings/README.md:79` | `review/skills/fanout/context/fix-pass-mode.md` | +| `V-review-03` | `docs/conventions/detector-findings/README.md:83` | `review/skills/fanout/context/findings-normalization.md` | +| `V-review-04` | `docs/conventions/detector-findings/README.md:109` | `review/skills/fanout/context/findings-normalization.md` | +| `V-review-05` | `docs/conventions/detector-findings/README.md:261` | `review/skills/fanout/context/fix-pass-mode.md` | +| `V-review-06` | `docs/conventions/detector-findings/README.md:303` | `review/skills/fanout/context/fix-pass-mode.md` | +| `V-review-07` | `docs/conventions/detector-findings/README.md:482` | `review/skills/fanout/context/fix-pass-mode.md` | +| `V-review-08` | `docs/conventions/detector-findings/README.md:497` | `review/skills/fanout/context/fix-pass-mode.md` | +| `V-review-09` | `docs/conventions/detector-findings/README.md:506` | `review/skills/fanout/context/fix-pass-mode.md` | +| `V-review-10` | `docs/conventions/detector-findings/README.md:628` | `review/skills/fanout/context/default-mode.md` | +| `V-review-11` | `docs/conventions/detector-findings/README.md:629` | `review/skills/fanout/context/fix-pass-mode.md` | +| `V-review-12` | `docs/conventions/detector-findings/README.md:631` | `review/skills/fanout/context/findings-normalization.md` | +| `V-review-13` | `docs/conventions/native-references/README.md:127` | `review/skills/quality-gate/context/pr.md` | +| `V-review-14` | `docs/conventions/native-references/README.md:183` | `review/skills/quality-gate/context/pr.md` | +| `V-slop-01` | `.claude/rules/vendor-docs-are-not-style.md:10` | `ai-slop/skills/audit/reference/rewrite-guide.md` | +| `V-slop-02` | `docs/conventions/upstream-drift/README.md:342` | `ai-slop/skills/audit/reference/catalog.md` | +| `V-dhg-01` | `docs/conventions/upstream-drift/README.md:343` | `docs-hygiene/skills/write-for-humans/reference/sources.md` | +| `V-sf-01` | `docs/conventions/pre-pr-ordering/README.md:5` | `session-flow/skills/workflow/context/pre-pr.md` | +| `V-sq-01` | `docs/PLUGIN-PHILOSOPHY.md:596` | `skill-quality/skills/check/reference/fresh-eyes-declarations.md`, also unresolvable | +| `V-sq-02` | `docs/PLUGIN-PHILOSOPHY.md:1071` | same target, also unresolvable | +| `V-sc-15` | `plugins/work-items/skills/setup/reference/overlay-ignore-probes.md:18` | `source-control/skills/setup/reference/apply-convention.md` | +| `V-ops-01` | `plugins/claude-config/skills/audit-pass/reference/run-state-and-resumability.md:70` | `claude-ops/skills/lanes/context/restart-consumer.md` | +| `V-auto-01` | `plugins/source-control/skills/babysit-loop/reference/promotion-evidence-resolution.md:8` | `autonomy/skills/setup/schemas/guardrails-security-binding.schema.json` | +| `V-ct-01` | `plugins/claude-config/skills/audit-instructions/reference/criteria.md:392` | `code-tidying/skills/tidy/reference/tidyings.md` | + +Three of these deserve their own note. + +`V-slop-01` is the only Tier 1 entry in the whole sweep: an always-loaded rule sends every agent in +every session to a private reference. `V-review-01` through `V-review-12` are the only case in the +corpus where the dependency is stated as a contract rather than written as a convenience: a +repo-level convention other plugins implement names three `review:fanout` private files as its +"External authority". `V-auto-01` is the corpus's only schema-file citation, and the contract's +treatment at +`plugins/docs-hygiene/skills/audit-encapsulation/context/public-surface-contract.md:37` is +unambiguous: + +```text +Schema files (`*.schema.json`) stay private — route via `/skill-name ` or vendor the schema to a shared tooling location the consumer repo owns. +``` + +Four of the 24 are skill-body reaches that cross a plugin boundary and are therefore filed under the +dissolved sibling-reach class in the audit's own roll-up: `V-sc-15`, `V-ops-01`, `V-auto-01`, +`V-ct-01`. They are not dissolved. + +### Group 2. Intra-plugin citations that do not resolve as written, 8 + +Legal as citations under ADR 0018, defective as paths. **The fix is form, not routing:** rewrite each +to the anchored `${CLAUDE_PLUGIN_ROOT}/skills//` form, which +`plugins/discovery/reference/parent-contract.md:15-17` already uses correctly for three of the same +targets, or add the `../` the `plugin-root` form is missing. + +| # | Citing `path:line` | Path as written | Resolves to | +|---|---|---|---| +| `V-sc-01` | `plugins/source-control/reference/config-resolution.md:179` | `skills/babysit-loop/reference/promotion-evidence-resolution.md` | `plugins/source-control/reference/skills/...`, absent | +| `V-sc-02` | `plugins/source-control/reference/review-discipline.md:306` | `skills/babysit-loop/reference/pre-escalation-dispatch.md` | same shape, absent | +| `V-sc-03` | `plugins/source-control/reference/review-discipline.md:171` | `skills/babysit-prs/reference/safety.md` | same shape, absent | +| `V-sc-04` | `plugins/source-control/reference/review-discipline.md:271` | `skills/babysit-prs/reference/safety.md` | same shape, absent | +| `V-sc-05` | `plugins/source-control/reference/review-discipline.md:303` | `skills/babysit-prs/reference/independent-resolution.md` | same shape, absent | +| `V-disc-04` | `plugins/discovery/reference/topic-docs.md:88` | `skills/explore/reference/dispatch.md` | `plugins/discovery/reference/skills/...`, absent | +| `V-disc-05` | `plugins/discovery/reference/topic-docs.md:88` | `skills/research/context/dispatch.md` | same shape, absent | +| `V-disc-06` | `plugins/discovery/reference/topic-docs.md:89` | `skills/trace-intent/context/dispatch.md` | same shape, absent | + +### Group 3. Heading anchors, 2 + +Both intra-plugin, both currently resolving, both outside ADR 0018's reach because an anchor binds +body structure rather than file layout. + +| # | Citing `path:line` | Cited anchor | +|---|---|---| +| `V-sc-17` | `plugins/source-control/reference/worktree-root-convention.md:66` | `../skills/worktree/SKILL.md#the-nesting-invariant-verified` | +| `V-dh-01` | `plugins/disk-hygiene/README.md:190` | `skills/clean/reference/safety-model.md#standalone-git-checkout-evidence` | + +`V-sc-17` has a genuine case for a narrow anchor carve-out rather than a rewrite: +`plugins/source-control/skills/worktree/SKILL.md:54` claims canonical ownership of that section for +the whole plugin fleet and says every other surface in the plugin points there instead of restating +it, which is a skill publishing an anchor as an interface. ADR 0018 declines to open that carve-out. + +### Not remediated and not counted + +57 intra-plugin citations would benefit from normalising to the anchored form. That is tidy-up, not a +defect, and it is not part of this set. Eight of them are in group 2 because they are also broken. + +## L5 noise: 10 findings + +All Tier 2 except the `plan-reference` finding, which is Tier 1. Treatment is never a deletion; the +constraint survives the rewrite. Line numbers below are **re-verified against the working tree on +2026-08-26** and differ from the audit's own where a file has since moved. + +### `negation`, 6 + +**1. `docs/PLUGIN-PHILOSOPHY.md:561`** (the audit recorded `:546`; the file gained 15 lines above it) + +```text +Do not swallow errors or claim success when the promised result was not produced. +``` + +No positive anywhere in the paragraph. Replacement: + +```text +Surface every error, and report the result the run actually produced. +``` + +**2. `plugins/review/skills/fanout/context/fix-pass-mode.md:142`** + +```text +- **NEVER route correctness findings to `/simplify`.** +``` + +A whole bullet with no positive. The destination for a correctness finding is left unstated. +Replacement: + +```text +- **Route correctness findings to the fix pass. `/simplify` is quality-only and does not hunt bugs.** +``` + +Confirm the destination against the fix pass's own routing table before applying. + +**3. `plugins/instruction-placement/skills/realign/context/apply-recipes.md:70`** + +```text +**Never** put an `@import` in the body of a path-scoped rule. The import inlines at session start and +defeats the scoping — the move would read as a saving and not be one. +``` + +The second sentence is rationale, not an alternative. Replacement for the first sentence, keeping +the second verbatim: + +```text +**Cite** the shared file from a path-scoped rule by path, never with an `@import`: the import +inlines at session start and defeats the scoping. +``` + +**4. `plugins/adhd/skills/shape/SKILL.md:39-40`** (the audit recorded `:38-39`) + +```text +1. **Working memory is small.** Anything off-screen is gone. Never ask the + reader to "keep in mind" something stated earlier. +``` + +The positive form is the skill's own standing rule, stated in its `description` as "restate state +across turns". Replacement for the third sentence: + +```text +Restate any earlier state the reader needs, in the current response. +``` + +**5. `plugins/source-control/skills/babysit-prs/SKILL.md:321`**, sentence-final on the line + +```text +Never block a safe iteration on the engine's absence. +``` + +The preceding clause covers reporting, not proceeding, so the positive is genuinely absent. +Replacement: + +```text +Let a safe iteration proceed when the engine is absent, reporting merge-readiness as unchecked. +``` + +**6. `plugins/source-control/skills/babysit-prs/reference/loop.md:629`** + +```text +- **Do not skip verification steps.** The D5/D6/D7 verification sub-steps exist because model + memory is unreliable across compaction boundaries. +``` + +Replacement for the bolded lead, the rationale sentences surviving verbatim: + +```text +- **Run the D5/D6/D7 verification sub-steps on every pass.** +``` + +### `ghost-ref`, 3 + +All three are paths that have never existed for any reader other than the original author: a pruned +topic slice, and two `.work/` paths that are gitignored at `.gitignore:29`. + +**1. `docs/specs/invocation-mode-doctrine-brief.md:5`**, lines 5 to 7: + +```text +`docs/topics/pocock-course-lanes/PLAN.md` on branch `claude/plan-mode-discussion-55kszx`, steering +rows now in `docs/upstream/aihero-course.md` — the interim steering record dissolved into it at +the lane 6 harvest). +``` + +The sentence already carries its own durable replacement one clause later. Replacement: + +```text +chain contract: the steering rows in `docs/upstream/aihero-course.md`, which the interim +steering record dissolved into at the lane 6 harvest). +``` + +**2. `docs/specs/invocation-mode-doctrine-brief.md:8`**, lines 7 to 8: + +```text +Interview ledger: +`.work/invocation-mode-doctrine/interview-checklist.md` (8/8 answered, register gate clean). +``` + +The parenthetical carries the whole load. Replacement: + +```text +Interview ledger: 8/8 answered, register gate clean (memory tier, not committed). +``` + +**3. `docs/specs/write-for-agents-brief.md:6`**, lines 5 to 8: + +```text +answered, register gate clean, **user confirmed the shared understanding 2026-08-17**). Working +ledger: the topic's memory slice (`.work/authoring-steering-skill/`, disposable). The verified +auto-read enumeration feeding the scope statement lives in that slice's `RESEARCH.md` artifact +set; its durable adaptation lands in the skill's reference file at implementation. +``` + +Worse than finding 2: this one sends the reader into an unrecoverable path for the evidence behind +the scope statement. Replacement for the last two sentences: + +```text +The verified auto-read enumeration behind the scope statement lands in the skill's reference +file at implementation. +``` + +If that enumeration is load-bearing evidence rather than working notes, promote it into the brief +instead of stripping the pointer. + +### `plan-reference`, 1 + +**`plugins/source-control/skills/babysit-prs/reference/loop.md:56`** (Tier 1), lines 56 to 59: + +```text +**Draft policy (replaces the old blanket draft skip):** drafts stay in the discovery list in +every tier. In the safe tier a draft is evaluated — terminal state, CI, unaddressed findings — +and reported, never fixed, never marked ready. Worker/autopilot draft handling (zero-blocker +drafts route through a worker; `gh pr ready` only in autopilot) is defined in SKILL.md. +``` + +The parenthetical narrates the changeset that produced the policy. No reader of this file has the +old blanket draft skip to compare against. Delete the parenthetical only. Replacement for the bolded +lead, the rest of the paragraph surviving verbatim: + +```text +**Draft policy:** drafts stay in the discovery list in +``` + +## L6 compression: 1 finding + +**`plugins/overengineering/skills/delta/context/recurring-wiring.md:83`.** One stacked-hedge +intensifier, 6 bytes. Verified still present on 2026-08-26. + +```text +own audit will later walk, judge on carry cost, and quite possibly recommend retiring. Wire it +``` + +Drop `quite`. This is the only proposed cut in the whole sweep that is a plain markdown edit, and it +still needs the semantic-diff gate that `docs-hygiene:compress` makes mandatory. + +Two more were held at SKIP and are flagged for `write-for-humans` rather than compression, because +the shorter form needs the surrounding clause re-punctuated, which is a rewrite rather than a word +drop: `plugins/architecture/skills/improve/actions/deepening.md:56` and +`plugins/event-storming/skills/methodology/reference/big-picture-workshop.md:224`, both carrying +`in terms of`. + +## L7 write-for-agents: 13 findings + +All 13 `path:line` citations were re-verified on 2026-08-26 and every one resolves to the quoted +text exactly. + +### P3, a pointer opens on the routing verb instead of the matching term, 11 + +| # | `path:line` | Tier | Verbatim | Replacement | +|---|---|---|---|---| +| B-1 | `plugins/claude-ops/skills/audit-install-state/SKILL.md:152` | T2 | `See [reference/surfaces.md](reference/surfaces.md).` | `Per-path retention rules: see [reference/surfaces.md](reference/surfaces.md).` | +| B-2 | `plugins/claude-ops/skills/audit-install-state/SKILL.md:170` | T2 | `See [reference/name-schemes.md](reference/name-schemes.md).` | `Name schemes and their liveness meanings: see [reference/name-schemes.md](reference/name-schemes.md).` | +| B-3 | `plugins/claude-ops/skills/audit-install-state/SKILL.md:205` | T2 | `See [reference/evidence-discipline.md](reference/evidence-discipline.md).` | `Cross-review procedure: see [reference/evidence-discipline.md](reference/evidence-discipline.md).` | +| B-4 | `plugins/claude-ops/skills/audit-install-state/SKILL.md:213` | T2 | `See [reference/evidence-discipline.md](reference/evidence-discipline.md) §6.` | `Upstream-claim verification: see [reference/evidence-discipline.md](reference/evidence-discipline.md) §6.` | +| F-1 | `plugins/mutation-testing/skills/principles/SKILL.md:48` | T2 | `See [scaling-and-suppression.md](reference/scaling-and-suppression.md).` | `Scaling and suppression mechanics: see [scaling-and-suppression.md](reference/scaling-and-suppression.md).` | +| F-2 | `plugins/testing/skills/run-e2e/context/e2e.md:35` | T3 | see below | see below | +| H-1 | `plugins/discovery/skills/research/SKILL.md:159` | T2 | `See the discipline file's "Tool-ecosystem Phase 3 fallback" for the playbook.` | `Tool-ecosystem Phase 3 fallback playbook: the discipline file's "Tool-ecosystem Phase 3 fallback".` This file cites the same target seven times and front-loads the term every other time; line 159 is the single deviation | +| J-1 | `plugins/playwright/skills/playwright/reference/storage-and-auth.md:53` | T3 | `See [running-code.md](running-code.md).` | `Running arbitrary page code: see [running-code.md](running-code.md).` | +| J-2 | `plugins/playwright/skills/playwright/reference/commands.md:52` | T3 | `See [snapshots-and-refs.md](snapshots-and-refs.md) for ref system.` | `Ref system: see [snapshots-and-refs.md](snapshots-and-refs.md).` | +| J-3 | `plugins/playwright/skills/playwright/reference/commands.md:129` | T3 | see below | see below | +| I-1 | 31 pointers across 16 files under `plugins/songwriting/context/pat-pattison/` and `plugins/songwriting/skills/suno/context/` | T3 | House `See for ` pattern | **Recommended disposition: do not apply.** One house pattern applied consistently, not 31 defects. Severity S3 on all 31, and this sub-tree is cross-referenced densely enough that a partial rewrite leaves two competing pointer styles in one reading path. Apply all 31 in one edit or none | + +B-1 through B-4 also fail L2's blind-pointer shape. If L2's fuller rewrite is applied, drop these +four rather than applying both. + +**F-2**, held out of the table because it carries nested code spans. Verbatim at +`plugins/testing/skills/run-e2e/context/e2e.md:35`: + +```text +**See `/playwright:playwright`** (when the playwright plugin is installed) for CLI mechanics — commands, sessions, snapshots, storage, tracing, network mocking, Windows quirks. This skill (`/testing:run-e2e`) owns the broader orchestrator + API + UI story. +``` + +The pointer covers its branches (condition, payload, complement), so it passes the branch predicate. +It fails only on the bolded leading token being the routing verb. Moving the emphasis fixes it +without touching the content, and removes an em dash as a side effect: + +```text +**CLI mechanics** (commands, sessions, snapshots, storage, tracing, network mocking, Windows quirks): see `/playwright:playwright`, when the playwright plugin is installed. This skill (`/testing:run-e2e`) owns the broader orchestrator + API + UI story. +``` + +**J-3**, same reason. Verbatim at +`plugins/playwright/skills/playwright/reference/commands.md:129`: + +```text +See [sessions.md](sessions.md) for `-s=` session isolation; [windows-quirks.md](windows-quirks.md) for `--headed` on Windows. +``` + +Both halves state their payload, so only the opening routing verb fails. Replacement: + +```text +Session isolation with `-s=`: see [sessions.md](sessions.md). `--headed` on Windows: see [windows-quirks.md](windows-quirks.md). +``` + +### P7, a step defers a fact it needs to an unnamed location, 2 + +**B-5. `plugins/claude-config/skills/audit/SKILL.md:90`** (T2, S2). Lines 88 to 90: + +```text +Record the installed Claude Code version (`claude --version`). Phase 3.2 compares issue-fix versions +against it. Then run `bash "${CLAUDE_PLUGIN_ROOT}/skills/audit/scripts/check-structure.sh"` +before the table below. +``` + +"The table below" is 35 lines and two subsections away, with other tables above and below it, so it +does not resolve during execution. Replacement for line 90: + +```text +before filling the `1.2 Structure inventory` table. +``` + +**D-1. `plugins/implementation/skills/implement/SKILL.md:71`** (T2, S1). The step commits; the rules +governing how it commits are in `### Commit discipline` at line 83, below an intervening section: + +```text +4. **Commit checkpoint**. Commit after tests pass. Each commit should represent a green state. See below for commit discipline +``` + +Replacement: + +```text +4. **Commit checkpoint**. Commit after tests pass. Each commit represents a green state; message shape and granularity are in "Commit discipline" below +``` + +The preferred alternative, if the L2 split of this file happens first, is to move the +`### Commit discipline` body up under step 4, which satisfies co-location outright. Pick one. + +### One doctrine edit the lane could not make itself + +`plugins/docs-hygiene/skills/write-for-agents/SKILL.md`'s "After writing" section says: + +```text +- Resolved or coined a domain term? Invoke `/domain-driven-design:curate-language` via the + Skill tool (if that plugin is installed), never hand-write a glossary entry. +``` + +The prohibition is unqualified; the skill it routes to is not. `curate-language` scopes itself to "a +consuming project's ubiquitous-language glossary" and excludes passive lookup. Six `AGENT` files +carry a hand-written vocabulary section that the prohibition catches and the routing target would +refuse: `plugins/event-storming/skills/methodology/reference/glossary-and-tools.md:3`, +`plugins/work-items/reference/execution-shape.md:111`, `plugins/review/context/severity.md:28`, +`plugins/architecture/skills/improve/research/deepening/vocabulary.md:7`, +`plugins/planning/skills/design/SKILL.md:144`, and +`plugins/songwriting/context/pat-pattison/research/book-references.md:182`. The defect is in the +doctrine sentence, not the six files. Proposed replacement: + +```text +- Resolved or coined a term in the **consuming project's** domain? Invoke + `/domain-driven-design:curate-language` via the Skill tool (if that plugin is installed) rather + than hand-writing the entry. A skill defining its own working vocabulary is out of that skill's + scope and stays where it is. +``` + +## L8 write-for-humans: 57 findings and 6 reclassifications + +55 of the 57 sit in plugin READMEs. All 57 `path:line` citations were re-verified on 2026-08-26 and +every one resolves. The exact replacement text for each was written into the audit and is **not** +carried here; what is carried is enough to locate each finding and fix it without re-running the +scan, and the two largest classes are mechanical enough that the fix follows from the class. + +The predicates: `Am1` a parenthetical is a full grammatical unit; `Am2` no `(s)` plurals; `Am3` no +slash coordination in prose; `Am4` `only` next to the word it changes; `L1` one thought per sentence; +`M1` one document one mode; `M2` release history belongs in the changelog; `M3` one place per +recurring block; `N1` one name per thing; `A1` command with its condition first. + +### The two mechanical classes + +**`Am1`, 18 findings.** A sentence ended *inside* a parenthetical, leaving a fragment on one side of +the period. The fix is the same every time: move the sentence break outside the parenthesis. +`plugins/adhd/README.md:105` is the model of the correct form and is not a finding. Every one of the +18 is in a plugin README and none is anywhere else in the corpus, which is the shape an automated +em-dash substitution produces when "end the sentence" is applied between parentheses. The worst is +`plugins/autonomy/README.md:183`: + +```text +Setup writes tracked config to `.claude/autonomy/` in the consuming repo (concern-named. The +config outlives any plugin restructure). +``` + +**`M3`, 16 findings.** The marker-delimited `### Options reference` block generated by +`scripts/sync-plugin-options-docs.py` sits under 13 different `##` headings across 34 READMEs. Its +*placement* is authored even though its content is generated. The remediation is mechanical and +identical in all 16: move the block from ` @@ -162,7 +137,7 @@ Three supported routes, in the order most people want them: for a non-sensitive option at `user` scope, by writing a non-default value to an installed plugin and restoring it. The short-circuit message is about the install, not the config write. That has not been verified for a `sensitive` option or for - `project`/`local` scope. Do **not** `claude plugin uninstall` in order to + `project`/`local` scope. Do **not** `claude plugin uninstall` to reconfigure: uninstalling drops this plugin's whole stored `pluginConfigs` entry, resetting every option in the table above to its default. `-s` defaults to `user`, so pass the scope `claude plugin list` reports for this plugin. @@ -206,6 +181,31 @@ hands a configured value to a hook process; the value comes from the routes abov +## Filing a report + +`--file` persists the report; filing is an explicit, separate hand-off. In a GitHub +repository with the `gh` CLI available: + +```shell +gh issue create --type Bug --body-file +``` + +Let `gh` prompt for the title interactively. `--type Bug` sets the native GitHub Issue +Type (org repos; omit on repos without native Issue Types, adding a `type: bug` label instead). If filing non-interactively, never paste +the reporter's title text into the command string. Write it to a file and pass +`--title "$(cat )"`: the substitution result is a quoted argument value and +is not re-parsed, so backticks or `$( )` in reporter text cannot execute. + +If a work-item tracker MCP tool is available, the skill can hand off to that instead. +Otherwise the emitted report is the deliverable. Copy it into your tracker. + +## Install + +```shell +/plugin marketplace add melodic-software/claude-code-plugins +/plugin install bugs@ +``` + ## License MIT (SPDX-License-Identifier: MIT). diff --git a/plugins/claude-config/.claude-plugin/plugin.json b/plugins/claude-config/.claude-plugin/plugin.json index 01e8e1651..3e5a97383 100644 --- a/plugins/claude-config/.claude-plugin/plugin.json +++ b/plugins/claude-config/.claude-plugin/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "claude-config", - "version": "0.40.4", - "description": "Nine configuration-health skills (plus setup) for a repo's Claude Code configuration: audit (settings.json / .mcp.json / hooks / plugins / permissions drift), audit-automation-gaps (evidence-gated verdicts on automation gaps), audit-permission-grants (allow-rule / allowed-tools grants for auto-mode durability and portability), audit-permission-state (the permission rules actually in effect — every settings scope merged with per-rule provenance, what auto mode drops on entry, config written where nothing reads it, and which managed intents are enforced versus loosenable), draft-auto-mode-rules (interview and draft a paste-ready autoMode classifier block; prints only, never writes), audit-instructions (locally-owned instruction surfaces vs current model capability — proposes removals/rewrites of instructions the model no longer needs, and detects cross-surface instruction conflicts), audit-prompting-postures (the additive lane — posture guidance the prompting guide says a component's purpose needs but the component does not carry), audit-pass (one coordinated, ordered, resumable pass over a named target — three-scope inventory, run-time-derived exclusion set, stable finding identity, suppression memory, resume, one human gate — delegating every check to the plugin that owns it), and unhobble (the empirical bare-baseline experiment: reversibly strip a repo's standing instructions, log real stumbles against the current model, re-add only what evidence earns).", + "version": "0.40.6", + "description": "Nine configuration-health skills (plus setup) for a repo's Claude Code configuration: audit (settings.json / .mcp.json / hooks / plugins / permissions drift), audit-automation-gaps (evidence-gated verdicts on automation gaps), audit-permission-grants (allow-rule / allowed-tools grants for auto-mode durability and portability), audit-permission-state (the permission rules actually in effect \u2014 every settings scope merged with per-rule provenance, what auto mode drops on entry, config written where nothing reads it, and which managed intents are enforced versus loosenable), draft-auto-mode-rules (interview and draft a paste-ready autoMode classifier block; prints only, never writes), audit-instructions (locally-owned instruction surfaces vs current model capability \u2014 proposes removals/rewrites of instructions the model no longer needs, and detects cross-surface instruction conflicts), audit-prompting-postures (the additive lane \u2014 posture guidance the prompting guide says a component's purpose needs but the component does not carry), audit-pass (one coordinated, ordered, resumable pass over a named target \u2014 three-scope inventory, run-time-derived exclusion set, stable finding identity, suppression memory, resume, one human gate \u2014 delegating every check to the plugin that owns it), and unhobble (the empirical bare-baseline experiment: reversibly strip a repo's standing instructions, log real stumbles against the current model, re-add only what evidence earns).", "author": { "name": "Melodic Software", "email": "info@melodicsoftware.com" diff --git a/plugins/claude-config/CHANGELOG.md b/plugins/claude-config/CHANGELOG.md index 1e05085bf..3e28a7471 100644 --- a/plugins/claude-config/CHANGELOG.md +++ b/plugins/claude-config/CHANGELOG.md @@ -3,6 +3,29 @@ All notable changes to the `claude-config` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.40.6] + +### Changed + +- **Two README run-ons became a list and a table.** The `audit-pass` run-semantics sentence (80 + words, 7 clause interrupters) is now a bulleted list, and `unhobble`'s four-phase description is + now a four-row table. Every fact survives; the reader no longer backtracks to parse them. + Docs-hygiene sweep, L8-write-for-humans. +- **`audit` phase 1.1 names the table it feeds.** "Before the table below" pointed thirty-five lines + and two subsections away, past other tables above and below it. It now names `1.2 Structure + inventory`. Docs-hygiene sweep, L7-write-for-agents. + +## [0.40.5] + +### Changed + +- **`audit-instructions` and `audit-pass` split against the progressive-disclosure audit.** + `audit-instructions` moved its Phase A surface-discovery procedure to + `context/phase-a-inventory.md`, which owns the discovery order, the per-surface record fields + Phase B and B2 key off, and the exclusions. `audit-pass` moved its argument reference to + `reference/arguments.md`. Both pointers say when to read the spoke, not just that it exists. + No content was dropped in either move. + ## [0.40.4] ### Changed diff --git a/plugins/claude-config/README.md b/plugins/claude-config/README.md index 1be1e3ac9..a8f950eb5 100644 --- a/plugins/claude-config/README.md +++ b/plugins/claude-config/README.md @@ -123,13 +123,19 @@ pass alone, so a scheduled hygiene routine can compose it on its own token budge ### audit-pass Coordinates one pass rather than adding checks: every check is delegated to the plugin that owns it -through a presence-gated invocation with a documented fallback. It supplies the run semantics that -invoking those skills by hand does not: a three-scope inventory taken before any check runs (managed -policy read-only, user scope routed as recommendations, project scope), an exclusion set derived at -run time from the target's own shared-source registry, the `vendor/` rule, `git worktree list`, and -the pass's own artifacts; content-derived finding identity that survives an unrelated edit above it; -a `finding_id`-keyed suppression record with staleness reporting; per-lane persistence with resume; -and one human gate per run. Findings report in three tiers: derived (exact equality across runs), +through a presence-gated invocation with a documented fallback. It supplies run semantics that +invoking those skills by hand does not: + +- A three-scope inventory taken before any check runs: managed policy read-only, user scope routed + as recommendations, and project scope. +- An exclusion set derived at run time from the target's own shared-source registry, the `vendor/` + rule, `git worktree list`, and the pass's own artifacts. +- Content-derived finding identity that survives an unrelated edit above it. +- A `finding_id`-keyed suppression record with staleness reporting. +- Per-lane persistence with resume. +- One human gate per run. + +Findings report in three tiers: derived (exact equality across runs), judged (a stability tolerance whose violation fails the run's self-check), delegated. `/doctor` is an operator handoff, never a dispatch, because it is interactive. @@ -156,12 +162,16 @@ as the project. The empirical counterpart to `audit-instructions`: instead of judging instruction *text* against doctrine, it measures the *model* against the repo with the instructions gone. Four resumable -phases: **snapshot** (inventory the live project surfaces on a dedicated experiment branch, classify -hooks policy-vs-behavioral), **bare** (reversibly strip the behavioral tier: tracked files via git, -settings entries via manifest-recorded backups; policy gates and managed settings are never -touched), **observe** (work normally in fresh sessions, logging real stumbles to a ledger), and -**readd** (restore only instructions with at least two same-cause ledger rows, each restore citing -its evidence; everything else stays deleted, with git history as the archive). The canonical trigger +phases: + +| Phase | What it does | +|---|---| +| `snapshot` | Inventories the live project surfaces on a dedicated experiment branch and classifies each hook as policy or behavioral | +| `bare` | Reversibly strips the behavioral tier: tracked files via git, settings entries via manifest-recorded backups. Policy gates and managed settings are never touched | +| `observe` | You work normally in fresh sessions, logging real stumbles to a ledger | +| `readd` | Restores only instructions with at least two same-cause ledger rows, each restore citing its evidence. Everything else stays deleted, with git history as the archive | + +The canonical trigger is a frontier model release. Instructions written for the previous generation are the experiment's subject. Human-gated at every mutation; state persists under `${CLAUDE_PLUGIN_DATA}` for resume. diff --git a/plugins/claude-config/skills/audit-instructions/SKILL.md b/plugins/claude-config/skills/audit-instructions/SKILL.md index fb08e2b5e..72ad86fcc 100644 --- a/plugins/claude-config/skills/audit-instructions/SKILL.md +++ b/plugins/claude-config/skills/audit-instructions/SKILL.md @@ -149,161 +149,10 @@ proposal for a human-gated relay, not an applied edit; see ## Phase A: Inventory -Enumerate the locally-owned instruction surfaces in scope. All paths below are current per the -official memory and `.claude`-directory docs (cited in the report's Sources line): - -- User: resolve the root as `${CLAUDE_CONFIG_DIR:-~/.claude}` (setting `CLAUDE_CONFIG_DIR` - relocates the whole `~/.claude` tree, so never hardcode `~/.claude`), then: `CLAUDE.md`, - `rules/`, `skills/`, `agents/`, `output-styles/` under that root. -- Project: `./CLAUDE.md` or `./.claude/CLAUDE.md`, `./CLAUDE.local.md`, and every nested - `CLAUDE.md` / `CLAUDE.local.md` in subdirectories of the project tree (Claude loads these on - demand when it reads files in those directories, so walk the tree and do not stop at the root); - `.claude/rules/`, `.claude/skills/`, `.claude/agents/`, `.claude/output-styles/`. -- **Hook instruction text** configured in the project or user `settings.json`, **and in - `.claude/settings.local.json`**, since local settings are a supported hook-configuration scope and a - hook configured there gates the session as much as one configured anywhere else, **and declared - in the `hooks:` frontmatter of the user- and project-scope skills and agents listed above**. - Frontmatter is a supported hook location, live "while the component is active", so a hook in a - locally owned `.claude/skills/**/SKILL.md` or `.claude/agents/*.md` is exactly as editable as the - body it rides on and belongs in this set, not the read-only tier below, whose counterpart - item covers the plugin cache and whose components no proposal may touch. Anchor a frontmatter hook at - its own component file and frontmatter line rather than at a settings file. A subagent's - frontmatter `Stop` hook is registered as `SubagentStop`, so resolve the effective event before - pairing. Two kinds, and the discriminator is **whether the handler's output reaches this session's - context, never the handler's `type`** - ([reference/conflict-criteria.md](reference/conflict-criteria.md) owns the distinction and its - citations): - - **Prompt-type hook text**: extract the prompt text. **What is compared is the gate, not the - prose:** it goes to a separate evaluator model, never into this session's context, so it enters - the comparison set as the act it blocks under its event and `matcher`. An `agent` handler is - treated the same way. - - **Context-injecting handler output**: a handler that prints to stdout on `SessionStart`, - `UserPromptSubmit`, or `UserPromptExpansion`, or returns `hookSpecificOutput.additionalContext` - on a main-session event that accepts it, puts that text in this session's context window. It is - live instruction text and enters the comparison set as text. `command` is not an exclusion: - `mcp_tool` shares the stdout channel and `http` the JSON one. Two bounds the criteria file - states and cites: `SubagentStart` / `SubagentStop` `additionalContext` lands in **that - subagent's** context, not this session's; and type decides registrability, so resolve the - event×type pair before admitting a surface (`SessionStart` takes only `command` and `mcp_tool`). - Where the output is not literal in the config, as with a handler that runs a script, record the - surface with the emitting handler's event and `matcher` and mark the text `text-unresolved` - rather than inventing it; a run inside the session it describes can read what was injected. - - Never carry a command line, token, or other secret-bearing value out of a settings file or a - component's frontmatter into the report. Extract only the injected text, under the same - no-secrets handling for both kinds and both locations. - -**The tree does not decide what is live.** Before the inventory is handed to any lane, resolve the -session's effective liveness controls: the launch directory, the merged `claudeMdExcludes`, -`--setting-sources`, the additional-directory inputs, **and effective hook enablement**. Then drop -what they exclude and add the memory files they contribute. A walk of the project tree alone both -invents surfaces that are dead in this session and misses live ones that are not in the tree at all. -The controls, their official sources, and the `liveness-unresolved` marking for values an -out-of-session inventory cannot read are in -[reference/conflict-criteria.md](reference/conflict-criteria.md), which owns the gate; name the -resolved controls in the report's tier-transparency line. - -**Hook enablement is a liveness control, and configuration alone does not establish it.** -`disableAllHooks` turns off hooks without removing them, so an enabled plugin's handler, or one wired -in project settings, sits on disk unable to run, and a gate that cannot run this session constrains -nothing to compare against. Its reach is all hooks with exactly one carve-out, and that carve-out is -what makes this a per-scope resolution rather than a per-file one: set in user, project, or local -settings it cannot disable **managed** hooks, so managed hook text stays live against it and must not -be dropped with the rest; only a managed-level `disableAllHooks` reaches those too. The mirror -control, `allowManagedHooksOnly`, cuts the other way and blocks user, project, and plugin hooks, -exempting plugins force-enabled in managed `enabledPlugins`. Omit every hook surface they disable, -both kinds, prompt-type and context-injecting alike, since neither reaches this session when the -handler never fires, and report both resolved values with the other controls. - -Exclude from the **editable** set, and hold for the routing subsection: auto-memory -(`projects//memory/` under the resolved user root, owned by `claude-memory`), installed -plugin-cache content, -and any managed materialization per the Scope boundary. Record each surface found and each surface -skipped, so the report's tier-transparency line can name both. - -Some surfaces are inventoried **read-only** rather than excluded outright, because a later phase has -to compare against them even though no proposed edit may ever touch them. Read-only inventory changes -nothing about ownership: these surfaces still produce no proposal of their own, and a finding -involving one still carries the no-change representation and its routing recommendation. - -- **Auto memory, when it is on**: the `MEMORY.md` entrypoint at the effective auto-memory location - (the highest-precedence scope that sets `autoMemoryDirectory`, otherwise - `projects//memory/` under the **resolved** user root above, never a hardcoded - `~/.claude`, since `CLAUDE_CONFIG_DIR` moves `projects/` with the rest of the tree and a hardcoded - default misses the live `MEMORY.md` and compares against a store the session no longer - writes). **Resolve the effective enabled state first, by - precedence rather than by any single scope's value.** `CLAUDE_CODE_DISABLE_AUTO_MEMORY` is authoritative - wherever it is set (`=1` off, `=0` on, even against `autoMemoryEnabled: false`); with the variable - unset, apply settings precedence (managed > local > project > user) to `autoMemoryEnabled`, which - defaults to on. Reading a lower-scope `false` as decisive would drop a `MEMORY.md` a - higher-precedence scope re-enabled, and inventorying unconditionally would pair live instructions - against a file left on disk after auto memory was turned off, the same defect as reading a - disabled plugin's cache. `/claude-memory:stateless` owns this resolver; its `status` action reports - the effective state, including a disagreement between the variable and the setting. When auto - memory is on it loads into every session, and - [reference/conflict-criteria.md](reference/conflict-criteria.md) assigns every pair involving it to - I15 precisely because `claude-memory`'s C6 does not read it, so excluding it outright would leave - a `MEMORY.md`-versus-`CLAUDE.md` contradiction audited by neither skill. Only the content that - actually loads is compared (the first 200 lines or 25KB); topic files beside it are read on demand - and are not resident. Ownership is unchanged: `claude-memory` still owns auto memory, and a finding - here routes there rather than editing it. -- **Each enabled agent's own memory, under that same gate**: an agent definition carrying a - `memory` field gets its **own** memory directory, separate from the main conversation's and named - per agent, and that subagent reads and writes its own `MEMORY.md` there. The field's value is the - scope, and each scope has its own location: `user` → `agent-memory//` under the - **resolved** user root above (never a hardcoded `~/.claude`, for the reason the entry above gives), - `project` → `.claude/agent-memory//`, `local` → - `.claude/agent-memory-local//`. - [reference/conflict-criteria.md](reference/conflict-criteria.md) keeps an agent-definition-versus- - its-own-memory contradiction in scope precisely because those two *do* co-reside in that subagent, - so this inventory has to reach it: enumerate that `MEMORY.md` for every inventoried agent whose - definition enables the field, under the same loaded-portion bound. The gate is the effective state - resolved just above: subagent memory is part of auto memory, so with auto memory off the `memory` - field has no effect and the subagent launches without the memory instructions or the memory tool - access; an agent memory left on disk after the switch flipped is not inventoried. - Read-only and `claude-memory`-owned exactly as the main entrypoint is. -- **Org-managed policy**: the managed-policy `CLAUDE.md`, any `claudeMd` value in managed settings, - and hook instruction text configured in managed settings, of **both** kinds above. All three are - live instruction text, and a managed hook contradicting a project skill is exactly the conflict I15 - explicitly owns; that comparison is impossible if the text is never read. Extract managed hook text - under the same two-kind, no-secrets handling as the other settings scopes. -- **Upstream-owned instruction text that is nonetheless live**: skill bodies and agent definitions - from the cache of an **enabled** plugin, hook instruction text of both kinds in an enabled - plugin's `hooks/hooks.json` (a plugin is a supported hook location, so that text is as live as a - settings-configured hook, and a plugin `SessionStart` handler injecting a standing behavioral - block is the case that motivated the two-kind split), hooks declared in the frontmatter of an - active skill or agent **from that cache** (a supported location, live "while the component is - active"; the user- and project-scope counterparts are locally owned and are inventoried in the - editable set above, not here), **the active - output style when a plugin supplies it**, and any managed materialization. The output-style case - is easy to miss because the user- and project-scope scans cannot reach the plugin cache: plugins - ship styles in an - `output-styles/` directory, and a plugin style with `force-for-plugin` applies "automatically - whenever the plugin is enabled, without requiring users to select it", overriding the user's - `outputStyle` setting ([output-styles](https://code.claude.com/docs/en/output-styles)). Resolve - which style is actually active, a `force-for-plugin` style from the enabled set first, else the - `outputStyle` value, which may itself name a plugin-supplied style, and inventory that one. Only - the active style is resident, so the others stay out of the corpus. Enablement is - the same gate for every plugin-sourced surface here: a disabled plugin's cache stays on disk while - none of its components load, including a `force-for-plugin` style, which applies only while its - plugin is enabled, so resolve effective `enabledPlugins` across settings scopes first and - inventory only the - plugins that resolve enabled. A cached body from a disabled plugin would put text Claude cannot - load into the comparison corpus. Enablement alone is not enough to pick a directory: the cache can - hold several versions of one plugin, and a plugin may be installed at more than one scope, so - resolve the install record that is actually selected for this project and read **only** that - version's path. An unselected or superseded cache directory is as unloadable as a disabled - plugin's, and reading it would manufacture conflict and shadowing findings from text no session - sees. An invoked plugin skill's - instructions are in context alongside the project's own, so they can hold one side of a conflict. - They are read for comparison only, prompt text only and no secret-bearing values: the existing - exclusion from the editable set and the upstream-routing behavior are unchanged, so a finding here - routes to the owning repository's tracker and proposes no in-place edit. -- **Every I15 counterpart outside the requested scope.** A scope argument narrows which surfaces may - *produce* findings, not which are read: a conflict is a relation between two surfaces, so a run - scoped to `skills` still inventories `CLAUDE.md`, rules, agents, hooks, and output styles as - comparison counterparts. Findings still name both sides; the filter decides which side the run is - auditing, never that the counterpart goes unread. +Enumerate every locally-owned instruction surface, then hand the per-surface list to Phase B. +Read [context/phase-a-inventory.md](context/phase-a-inventory.md) before starting Phase A: it +owns the surface discovery order, the per-surface record fields Phase B and Phase B2 both key +off, and the exclusions. Phase B cannot run against a record set built any other way. ## Phase B: Per-surface lanes diff --git a/plugins/claude-config/skills/audit-instructions/context/phase-a-inventory.md b/plugins/claude-config/skills/audit-instructions/context/phase-a-inventory.md new file mode 100644 index 000000000..ba21720ac --- /dev/null +++ b/plugins/claude-config/skills/audit-instructions/context/phase-a-inventory.md @@ -0,0 +1,162 @@ +# Phase A: Inventory + +The surface-discovery layer of `/claude-config:audit-instructions` +([`../SKILL.md`](../SKILL.md)). Phase A enumerates every locally-owned instruction surface and +records one entry per surface; Phase B and Phase B2 both key off those records and cannot run +against a record set built any other way. + +Enumerate the locally-owned instruction surfaces in scope. All paths below are current per the +official memory and `.claude`-directory docs (cited in the report's Sources line): + +- User: resolve the root as `${CLAUDE_CONFIG_DIR:-~/.claude}` (setting `CLAUDE_CONFIG_DIR` + relocates the whole `~/.claude` tree, so never hardcode `~/.claude`), then: `CLAUDE.md`, + `rules/`, `skills/`, `agents/`, `output-styles/` under that root. +- Project: `./CLAUDE.md` or `./.claude/CLAUDE.md`, `./CLAUDE.local.md`, and every nested + `CLAUDE.md` / `CLAUDE.local.md` in subdirectories of the project tree (Claude loads these on + demand when it reads files in those directories, so walk the tree and do not stop at the root); + `.claude/rules/`, `.claude/skills/`, `.claude/agents/`, `.claude/output-styles/`. +- **Hook instruction text** configured in the project or user `settings.json`, **and in + `.claude/settings.local.json`**, since local settings are a supported hook-configuration scope and a + hook configured there gates the session as much as one configured anywhere else, **and declared + in the `hooks:` frontmatter of the user- and project-scope skills and agents listed above**. + Frontmatter is a supported hook location, live "while the component is active", so a hook in a + locally owned `.claude/skills/**/SKILL.md` or `.claude/agents/*.md` is exactly as editable as the + body it rides on and belongs in this set, not the read-only tier below, whose counterpart + item covers the plugin cache and whose components no proposal may touch. Anchor a frontmatter hook at + its own component file and frontmatter line rather than at a settings file. A subagent's + frontmatter `Stop` hook is registered as `SubagentStop`, so resolve the effective event before + pairing. Two kinds, and the discriminator is **whether the handler's output reaches this session's + context, never the handler's `type`** + ([reference/conflict-criteria.md](../reference/conflict-criteria.md) owns the distinction and its + citations): + - **Prompt-type hook text**: extract the prompt text. **What is compared is the gate, not the + prose:** it goes to a separate evaluator model, never into this session's context, so it enters + the comparison set as the act it blocks under its event and `matcher`. An `agent` handler is + treated the same way. + - **Context-injecting handler output**: a handler that prints to stdout on `SessionStart`, + `UserPromptSubmit`, or `UserPromptExpansion`, or returns `hookSpecificOutput.additionalContext` + on a main-session event that accepts it, puts that text in this session's context window. It is + live instruction text and enters the comparison set as text. `command` is not an exclusion: + `mcp_tool` shares the stdout channel and `http` the JSON one. Two bounds the criteria file + states and cites: `SubagentStart` / `SubagentStop` `additionalContext` lands in **that + subagent's** context, not this session's; and type decides registrability, so resolve the + event×type pair before admitting a surface (`SessionStart` takes only `command` and `mcp_tool`). + Where the output is not literal in the config, as with a handler that runs a script, record the + surface with the emitting handler's event and `matcher` and mark the text `text-unresolved` + rather than inventing it; a run inside the session it describes can read what was injected. + + Never carry a command line, token, or other secret-bearing value out of a settings file or a + component's frontmatter into the report. Extract only the injected text, under the same + no-secrets handling for both kinds and both locations. + +**The tree does not decide what is live.** Before the inventory is handed to any lane, resolve the +session's effective liveness controls: the launch directory, the merged `claudeMdExcludes`, +`--setting-sources`, the additional-directory inputs, **and effective hook enablement**. Then drop +what they exclude and add the memory files they contribute. A walk of the project tree alone both +invents surfaces that are dead in this session and misses live ones that are not in the tree at all. +The controls, their official sources, and the `liveness-unresolved` marking for values an +out-of-session inventory cannot read are in +[reference/conflict-criteria.md](../reference/conflict-criteria.md), which owns the gate; name the +resolved controls in the report's tier-transparency line. + +**Hook enablement is a liveness control, and configuration alone does not establish it.** +`disableAllHooks` turns off hooks without removing them, so an enabled plugin's handler, or one wired +in project settings, sits on disk unable to run, and a gate that cannot run this session constrains +nothing to compare against. Its reach is all hooks with exactly one carve-out, and that carve-out is +what makes this a per-scope resolution rather than a per-file one: set in user, project, or local +settings it cannot disable **managed** hooks, so managed hook text stays live against it and must not +be dropped with the rest; only a managed-level `disableAllHooks` reaches those too. The mirror +control, `allowManagedHooksOnly`, cuts the other way and blocks user, project, and plugin hooks, +exempting plugins force-enabled in managed `enabledPlugins`. Omit every hook surface they disable, +both kinds, prompt-type and context-injecting alike, since neither reaches this session when the +handler never fires, and report both resolved values with the other controls. + +Exclude from the **editable** set, and hold for the routing subsection: auto-memory +(`projects//memory/` under the resolved user root, owned by `claude-memory`), installed +plugin-cache content, +and any managed materialization per the Scope boundary. Record each surface found and each surface +skipped, so the report's tier-transparency line can name both. + +Some surfaces are inventoried **read-only** rather than excluded outright, because a later phase has +to compare against them even though no proposed edit may ever touch them. Read-only inventory changes +nothing about ownership: these surfaces still produce no proposal of their own, and a finding +involving one still carries the no-change representation and its routing recommendation. + +- **Auto memory, when it is on**: the `MEMORY.md` entrypoint at the effective auto-memory location + (the highest-precedence scope that sets `autoMemoryDirectory`, otherwise + `projects//memory/` under the **resolved** user root above, never a hardcoded + `~/.claude`, since `CLAUDE_CONFIG_DIR` moves `projects/` with the rest of the tree and a hardcoded + default misses the live `MEMORY.md` and compares against a store the session no longer + writes). **Resolve the effective enabled state first, by + precedence rather than by any single scope's value.** `CLAUDE_CODE_DISABLE_AUTO_MEMORY` is authoritative + wherever it is set (`=1` off, `=0` on, even against `autoMemoryEnabled: false`); with the variable + unset, apply settings precedence (managed > local > project > user) to `autoMemoryEnabled`, which + defaults to on. Reading a lower-scope `false` as decisive would drop a `MEMORY.md` a + higher-precedence scope re-enabled, and inventorying unconditionally would pair live instructions + against a file left on disk after auto memory was turned off, the same defect as reading a + disabled plugin's cache. `/claude-memory:stateless` owns this resolver; its `status` action reports + the effective state, including a disagreement between the variable and the setting. When auto + memory is on it loads into every session, and + [reference/conflict-criteria.md](../reference/conflict-criteria.md) assigns every pair involving it to + I15 precisely because `claude-memory`'s C6 does not read it, so excluding it outright would leave + a `MEMORY.md`-versus-`CLAUDE.md` contradiction audited by neither skill. Only the content that + actually loads is compared (the first 200 lines or 25KB); topic files beside it are read on demand + and are not resident. Ownership is unchanged: `claude-memory` still owns auto memory, and a finding + here routes there rather than editing it. +- **Each enabled agent's own memory, under that same gate**: an agent definition carrying a + `memory` field gets its **own** memory directory, separate from the main conversation's and named + per agent, and that subagent reads and writes its own `MEMORY.md` there. The field's value is the + scope, and each scope has its own location: `user` → `agent-memory//` under the + **resolved** user root above (never a hardcoded `~/.claude`, for the reason the entry above gives), + `project` → `.claude/agent-memory//`, `local` → + `.claude/agent-memory-local//`. + [reference/conflict-criteria.md](../reference/conflict-criteria.md) keeps an agent-definition-versus- + its-own-memory contradiction in scope precisely because those two *do* co-reside in that subagent, + so this inventory has to reach it: enumerate that `MEMORY.md` for every inventoried agent whose + definition enables the field, under the same loaded-portion bound. The gate is the effective state + resolved just above: subagent memory is part of auto memory, so with auto memory off the `memory` + field has no effect and the subagent launches without the memory instructions or the memory tool + access; an agent memory left on disk after the switch flipped is not inventoried. + Read-only and `claude-memory`-owned exactly as the main entrypoint is. +- **Org-managed policy**: the managed-policy `CLAUDE.md`, any `claudeMd` value in managed settings, + and hook instruction text configured in managed settings, of **both** kinds above. All three are + live instruction text, and a managed hook contradicting a project skill is exactly the conflict I15 + explicitly owns; that comparison is impossible if the text is never read. Extract managed hook text + under the same two-kind, no-secrets handling as the other settings scopes. +- **Upstream-owned instruction text that is nonetheless live**: skill bodies and agent definitions + from the cache of an **enabled** plugin, hook instruction text of both kinds in an enabled + plugin's `hooks/hooks.json` (a plugin is a supported hook location, so that text is as live as a + settings-configured hook, and a plugin `SessionStart` handler injecting a standing behavioral + block is the case that motivated the two-kind split), hooks declared in the frontmatter of an + active skill or agent **from that cache** (a supported location, live "while the component is + active"; the user- and project-scope counterparts are locally owned and are inventoried in the + editable set above, not here), **the active + output style when a plugin supplies it**, and any managed materialization. The output-style case + is easy to miss because the user- and project-scope scans cannot reach the plugin cache: plugins + ship styles in an + `output-styles/` directory, and a plugin style with `force-for-plugin` applies "automatically + whenever the plugin is enabled, without requiring users to select it", overriding the user's + `outputStyle` setting ([output-styles](https://code.claude.com/docs/en/output-styles)). Resolve + which style is actually active, a `force-for-plugin` style from the enabled set first, else the + `outputStyle` value, which may itself name a plugin-supplied style, and inventory that one. Only + the active style is resident, so the others stay out of the corpus. Enablement is + the same gate for every plugin-sourced surface here: a disabled plugin's cache stays on disk while + none of its components load, including a `force-for-plugin` style, which applies only while its + plugin is enabled, so resolve effective `enabledPlugins` across settings scopes first and + inventory only the + plugins that resolve enabled. A cached body from a disabled plugin would put text Claude cannot + load into the comparison corpus. Enablement alone is not enough to pick a directory: the cache can + hold several versions of one plugin, and a plugin may be installed at more than one scope, so + resolve the install record that is actually selected for this project and read **only** that + version's path. An unselected or superseded cache directory is as unloadable as a disabled + plugin's, and reading it would manufacture conflict and shadowing findings from text no session + sees. An invoked plugin skill's + instructions are in context alongside the project's own, so they can hold one side of a conflict. + They are read for comparison only, prompt text only and no secret-bearing values: the existing + exclusion from the editable set and the upstream-routing behavior are unchanged, so a finding here + routes to the owning repository's tracker and proposes no in-place edit. +- **Every I15 counterpart outside the requested scope.** A scope argument narrows which surfaces may + *produce* findings, not which are read: a conflict is a relation between two surfaces, so a run + scoped to `skills` still inventories `CLAUDE.md`, rules, agents, hooks, and output styles as + comparison counterparts. Findings still name both sides; the filter decides which side the run is + auditing, never that the counterpart goes unread. diff --git a/plugins/claude-config/skills/audit-pass/SKILL.md b/plugins/claude-config/skills/audit-pass/SKILL.md index df122f5ce..036a37634 100644 --- a/plugins/claude-config/skills/audit-pass/SKILL.md +++ b/plugins/claude-config/skills/audit-pass/SKILL.md @@ -51,81 +51,19 @@ them. ## Arguments -Parse `$ARGUMENTS`: - -- **`target`**: the git repository to audit. Default: the project root Claude Code resolved for this - session; where no such root is available, `git rev-parse --show-toplevel`. Never the working - directory, since a run launched from a subdirectory must key and scan identically to one launched from - the root. - - **Do not express this as a condition over `${CLAUDE_PROJECT_DIR}` "when set".** That placeholder is - substituted inline in skill content before this file reaches you, so the literal token is never - visible and the test is not yours to make. You would be deciding "is it set?" about a value that - has already been resolved. Work from what you can observe: the resolved path, or a command you run. - The sibling `audit-prompting-postures` states this same rule where it derives its report path, and - the two skills contradicted each other on it until this was fixed. - - **`target` must resolve to the active project root, and a path that does not is refused.** The - delegated interfaces accept no target: `audit-instructions` takes a surface scope and inventories - the active project, and `claude-memory:audit` takes an action verb. This pass dispatches skills and - never reads inside one, so there is no channel through which it could tell a delegate to look - elsewhere. A run given `../other-repo` would key, lock, and report against that path while every - delegated finding came from the active project. Findings attributed to the wrong repository are - worse than a refusal, because nothing downstream can detect the mismatch. - - So the argument is validated rather than silently reinterpreted: a `target` that does not resolve - to the active project root exits non-zero, naming both paths and the reason. Auditing another - repository means opening it as the project. Lifting the restriction is a change to the - **delegated** interfaces, each of which would have to accept and honor a target root, and belongs to - those skills rather than this one. The argument itself survives because the state key, the lock, - and the report are already keyed on the resolved root. - - **The gate enforces both halves of that first sentence: the active project root, *and* a git - repository.** A `target` that is not inside a git repository is refused the same way: non-zero, - before Phase 0 does any work, naming the path and the reason, writing nothing. - - **Name the directory, not an empty string.** In the case this refusal is *for*, the default - resolution above produces nothing: with no explicit `target` and no session-resolved project root, - `git rev-parse --show-toplevel` fails outside a repository and there is no resolved root to report. - So for the diagnostic only, fall back to the current directory and name **that**. A refusal that - cannot say which path it refused is barely better than a silent one. The fallback is for the message; - it never becomes a target. - Requiring only "the active project root" let a non-git directory through into a contract with no - branch for it, and the run then went quiet in four places rather than one: - - - the scan baseline is *the target's HEAD commit and the run's state digest*, and HEAD does not - exist; - - Class 3 exclusion derives worktrees from `git worktree list`, and unlike Class 1 it is given no - fallback; - - assertion 2.1 is stated over `git status --porcelain`, so the top read-only assertion is - unevaluable; - - and, the one that is a permanent capability loss rather than a missing derivation, **only the - team layer enacts a suppression**, and the team layer is the *tracked* layer. With nothing - tracked, no suppression is ever enactable on such a target, so an operator could accept a finding - and have the acceptance silently fail to persist, forever. - - **The refusal says that cost out loud** rather than reading as an arbitrary restriction, and it names - the suppression consequence in particular. Refusing closes a target class deliberately; it is not a - side effect. The alternative, specifying all four branches, was considered and rejected, because - the last of them obliges the contract to promise a capability it can never deliver on that class. - A non-git directory is audited by opening it as a repository, or by the delegated skills directly. -- **`--fix`**: the explicit mutation override. Absent, the pass writes nothing into the target. -- **`--opinion`**: run the `OPINION`-tier checks the delegated catalogs declare default-off. -- **`--resume`**: resume the most recent incomplete run for this target's state key. -- **`--report-to `**: redirect the report into the target tree. The destination is accepted only - if it is an `audit-pass`-owned report or a new path that is **not a recognized instruction surface**; - anything else is refused non-zero, naming the file. Refused on name rather than on existence, - because `--report-to CLAUDE.md` against a repo that has none would *create* a live instruction - surface out of a JSON report and then hide it from every later scan. - - **The self-exclusion obligation is not this flag's.** It belongs to the predicate - `report_path ⊆ target_root`: **any** run whose resolved report path is contained in the target adds - that path to its own exclusion set before writing, not only for later runs, since otherwise the two - runs' derived sets could not be equal, and says so in its output. `--report-to` is one way containment arises. The - **default** path is another, because `${CLAUDE_PLUGIN_DATA}` resolves under `~` and is inside any - target at or above it. Full statement in - [reference/report-location-and-schema.md](reference/report-location-and-schema.md) §2 and - [reference/exclusion-set.md](reference/exclusion-set.md) Class 4. +Parse `$ARGUMENTS`. The names and their one-line meanings: + +| Argument | Meaning | +|---|---| +| `target` | The git repository to audit. Defaults to the active project root; a `target` resolving anywhere else is refused non-zero. | +| `--fix` | The explicit mutation override. Absent, the pass writes nothing into the target. | +| `--opinion` | Run the `OPINION`-tier checks the delegated catalogs declare default-off. | +| `--resume` | Resume the most recent incomplete run for this target's state key. | +| `--report-to ` | Redirect the report into the target tree, subject to the destination gate. | + +Flag semantics, precedence between them, and what `--fix` and `--resume` change about the phases +below are in [reference/arguments.md](reference/arguments.md). Read it when a run is invoked with +more than one flag, or with any flag whose effect on a later phase you are about to assume. ## Phase 0: Resolve, key, lock diff --git a/plugins/claude-config/skills/audit-pass/reference/arguments.md b/plugins/claude-config/skills/audit-pass/reference/arguments.md new file mode 100644 index 000000000..9f76338b8 --- /dev/null +++ b/plugins/claude-config/skills/audit-pass/reference/arguments.md @@ -0,0 +1,81 @@ +# Arguments + +Full semantics for every `audit-pass` argument, the precedence between them, and what `--fix` and +`--resume` change about the phases in [`../SKILL.md`](../SKILL.md). The hub carries the flag names +and their one-line meanings; this file carries the reasoning each refusal and default rests on. + +Parse `$ARGUMENTS`: + +- **`target`**: the git repository to audit. Default: the project root Claude Code resolved for this + session; where no such root is available, `git rev-parse --show-toplevel`. Never the working + directory, since a run launched from a subdirectory must key and scan identically to one launched from + the root. + + **Do not express this as a condition over `${CLAUDE_PROJECT_DIR}` "when set".** That placeholder is + substituted inline in skill content before this file reaches you, so the literal token is never + visible and the test is not yours to make. You would be deciding "is it set?" about a value that + has already been resolved. Work from what you can observe: the resolved path, or a command you run. + The sibling `audit-prompting-postures` states this same rule where it derives its report path, and + the two skills contradicted each other on it until this was fixed. + + **`target` must resolve to the active project root, and a path that does not is refused.** The + delegated interfaces accept no target: `audit-instructions` takes a surface scope and inventories + the active project, and `claude-memory:audit` takes an action verb. This pass dispatches skills and + never reads inside one, so there is no channel through which it could tell a delegate to look + elsewhere. A run given `../other-repo` would key, lock, and report against that path while every + delegated finding came from the active project. Findings attributed to the wrong repository are + worse than a refusal, because nothing downstream can detect the mismatch. + + So the argument is validated rather than silently reinterpreted: a `target` that does not resolve + to the active project root exits non-zero, naming both paths and the reason. Auditing another + repository means opening it as the project. Lifting the restriction is a change to the + **delegated** interfaces, each of which would have to accept and honor a target root, and belongs to + those skills rather than this one. The argument itself survives because the state key, the lock, + and the report are already keyed on the resolved root. + + **The gate enforces both halves of that first sentence: the active project root, *and* a git + repository.** A `target` that is not inside a git repository is refused the same way: non-zero, + before Phase 0 does any work, naming the path and the reason, writing nothing. + + **Name the directory, not an empty string.** In the case this refusal is *for*, the default + resolution above produces nothing: with no explicit `target` and no session-resolved project root, + `git rev-parse --show-toplevel` fails outside a repository and there is no resolved root to report. + So for the diagnostic only, fall back to the current directory and name **that**. A refusal that + cannot say which path it refused is barely better than a silent one. The fallback is for the message; + it never becomes a target. + Requiring only "the active project root" let a non-git directory through into a contract with no + branch for it, and the run then went quiet in four places rather than one: + + - the scan baseline is *the target's HEAD commit and the run's state digest*, and HEAD does not + exist; + - Class 3 exclusion derives worktrees from `git worktree list`, and unlike Class 1 it is given no + fallback; + - assertion 2.1 is stated over `git status --porcelain`, so the top read-only assertion is + unevaluable; + - and, the one that is a permanent capability loss rather than a missing derivation, **only the + team layer enacts a suppression**, and the team layer is the *tracked* layer. With nothing + tracked, no suppression is ever enactable on such a target, so an operator could accept a finding + and have the acceptance silently fail to persist, forever. + + **The refusal says that cost out loud** rather than reading as an arbitrary restriction, and it names + the suppression consequence in particular. Refusing closes a target class deliberately; it is not a + side effect. The alternative, specifying all four branches, was considered and rejected, because + the last of them obliges the contract to promise a capability it can never deliver on that class. + A non-git directory is audited by opening it as a repository, or by the delegated skills directly. +- **`--fix`**: the explicit mutation override. Absent, the pass writes nothing into the target. +- **`--opinion`**: run the `OPINION`-tier checks the delegated catalogs declare default-off. +- **`--resume`**: resume the most recent incomplete run for this target's state key. +- **`--report-to `**: redirect the report into the target tree. The destination is accepted only + if it is an `audit-pass`-owned report or a new path that is **not a recognized instruction surface**; + anything else is refused non-zero, naming the file. Refused on name rather than on existence, + because `--report-to CLAUDE.md` against a repo that has none would *create* a live instruction + surface out of a JSON report and then hide it from every later scan. + + **The self-exclusion obligation is not this flag's.** It belongs to the predicate + `report_path ⊆ target_root`: **any** run whose resolved report path is contained in the target adds + that path to its own exclusion set before writing, not only for later runs, since otherwise the two + runs' derived sets could not be equal, and says so in its output. `--report-to` is one way containment arises. The + **default** path is another, because `${CLAUDE_PLUGIN_DATA}` resolves under `~` and is inside any + target at or above it. Full statement in + [reference/report-location-and-schema.md](report-location-and-schema.md) §2 and + [reference/exclusion-set.md](exclusion-set.md) Class 4. diff --git a/plugins/claude-config/skills/audit/SKILL.md b/plugins/claude-config/skills/audit/SKILL.md index d692d14de..3d4f9c90a 100644 --- a/plugins/claude-config/skills/audit/SKILL.md +++ b/plugins/claude-config/skills/audit/SKILL.md @@ -87,7 +87,7 @@ Read all config files and validate basic structure. Record the installed Claude Code version (`claude --version`). Phase 3.2 compares issue-fix versions against it. Then run `bash "${CLAUDE_PLUGIN_ROOT}/skills/audit/scripts/check-structure.sh"` -before the table below. +before filling the `1.2 Structure inventory` table. ### 1.0 Hook inventory diff --git a/plugins/claude-ops/.claude-plugin/plugin.json b/plugins/claude-ops/.claude-plugin/plugin.json index 49e235ac1..761b3b024 100644 --- a/plugins/claude-ops/.claude-plugin/plugin.json +++ b/plugins/claude-ops/.claude-plugin/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "claude-ops", - "version": "0.38.5", - "description": "Claude Code operations toolkit. Twelve skills: audit-skill-visibility (audit whether each installed skill is actually VISIBLE to the model, and diagnose why most of a fleet never gets used — a skill is invisible when its description is dropped by Claude Code's skill-listing context budget, which drops descriptions least-invoked-first so an unused skill loses the keywords that would let it be matched, from skills genuinely not wanted, from skills the run cannot observe at all; computes whether the listing overflows from documented settings, and withholds every cold verdict the data cannot support rather than reporting absence of data as absence of use), inventory (read-only enumeration of the complete invocable surface — every built-in CLI command with aliases and hidden/gated status, every bundled skill, and every component of every installed plugin across all marketplaces; reads the shipped binary because upstream publishes no built-in command list, and carries an integrity verdict so a drifted build reports counts as floors rather than silently short totals), audit-install-state (read-only audit of the machine-scope ~/.claude installation directory and ~/.claude.json — full inventory split into an authored surface and rolled-up bulk trees, product-managed retention vs genuinely unmanaged state, filename-scheme resolution before any process-liveness check, and deliberate/mid-experiment detection; reports, never deletes), audit-performance (read-only slowness-diagnostic capture run at the moment the machine or a session feels slow: CLI version, retention-sweep health including the silent unparsable-settings pause, a timed census walk of the install tree as a sweep-cost proxy, active-session and plugin-fleet counts, a process census, and the fan-out layer, which covers a load-labelled no-op spawn baseline, every hook that will fire bucketed per-tool-call versus per-turn with its invocation shape, the configured statusline, subagent concurrency and spawn-depth ceilings against documented defaults, whether running sessions predate the settings file they are judged by, and orphan attribution by parent liveness rather than age; read against a bundled known-performance-issues reference that also records the causes tested and cleared; separates the four documented suspects of accumulated state, version regression, component bloat, and per-spawn fan-out cost, and routes remediation out; reports, never mutates, and never executes a discovered hook or statusline command), audit-native-overlap (map native Claude Code surfaces — built-in CLI commands, bundled skills, plugin-backed built-ins, session-provided skills — against the current repo's plugin skills and agents, so a custom component never silently duplicates what Claude Code itself ships; bare invocation is a read-only overlap report carrying the extraction's integrity floors and a shared-listing-budget exposure section, verdicts are human-gated in a committed store rendered into a generated registry whose every row carries an observable recheck trigger, and only an explicit apply step bakes presence-gated native references into descriptions and Boundary sections), observability (read locally captured telemetry — OTEL store, collector, hook-event JSONL, ccusage — with trend reports and store pruning), known-issues (search known Claude product GitHub bugs, check service health, maintain a persistent tracked-issue registry), changelog (ingest Claude Code changelog entries and integrate them into the current repo), plugins (bring a machine's plugin fleet current on demand — marketplace refresh, effective-scope updates including in-repo project/local installs, new-plugin install per policy, scope-divergence detection and explicit convergence), morning-brief (read-only gh-based operator morning view — queue-label counts, merge-ready PRs, parked decisions with their RECOMMENDED lines, and loop-lane telemetry freshness), lanes (start/restart/stop/status loop lanes as named background Claude Code sessions seeded from canonical prompt files, with per-lane model/effort, a repo-pull + marketplace-refresh launch step, and a consume-restarts action — an OS-schedulable reader that relaunches stopped lanes whose telemetry carries a restart_request), and a re-runnable setup action that settles where the known-issues registry lives. Plus a family of eight advisory *-audit hooks (API errors, config changes, instruction loads, permission denials, pre-compaction, skill usage, tool failures, and unsurfaced hook failures — the last also warns the user via systemMessage, since a hook that fails to launch enforces nothing and Claude Code surfaces the failure to nobody) that emit the shared hook-telemetry envelope, and a reference sink that maps envelopes into the hook-events.jsonl the observability skill reads.", + "version": "0.38.7", + "description": "Claude Code operations toolkit. Twelve skills: audit-skill-visibility (audit whether each installed skill is actually VISIBLE to the model, and diagnose why most of a fleet never gets used \u2014 a skill is invisible when its description is dropped by Claude Code's skill-listing context budget, which drops descriptions least-invoked-first so an unused skill loses the keywords that would let it be matched, from skills genuinely not wanted, from skills the run cannot observe at all; computes whether the listing overflows from documented settings, and withholds every cold verdict the data cannot support rather than reporting absence of data as absence of use), inventory (read-only enumeration of the complete invocable surface \u2014 every built-in CLI command with aliases and hidden/gated status, every bundled skill, and every component of every installed plugin across all marketplaces; reads the shipped binary because upstream publishes no built-in command list, and carries an integrity verdict so a drifted build reports counts as floors rather than silently short totals), audit-install-state (read-only audit of the machine-scope ~/.claude installation directory and ~/.claude.json \u2014 full inventory split into an authored surface and rolled-up bulk trees, product-managed retention vs genuinely unmanaged state, filename-scheme resolution before any process-liveness check, and deliberate/mid-experiment detection; reports, never deletes), audit-performance (read-only slowness-diagnostic capture run at the moment the machine or a session feels slow: CLI version, retention-sweep health including the silent unparsable-settings pause, a timed census walk of the install tree as a sweep-cost proxy, active-session and plugin-fleet counts, a process census, and the fan-out layer, which covers a load-labelled no-op spawn baseline, every hook that will fire bucketed per-tool-call versus per-turn with its invocation shape, the configured statusline, subagent concurrency and spawn-depth ceilings against documented defaults, whether running sessions predate the settings file they are judged by, and orphan attribution by parent liveness rather than age; read against a bundled known-performance-issues reference that also records the causes tested and cleared; separates the four documented suspects of accumulated state, version regression, component bloat, and per-spawn fan-out cost, and routes remediation out; reports, never mutates, and never executes a discovered hook or statusline command), audit-native-overlap (map native Claude Code surfaces \u2014 built-in CLI commands, bundled skills, plugin-backed built-ins, session-provided skills \u2014 against the current repo's plugin skills and agents, so a custom component never silently duplicates what Claude Code itself ships; bare invocation is a read-only overlap report carrying the extraction's integrity floors and a shared-listing-budget exposure section, verdicts are human-gated in a committed store rendered into a generated registry whose every row carries an observable recheck trigger, and only an explicit apply step bakes presence-gated native references into descriptions and Boundary sections), observability (read locally captured telemetry \u2014 OTEL store, collector, hook-event JSONL, ccusage \u2014 with trend reports and store pruning), known-issues (search known Claude product GitHub bugs, check service health, maintain a persistent tracked-issue registry), changelog (ingest Claude Code changelog entries and integrate them into the current repo), plugins (bring a machine's plugin fleet current on demand \u2014 marketplace refresh, effective-scope updates including in-repo project/local installs, new-plugin install per policy, scope-divergence detection and explicit convergence), morning-brief (read-only gh-based operator morning view \u2014 queue-label counts, merge-ready PRs, parked decisions with their RECOMMENDED lines, and loop-lane telemetry freshness), lanes (start/restart/stop/status loop lanes as named background Claude Code sessions seeded from canonical prompt files, with per-lane model/effort, a repo-pull + marketplace-refresh launch step, and a consume-restarts action \u2014 an OS-schedulable reader that relaunches stopped lanes whose telemetry carries a restart_request), and a re-runnable setup action that settles where the known-issues registry lives. Plus a family of eight advisory *-audit hooks (API errors, config changes, instruction loads, permission denials, pre-compaction, skill usage, tool failures, and unsurfaced hook failures \u2014 the last also warns the user via systemMessage, since a hook that fails to launch enforces nothing and Claude Code surfaces the failure to nobody) that emit the shared hook-telemetry envelope, and a reference sink that maps envelopes into the hook-events.jsonl the observability skill reads.", "author": { "name": "Melodic Software", "email": "info@melodicsoftware.com" @@ -39,7 +39,7 @@ "skill_usage_scope": { "type": "string", "title": "Skill-usage log scope", - "description": "Where the skill-usage store lives. Valid values: \"repo\" (default — project tree under the repo root, kept out of git status via a machine-local .git/info/exclude entry), \"user\" (the skill_usage_dir subpath under $HOME, one cross-repo store; rows carry a project field), \"data-dir\" (${CLAUDE_PLUGIN_DATA}/skill-usage/, plugin-owned and update-safe). The manifest schema has no enum type, so this validates in prose; any other value is treated as \"repo\" with a one-time advisory.", + "description": "Where the skill-usage store lives. Valid values: \"repo\" (default \u2014 project tree under the repo root, kept out of git status via a machine-local .git/info/exclude entry), \"user\" (the skill_usage_dir subpath under $HOME, one cross-repo store; rows carry a project field), \"data-dir\" (${CLAUDE_PLUGIN_DATA}/skill-usage/, plugin-owned and update-safe). The manifest schema has no enum type, so this validates in prose; any other value is treated as \"repo\" with a one-time advisory.", "default": "repo" }, "skill_usage_git_exclude": { @@ -51,7 +51,7 @@ "install_new": { "type": "string", "title": "New-plugin install policy for the plugins skill's sync action", - "description": "Controls what `sync` does with catalog plugins that aren't installed yet. Valid values: \"ask\" (default — offer them in one batched multi-select prompt), \"all\" (install every one automatically), \"none\" (report only, never install). The manifest schema has no enum type, so this validates in prose, not JSON Schema; any other value is treated as \"ask\".", + "description": "Controls what `sync` does with catalog plugins that aren't installed yet. Valid values: \"ask\" (default \u2014 offer them in one batched multi-select prompt), \"all\" (install every one automatically), \"none\" (report only, never install). The manifest schema has no enum type, so this validates in prose, not JSON Schema; any other value is treated as \"ask\".", "default": "ask" }, "api_error_audit_enabled": { @@ -111,7 +111,7 @@ "stdin_read_timeout": { "type": "number", "title": "Hook stdin read timeout (seconds)", - "description": "Idle bound on reading the hook payload from stdin — how long the pipe may go silent before the hook gives up and fails open", + "description": "Idle bound on reading the hook payload from stdin \u2014 how long the pipe may go silent before the hook gives up and fails open", "default": 2, "min": 1 } diff --git a/plugins/claude-ops/CHANGELOG.md b/plugins/claude-ops/CHANGELOG.md index dff925971..7d09b9084 100644 --- a/plugins/claude-ops/CHANGELOG.md +++ b/plugins/claude-ops/CHANGELOG.md @@ -3,6 +3,34 @@ All notable changes to the `claude-ops` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.38.7] + +### Changed + +- **Four `audit-install-state` pointers front-load their subject.** Each phase section closed with a + bare `See .`, so the term a reader matches on arrived only inside the filename. Each now + leads with what following the pointer gets you. Docs-hygiene sweep, L7-write-for-agents. +- **Two README parentheticals repaired, and the `audit-performance` cell is scannable.** Both + parentheticals ended a sentence inside themselves. The `audit-performance` skills-table cell named + its four suspects across a 100-word sentence; it now names them plainly and leaves the per-suspect + evidence and verdict routing to the skill body, which already documents both. Docs-hygiene sweep, + L8-write-for-humans. + +## [0.38.6] + +### Changed + +- **known-issues: `context/issue-templates.md` is a pointer, not a snapshot (docs-hygiene repo + sweep, L1-derivability).** The file carried a 2026-03-29 copy of + `anthropics/claude-code/.github/ISSUE_TEMPLATE/` with no regeneration path and no recorded + recheck trigger, while `context/action-create.md` told its only reader twice never to trust it. + Templates that change without notice cannot be cached honestly, so the body now names the live + source, the `gh api` command that fetches it, and `action-create.md` as owner of the fetch + protocol. The one fact that was local rather than copied stays: resolve the regression field from + the registry's last known working state when the issue reached `create` through + `/claude-ops:known-issues search`. Both `action-create.md` citations were reworded, since they + described a snapshot that no longer exists. + ## [0.38.5] ### Changed diff --git a/plugins/claude-ops/README.md b/plugins/claude-ops/README.md index 3d425d3dc..c885af172 100644 --- a/plugins/claude-ops/README.md +++ b/plugins/claude-ops/README.md @@ -26,7 +26,7 @@ Claude Code's native OTEL cannot see. | `/claude-ops:audit-skill-visibility` | Audits whether the model can actually **see** each installed skill, the question behind "why does most of my fleet never get used?", since a skill the model cannot see can never be chosen. Reports three independent things per skill: **reachability** (visible, `user-only` by design, hidden by an override or disabled plugin, or invisibly misconfigured), **observation** (what usage was actually recorded, always horizon-qualified), and **starvation** (whether it is losing the description-budget contest. Claude Code drops descriptions starting with the skills you invoke least, so an unused skill loses the keywords a request would match and stays unused). Whether the listing overflows is computed from documented settings; which particular skills lose their descriptions is a labelled likelihood band, never an exact cutoff. Withholds every cold verdict the data cannot support instead of reporting absence of data as absence of use. Read-only. | | `/claude-ops:audit-install-state` | Read-only audit of the machine-scope Claude Code installation directory, the `~/.claude` tree plus the home-root `~/.claude.json`. Inventories every file (entries labelled as an authored surface or a rolled-up bulk tree, with the complete per-file rows in a CSV artifact), separates what Claude Code's own `cleanupPeriodDays` sweep already manages from what nothing manages, resolves what each number in a filename actually *is* before attempting any process-liveness lookup, and deny-lists any subtree holding a revert ledger before classifying anything as stale. Never deletes; hands off to `claude project purge` and `/disk-hygiene:clean`. | | `/claude-ops:audit-native-overlap` | Maps native Claude Code surfaces, built-in CLI commands, bundled skills, plugin-backed built-ins, session-provided skills, against the current repo's plugin skills and agents. Bare invocation is a read-only overlap report (candidates with evidence, detection integrity floors carried through, and a shared-listing-budget exposure section); verdicts (`prefer-native` / `prefer-ours` / `complementary` / `superseded` / `defer`) are human-gated in a committed store (`docs/native-surfaces/records.json`) rendered into a generated registry (`docs/NATIVE-SURFACES.md`) whose every row carries an observable recheck trigger; only an explicit `apply` step bakes presence-gated native references into component descriptions and Boundary sections. | -| `/claude-ops:audit-performance` | Read-only slowness-diagnostic capture, run at the moment the machine or a session feels slow, before restarting or deleting anything. One timed engine pass separates the four documented suspects: accumulated install-tree state (retention-sweep health including the silent unparsable-`settings.json` pause, plus a timed stat-walk whose duration approximates the product's own daily sweep cost), version regression (CLI version against a bundled known-performance-issues reference), component bloat (fleet and process censuses, verdict routed to `/claude-ops:plugins audit`), and the fan-out layer (a load-labelled no-op spawn baseline, every hook that will fire bucketed per-tool-call versus per-turn with its invocation shape, the configured statusline, subagent concurrency and spawn-depth ceilings against documented defaults, whether running sessions predate the settings file, and orphan attribution by parent liveness rather than age). Phase timings are first-class evidence; content reads are allowlisted to four non-secret config files (`settings.json`, `.last-cleanup`, `hooks.json`, `installed_plugins.json`), so `~/.claude.json` and `history.jsonl` stay stat-only. Reports and routes; never mutates, never elevates, and never executes a discovered hook or statusline command. | +| `/claude-ops:audit-performance` | Read-only slowness-diagnostic capture, run at the moment the machine or a session feels slow, before restarting or deleting anything. One timed engine pass separates four documented suspects: accumulated install-tree state, version regression, component bloat, and the fan-out layer. Each suspect's evidence and verdict routing is documented in the skill. Phase timings are first-class evidence; content reads are allowlisted to four non-secret config files (`settings.json`, `.last-cleanup`, `hooks.json`, `installed_plugins.json`), so `~/.claude.json` and `history.jsonl` stay stat-only. Reports and routes; never mutates, never elevates, and never executes a discovered hook or statusline command. | | `/claude-ops:observability` | Reads locally captured Claude Code telemetry, OTEL DuckDB store, machine-owned collector, optional Aspire dashboard, hook-event JSONL, ccusage, and renders cross-session trend reports (`session`/`day`/`week`/`month`/`since:`/`all` scopes). Read-only except the explicit `clean` action, which prunes the JSONL log and OTEL store by age. | | `/claude-ops:known-issues` | Searches known Claude product GitHub bugs before you build on a feature, checks service health and model quality, and maintains a persistent registry of tracked issues (what they block, workarounds, follow-ups when fixed). Actions: `status` (default), `search`, `check-all`, `scan`, `list`, `quality`, `create`. | | `/claude-ops:changelog` | Ingests Claude Code changelog entries and integrates them into the current repo: `fetch` (read-only display), `diff` (impact triage, no edits), `status` (applied versions from git history), and `apply` (full explore → research → interview → implement pipeline, explicit user intent only). | @@ -37,7 +37,8 @@ Claude Code's native OTEL cannot see. ## The audit hooks -Eight advisory `*-audit` hooks (across nine hook scripts. `skill-usage-audit` has two producers, see below) emit the marketplace +Eight advisory `*-audit` hooks, spread across nine hook scripts because `skill-usage-audit` has two +producers, emit the marketplace [hook-telemetry envelope](../../docs/conventions/hook-telemetry/README.md). One JSON event per run carrying that hook's own `duration_ms`, outcome, and a privacy-safe subject. Each is independently toggleable via its own `userConfig` @@ -57,8 +58,8 @@ approved, and the only durable trace is a transcript attachment no human reads (#2577). It runs once per `Stop`, tails a bounded window of the session transcript for `hook_non_blocking_error` attachments (structural match on the attachment type, never substring), and warns once per session per distinct -failing hook registration (`hookName` plus registered command. Several plugins -share an event+matcher), re-warning when a new registration starts failing. It +failing hook registration, keyed on `hookName` plus the registered command because several plugins +share an event and matcher, re-warning when a new registration starts failing. It lives in this plugin, not in the plugin it might report on, deliberately: an in-plugin detector shares its plugin's registration form and dies with it, which is @@ -313,7 +314,7 @@ Three supported routes, in the order most people want them: for a non-sensitive option at `user` scope, by writing a non-default value to an installed plugin and restoring it. The short-circuit message is about the install, not the config write. That has not been verified for a `sensitive` option or for - `project`/`local` scope. Do **not** `claude plugin uninstall` in order to + `project`/`local` scope. Do **not** `claude plugin uninstall` to reconfigure: uninstalling drops this plugin's whole stored `pluginConfigs` entry, resetting every option in the table above to its default. `-s` defaults to `user`, so pass the scope `claude plugin list` reports for this plugin. diff --git a/plugins/claude-ops/skills/audit-install-state/SKILL.md b/plugins/claude-ops/skills/audit-install-state/SKILL.md index b9df64ce3..cc9615871 100644 --- a/plugins/claude-ops/skills/audit-install-state/SKILL.md +++ b/plugins/claude-ops/skills/audit-install-state/SKILL.md @@ -149,7 +149,7 @@ Each entry carries `surface`, a `reading` with its own `evidence`, and `file_cou count** and keeps each file's first snapshot regardless of age; `subagents/` and `tool-results/` age out with their parent transcript; `session-env/`, `tasks/` and `debug/` are per-session. Old mtimes on those paths are the documented behaviour. -See [reference/surfaces.md](reference/surfaces.md). +Per-path retention rules: see [reference/surfaces.md](reference/surfaces.md). ## Phase 4. Numeric names and liveness @@ -167,7 +167,7 @@ missed. That distinction is the whole point: `alive` is a measurement about *a* process with that id; PIDs get reused, so "therefore this file is in use" is a further inference. A probe that could not run reports `unverified`, **never** `dead`. -See [reference/name-schemes.md](reference/name-schemes.md). +Name schemes and their liveness meanings: see [reference/name-schemes.md](reference/name-schemes.md). ## Phase 5. Home-root state @@ -202,7 +202,7 @@ If you fan this out across agents, keep an explicit cross-review stage: in the a from, five errors were made and five were caught, **none by the agent that made it**. Parallelism buys coverage, not correctness. Verify a peer's claim against your own evidence before adopting it, and record a disagreement nothing depends on as unresolved rather than settling it silently. -See [reference/evidence-discipline.md](reference/evidence-discipline.md). +Cross-review procedure: see [reference/evidence-discipline.md](reference/evidence-discipline.md). ## Verifying an upstream claim @@ -210,7 +210,7 @@ Any claim about what Claude Code itself does must come from the raw markdown end `https://code.claude.com/docs/en/claude-directory.md` to a file, then read the file. A summarizing fetch returns a small model's answer *about* the page, so **absence from it is not evidence of absence**, and no destructive conclusion may rest on one. -See [reference/evidence-discipline.md](reference/evidence-discipline.md) §6. +Upstream-claim verification: see [reference/evidence-discipline.md](reference/evidence-discipline.md) §6. ## Gotchas diff --git a/plugins/claude-ops/skills/known-issues/context/action-create.md b/plugins/claude-ops/skills/known-issues/context/action-create.md index 892e6e35b..eb4a00dff 100644 --- a/plugins/claude-ops/skills/known-issues/context/action-create.md +++ b/plugins/claude-ops/skills/known-issues/context/action-create.md @@ -35,7 +35,7 @@ gh api repos/{repo}/contents/.github/ISSUE_TEMPLATE/{template_file} --jq '.conte | `bug` | `bug_report.yml` | | `feature` | `feature_request.yml` | -If template can't be fetched (repo inaccessible, template renamed/removed), STOP and inform user. Template structure may change at any time — `context/issue-templates.md` is a reference snapshot, not source of truth. +If template can't be fetched (repo inaccessible, template renamed/removed), STOP and inform user. Template structure may change at any time and this repo keeps no cached copy to fall back on; `context/issue-templates.md` names the live source and this plugin's local field rule. **Gate 2: Version check** — verify user is on latest Claude Code version: @@ -138,4 +138,4 @@ ISSUE_BODY ## Template reference -Snapshot of template fields and body format (offline reference only — always fetch live): `context/issue-templates.md` +Live template source and this plugin's local field rule: `context/issue-templates.md` diff --git a/plugins/claude-ops/skills/known-issues/context/issue-templates.md b/plugins/claude-ops/skills/known-issues/context/issue-templates.md index d650999e1..5dc4084de 100644 --- a/plugins/claude-ops/skills/known-issues/context/issue-templates.md +++ b/plugins/claude-ops/skills/known-issues/context/issue-templates.md @@ -1,200 +1,23 @@ -# Anthropic Issue Templates Reference (Snapshot) +# Anthropic issue templates: where to get them -**Point-in-time snapshot for offline reference only.** `create` action MUST fetch live template from GitHub before drafting. Templates may change without notice. +Template structure is owned upstream and changes without notice, so this repo keeps no copy of it. +The `create` action fetches the live template before drafting and stops when it cannot. -Snapshot retrieved 2026-03-29 from `anthropics/claude-code/.github/ISSUE_TEMPLATE/`. - -Live source: +Live source: `anthropics/claude-code/.github/ISSUE_TEMPLATE/`. ```bash gh api repos/anthropics/claude-code/contents/.github/ISSUE_TEMPLATE/