Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/docs-hygiene/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "docs-hygiene",
"version": "0.9.4",
"version": "0.9.5",
"description": "Documentation-hygiene toolkit of six skills: compress (flavor-trim markdown with a semantic-diff safety net), audit-noise (classify markdown noise), extract-ssot (deduplicate repeated content into a single source of truth), audit-encapsulation (detect citations into skill-private surfaces), rename-references (sweep stale references after renames), and audit-derivability (classify whether a whole document earns its existence — could a fresh agent re-derive it from the code?).",
"author": {
"name": "Melodic Software",
Expand Down
34 changes: 34 additions & 0 deletions plugins/docs-hygiene/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
# Changelog — docs-hygiene plugin

## [0.9.5]

### Fixed

- **`extract-ssot`'s volatility gate no longer rests on a cache-invalidation mechanism that does
not operate on the surface it scopes to** (docs-hygiene 0.9.4 → 0.9.5). Anti-pattern #9 was
"Cache invalidation cascade": extracting into an always-loaded file that gets edited often meant
"downstream sessions' prompt caches invalidate on every edit", with a symptom of falling cache
hit rate. Decision-framework test #3 carried the same leg, cited to the **API** prompt-caching
page with the gloss "cache TTL hinges on stability".

Both fail against the docs. The API page nowhere ties TTL to content stability — TTL is an
explicit five-minute default with a one-hour opt-in — and it is the wrong surface besides: this
skill scopes to a consuming repository's tracked markdown, which is consumed by Claude Code
sessions. On that surface Claude Code's own prompt-caching page is the authority, and it says
editing an always-loaded file mid-session "does not invalidate the cache, but the edit also
doesn't apply", while sequential sessions "share the prefix only when the git status snapshot at
startup matches" — so any commit already breaks cross-session prefix sharing and the SSOT's edit
frequency is not the marginal driver.

The real cost of a volatile always-loaded SSOT is propagation, and it is worth a gate. Pattern #9
is now "Always-loaded SSOT propagation lag": a correction lands in the repo while every session
already running keeps following the superseded version until its next `/clear`, `/compact`, or
restart. Its symptom, mitigations, and the test #3 rationale are rewritten to that mechanism, a
scope fence separates it from API-surface caching (where prefix volatility *does* cost an Agent
SDK fleet sharing one prefix across machines), and a fourth mitigation tells the author to say so
when a correction must reach live sessions. The slot number is unchanged, so the by-number
citations to patterns #10-#13 across `actions/`, `lessons.md`, and SKILL.md are untouched;
SKILL.md's taxonomy list carries the new name. Sources:
<https://code.claude.com/docs/en/prompt-caching#editing-claude-md-mid-session>,
<https://code.claude.com/docs/en/prompt-caching#cache-scope>, and
<https://platform.claude.com/docs/en/build-with-claude/prompt-caching#cache-storage-and-sharing>
(verified 2026-08-04).

## [0.9.4]

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion plugins/docs-hygiene/skills/extract-ssot/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Encapsulation enforcement (detection grep, public/private surface matrix, remedi

## Anti-patterns guarded

13-pattern taxonomy with mitigations: citation rot, over-indirection, leaky abstraction, loss of locality, reference resolution failure, wrong abstraction, premature extraction, self-generated SSOT, cache invalidation cascade, encapsulation violation, source-of-truth bifurcation, primary-source citation gate, Shape C dedup-by-deletion (positive). Each pattern + symptom + mitigation procedure: `context/anti-patterns.md`.
13-pattern taxonomy with mitigations: citation rot, over-indirection, leaky abstraction, loss of locality, reference resolution failure, wrong abstraction, premature extraction, self-generated SSOT, always-loaded SSOT propagation lag, encapsulation violation, source-of-truth bifurcation, primary-source citation gate, Shape C dedup-by-deletion (positive). Each pattern + symptom + mitigation procedure: `context/anti-patterns.md`.

Patterns #11/#12/#13 derive from the empirical lessons in `context/lessons.md` and are surfaced as REFUSE triggers in the `verify` action.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,17 +118,20 @@ Patterns are framed for markdown extraction (the dominant case) but apply to cod
2. Phase boundaries surface the diff to the user explicitly; never auto-stage/commit/push
3. Eval cases for any new skill MUST be human-reviewed against expected output before declaring done

## 9. Cache invalidation cascade
## 9. Always-loaded SSOT propagation lag

**Pattern.** Extraction creates a new always-loaded file that gets edited often; downstream sessions' prompt caches invalidate on every edit; token cost rises for every session that loads it.
**Pattern.** Extraction moves content into an always-loaded file (`CLAUDE.md`, an unscoped `.claude/rules/` file — rules with `paths:` frontmatter load lazily instead, so an edit before they load does take effect) that then gets edited often. Sessions already running never see the edits: those files are read once at session start, and a mid-session edit neither applies nor invalidates the cache — the new content loads on the next `/clear`, `/compact`, or restart. A correction lands in the repo while every live consumer keeps following the superseded version.

**Symptom.** Cache-creation token volume rises relative to baseline in usage telemetry; cache hit rate for sessions in the repo drops after the SSOT lands.
**Scope fence — Claude Code sessions, and the cost is propagation, not caching.** A mid-session edit to an always-loaded file keeps the cached prefix, and sequential sessions in a directory share a prefix only when the startup git-status snapshot matches, which captures branch and recent commits — so committing the edit breaks sharing exactly as any other commit does, and SSOT edit frequency is not a distinct driver of cache misses ([editing CLAUDE.md mid-session](https://code.claude.com/docs/en/prompt-caching#editing-claude-md-mid-session), [cache scope](https://code.claude.com/docs/en/prompt-caching#cache-scope), verified 2026-08-04). On the API surface a volatility cost is real, because cache hits there require byte-identical prefix segments — that reaches an Agent SDK fleet assembling one shared prefix across machines, not the tracked-markdown extractions this skill scopes to ([cache storage and sharing](https://platform.claude.com/docs/en/build-with-claude/prompt-caching#cache-storage-and-sharing), verified 2026-08-04).

**Symptom.** A rule corrected hours ago is still being violated by long-running sessions; two concurrent sessions in the same repo follow different versions of the same extracted rule.

**Mitigation.**

1. Decision-framework test #3 (Stable — content changes <1×/quarter) is the up-front gate
2. If the SSOT must be edited frequently, split it: stable categorical bits stay in the SSOT, volatile narrative goes back inline
2. If the SSOT must be edited frequently, split it: stable categorical bits stay in the always-loaded SSOT, volatile narrative moves to a surface that loads late enough to see corrections — a skill body (injects at invocation), a `paths:`-scoped rule (loads on first matching read), or a file consulted on demand. Going back inline helps only when the original home was itself lazy-loaded; inline in `CLAUDE.md` or an unscoped rule is the same always-loaded surface with the same lag, and buys nothing
3. A Recheck-triggers section in the SSOT documents anticipated edit frequency; if it drifts >1×/month, raise it as a side observation
4. After a correction live sessions must honor, say so — the fix reaches them only on `/clear`, `/compact`, or restart

## 10. Encapsulation violation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Applies to any repeated text content — markdown (rules, skills, docs), code (c
|---|------|-----|----------|
| 1 | **Rule of Three** — duplication appears in 3+ places | Premature abstraction creates the wrong-abstraction trap; 1-2 instances are usually coincidence, not pattern. Same principle whether the duplicated unit is a markdown heading, a string constant, a helper function, or a CI step | Don Roberts / Fowler *Refactoring* §1; Sandi Metz "The Wrong Abstraction" |
| 2 | **Namable as a stable canonical unit** — the cluster has an identity that can be given one name and referenced by that name | For markdown: a heading or rule name. For code: a function/constant/type identifier. For config: an anchor/include/`$ref` target. Without a stable name, callers can't cite/import unambiguously and the SSOT becomes a grab-bag. For markdown specifically, the unit should also be categorical (vocabulary, constraints, IF-THEN) rather than nuanced reasoning — MDEval finding: providing an external markdown reference does NOT improve a model's Markdown Awareness vs well-designed inline rules ("feeding a reference to an LLM does not bring any benefit for Markdown Awareness; this unexpected finding challenges prevalent assumptions") | Anthropic best-practices "Avoid offering too many options"; MDEval arxiv 2501.15000; Endor Labs anti-pattern avoidance (64% reduction with categorical extraction) |
| 3 | **Stable** — content does NOT change more than 1×/quarter | Volatile SSOTs invalidate downstream sessions' prompt caches on every edit (an always-loaded file that changes often is a cache-miss generator); high churn also drives heading/identifier rename frequency, which compounds the citation-rot risk captured in test #6 below. Code-side equivalent: high churn means callers chase signature changes constantly | Anthropic prompt caching docs (cache TTL hinges on stability — [platform.claude.com/docs/en/build-with-claude/prompt-caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)); Sandi Metz wrong-abstraction (volatile = signal that the abstraction shape is not yet stable) |
| 3 | **Stable** — content does NOT change more than 1×/quarter | High churn drives heading/identifier rename frequency, which compounds the citation-rot risk captured in test #6 below; and every edit to an always-loaded file reaches sessions already running only at the next `/clear`, `/compact`, or restart, so a volatile SSOT ships corrections its live consumers do not see (anti-pattern #9). Not a caching cost — a mid-session edit to an always-loaded file keeps the cached prefix. Code-side equivalent: high churn means callers chase signature changes constantly | Claude Code prompt caching, [editing CLAUDE.md mid-session](https://code.claude.com/docs/en/prompt-caching#editing-claude-md-mid-session) (verified 2026-08-04); Sandi Metz wrong-abstraction (volatile = signal that the abstraction shape is not yet stable) |
| 4 | **Self-contained** — content has no implicit dependency on caller context | Leaky abstraction = silent failure. For markdown: the extracted block must not say "the prior step" or "as discussed earlier". For code: the helper must not depend on global state the caller happens to set. For config: the include must not reference variables the includer happens to define | Joel Spolsky "Law of Leaky Abstractions"; elements.cloud agent-instruction antipatterns |
| 5 | **Bounded size** — extracted markdown file < 500 lines; extracted code module sized per language idiom | Anthropic's documented best-practice guideline ("Keep SKILL.md body under 500 lines for optimal performance"); over-long files force partial reads and downstream-session cache pressure. For code: each language has its own conventions (small composable modules over monoliths) | Anthropic best-practices "Keep SKILL.md body under 500 lines for optimal performance"; GitHub Copilot 4000-char hard truncation |
| 6 | **One level deep** — referenced directly from caller, never via another reference | Markdown: A.md → B.md → C.md chains compound failure rates (5-20% per step). Code/config: the equivalent rule is no transitive re-export chains; the call site imports/cites the canonical SSOT directly | Anthropic best-practices "Avoid deeply nested references" |
Expand All @@ -23,7 +23,7 @@ ALL six must pass. Failing one means: keep inline, OR refactor the candidate bef
|---|------|-----|
| A | Single use site OR < 3 instances of duplication | Premature abstraction; wait for the third instance |
| B | Markdown content is reasoning, decision logic, or tradeoff explanation | Indirection breaks down on nuanced content (MDEval finding); for code/config this test usually doesn't apply because the unit is mechanical |
| C | Content is volatile (changes more than monthly) | Citation rot guaranteed (heading/function/anchor renames); cache-invalidation cascade for downstream sessions |
| C | Content is volatile (changes more than monthly) | Citation rot guaranteed (heading/function/anchor renames); and corrections ship that live consumers do not see until their next `/clear`, `/compact`, or restart (anti-pattern #9) |
| D | Instances differ in non-trivial ways and would force conditionals/flags inside the SSOT | Sandi Metz wrong-abstraction trap; params/conditionals proliferating IS the signal that the unit is NOT yet stable |
| E | Cluster is small (< ~10 lines) AND specific to one consumer's behavior | Indirection cost > duplication cost; the cite-by-name / import overhead wins |

Expand Down
2 changes: 1 addition & 1 deletion plugins/playbooks/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "playbooks",
"version": "0.6.14",
"version": "0.6.15",
"description": "Doctrine and knowledge playbooks as on-demand skills, plus a maintainer-facing update skill. boris — Boris Cherny's Claude Code workflow tips (howborisusesclaudecode.com); skill-authoring — Anthropic's internal skill-authoring playbook; fable-5 — Claude Fable 5's operating doctrine (self-authored, no upstream). The boris and skill-authoring packs vendor a verbatim upstream baseline; /playbooks:update drift-checks and syncs those baselines centrally (maintainers).",
"author": {
"name": "Melodic Software",
Expand Down
22 changes: 22 additions & 0 deletions plugins/playbooks/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ All notable changes to the `playbooks` plugin are recorded here. The `version` i
`.claude-plugin/plugin.json` is the delivery vehicle — a consumer receives a change
only after that version increases.

## [0.6.15]

### Fixed

- **`fable-5` orchestration: continuing an oriented worker is no longer sold as a cache read**
(playbooks 0.6.14 → 0.6.15). The "keep working while workers run" chapter told readers to
continue a worker that already holds a subject rather than spawn a replacement, and grounded it
in cost: "accumulated context is a cache read rather than a re-derivation". The mechanism fails
in the chapter's own modal case. Claude Code's prompt-caching page states that a subagent
"builds its own cache" and that "Subagents use the five-minute TTL even on a subscription, since
the automatic one-hour TTL applies to the main conversation" — so a worker resumed after a wave
that ran longer than five minutes re-writes its whole accumulated context at the five-minute
cache-write rate ("1.25 times the base input tokens price"), not the cache-read rate, and
fan-out waves routinely run longer than five minutes.

The recommendation survives unchanged; its reason is corrected. The saving is the re-derivation,
not the tokens: a continued worker re-sends its accumulated context either way, and it still
beats a replacement, which pays those same tokens *plus* the tool turns to rediscover the
material. The bullet now says that, names the five-minute subagent TTL as the reason a resumed
worker often pays the higher rate, and cites
<https://code.claude.com/docs/en/prompt-caching#subagents-and-the-cache> (verified 2026-08-04).

## [0.6.14]

### Changed
Expand Down
2 changes: 1 addition & 1 deletion plugins/playbooks/skills/fable-5/context/orchestration.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Research parallelizes well: read-only, results merge by union. Code parallelizes

- **Dispatch is not a blocking call.** Move to the next piece of your own work that no pending return feeds. Waiting the wave out makes your throughput the slowest worker's — and the slowest worker is usually the one that drifted, so the wait buys a late return you then discard.
- **Check in rather than wait out.** Read a running wave against the drift signals below and intervene on what you find: a worker missing context you already hold gets it while its run can still use it, not in the post-mortem after its return is unusable.
- **A worker already oriented on a subject is cheaper than a fresh one.** Where successive subtasks share a subject, continue the worker that holds its orientation instead of spawning a replacement to re-read the same material — accumulated context is a cache read rather than a re-derivation, and it keeps the wave off the slowest-spawn path. Start fresh when the subject changes, and always for the fresh-context verifier above, whose entire value is holding none of it.
- **A worker already oriented on a subject is cheaper than a fresh one.** Where successive subtasks share a subject, continue the worker that holds its orientation instead of spawning a replacement to re-read the same material, which also keeps the wave off the slowest-spawn path. What you save is the re-derivation, not the tokens: a continued worker re-sends its accumulated context either way — billed as a cache read within its cache lifetime, and re-written past it at the five-minute cache-write rate, which is 1.25× base input rather than base input. Subagents get the five-minute TTL even on a subscription, so a worker resumed after a long wave pays that write rate. It still beats a replacement, which pays those same tokens plus the tool turns to rediscover the material ([prompt caching: subagents and the cache](https://code.claude.com/docs/en/prompt-caching#subagents-and-the-cache) and [pricing](https://platform.claude.com/docs/en/build-with-claude/prompt-caching#pricing), verified 2026-08-04). Start fresh when the subject changes, and always for the fresh-context verifier above, whose entire value is holding none of it.

## Monitor, intervene, plan for partial failure

Expand Down