diff --git a/plugins/docs-hygiene/.claude-plugin/plugin.json b/plugins/docs-hygiene/.claude-plugin/plugin.json index da49a91db..14d13958b 100644 --- a/plugins/docs-hygiene/.claude-plugin/plugin.json +++ b/plugins/docs-hygiene/.claude-plugin/plugin.json @@ -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", diff --git a/plugins/docs-hygiene/CHANGELOG.md b/plugins/docs-hygiene/CHANGELOG.md index b2702a05b..99a77a5e8 100644 --- a/plugins/docs-hygiene/CHANGELOG.md +++ b/plugins/docs-hygiene/CHANGELOG.md @@ -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: + , + , and + + (verified 2026-08-04). + ## [0.9.4] ### Fixed diff --git a/plugins/docs-hygiene/skills/extract-ssot/SKILL.md b/plugins/docs-hygiene/skills/extract-ssot/SKILL.md index e74ce190a..2ad5bed5d 100644 --- a/plugins/docs-hygiene/skills/extract-ssot/SKILL.md +++ b/plugins/docs-hygiene/skills/extract-ssot/SKILL.md @@ -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. diff --git a/plugins/docs-hygiene/skills/extract-ssot/context/anti-patterns.md b/plugins/docs-hygiene/skills/extract-ssot/context/anti-patterns.md index b1ff31425..b3475e377 100644 --- a/plugins/docs-hygiene/skills/extract-ssot/context/anti-patterns.md +++ b/plugins/docs-hygiene/skills/extract-ssot/context/anti-patterns.md @@ -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 diff --git a/plugins/docs-hygiene/skills/extract-ssot/context/decision-framework.md b/plugins/docs-hygiene/skills/extract-ssot/context/decision-framework.md index ef43e9067..c8e778272 100644 --- a/plugins/docs-hygiene/skills/extract-ssot/context/decision-framework.md +++ b/plugins/docs-hygiene/skills/extract-ssot/context/decision-framework.md @@ -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" | @@ -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 | diff --git a/plugins/playbooks/.claude-plugin/plugin.json b/plugins/playbooks/.claude-plugin/plugin.json index 061dd3be9..b6a4f269d 100644 --- a/plugins/playbooks/.claude-plugin/plugin.json +++ b/plugins/playbooks/.claude-plugin/plugin.json @@ -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", diff --git a/plugins/playbooks/CHANGELOG.md b/plugins/playbooks/CHANGELOG.md index a8623da7f..024b63ec0 100644 --- a/plugins/playbooks/CHANGELOG.md +++ b/plugins/playbooks/CHANGELOG.md @@ -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 + (verified 2026-08-04). + ## [0.6.14] ### Changed diff --git a/plugins/playbooks/skills/fable-5/context/orchestration.md b/plugins/playbooks/skills/fable-5/context/orchestration.md index a0a52656f..36885771d 100644 --- a/plugins/playbooks/skills/fable-5/context/orchestration.md +++ b/plugins/playbooks/skills/fable-5/context/orchestration.md @@ -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