fix(claude-ops): give the skill-visibility starvation band a real usage signal - #3532
Conversation
Exploration artifact only. Records the verified shape and write paths of skillUsage, pluginUsage, agentLastUsed and projects[] in ~/.claude.json, the recovered skill-listing budget scorer, and the repo surfaces that already read those counters. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
A live run of audit_skill_visibility.py returns usage_score 0 for every row, so the band falls back to its alphabetical tiebreaker. Records the run's numbers and the inverted example rows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
The observability reporter's lanes omit ~/.claude.json; nothing reads agentLastUsed or the per-project cost snapshot; pair co-occurrence needs the event stream the counters cannot supply. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
Adds the Claim/Basis/As-of/Recheck stamp for the binary-derived listing scorer and the fallback rule for a build mismatch. Restates the skill-usage.jsonl finding as what was checked and what remains unresolved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
…ints Records claude_code.skill_activated's invocation_trigger as the only source that separates user-invoked from model-invoked, the retro transcript parser as the only working agent-usage source, and the three codified refusals: ADR 0016's deferral of usage-driven surfacing, the performance engine's stat-only posture on ~/.claude.json, and the exposure-floor withheld verdict. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
…plit drop The audit is a three-source reconciler with a capability tier model, not a native-counter reader, so parse_otel and TIER_CAPABILITIES replace the earlier claim that OTEL appears only in a reference file. The 60s debounce was already documented at SKILL.md:194-196, so it is recorded as not-a-gap rather than a finding. The qualified-vs-bare key split is confirmed: classify() looks events up by qualified_name alone, so bare-key rows are discarded silently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
…no re-derivation Section 3.9 was doing audit-native-overlap's job in prose, a silent second way alongside docs/native-surfaces/records.json. It now raises the missing doctor -> audit-skill-visibility row as a candidate and leaves the verdict to the human gate that skill's contract requires. Part 3.8 told the reader to start from the codified refusals "rather than re-derive them", which is the inertia this repo's incumbency discipline exists to catch. It now separates the two refusals that name a purpose and a measurement from the one that names only the state of the substrate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
…ad both ways A cross-vendor re-derivation run blind to the earlier reasoning found the "scope limit" framing wrong in both directions. The deferral is scoped to show-options rotation, so encoding zPe inside audit-skill-visibility was never inside it and does not need to be framed as an exception. But the deferral is also not up for lifting: its real ground is that zPe is wrong-signed for a forgotten-skill nudge, that skillUsage cannot name never-invoked skills, and that it carries no take-up attribution. Records the lift conditions and the reason a binary-derived stamp is not what the ADR meant by documented. Also drafts, without applying, the correction owed to the ADR's Context line about drop ordering. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
…al ground Two dated revision blockquotes in the ADR's own established shape, which preserves superseded reasoning rather than editing Context in place. The Context paragraph stated that Claude Code drops descriptions "starting with the skills invoked least". The mechanism is a decay-weighted score, usageCount * max(0.5 ^ (daysSinceUse / 7), 0.1), sorted descending and granted greedily, so a heavily used but stale skill can be shed before a lightly used fresh one. The correction strengthens the decision rather than weakening it: a never-invoked skill still scores zero and is shed first, and the decay term adds a second bias against exactly the forgotten-skill population show-options exists to surface. The deferral clause rested on skillUsage being "undocumented internal state". That substrate is now characterized and dated, which invites a false lift, so the ground is restated on three reasons documentation cannot cure: the scorer is wrong-signed for the question, skillUsage cannot name the never-invoked population, and it carries no take-up attribution. Lift conditions recorded. Core decision untouched in both cases. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
…pping bare usage keys Two defects in audit-skill-visibility, both confirmed against live data. compute_listing sorted on a usage_score only the test fixtures ever set. The live collector builds its denominator from a filesystem walk and joined usage events afterwards, so every real run scored zero and the band fell through to its alphabetical tiebreaker while presenting itself as usage-informed. On this machine that ranked adhd:clarify (1 use) first to lose its description and work-items:triage (99 uses) among the safest. Scores are now computed before the listing is built. Events were looked up by qualified <plugin>:<leaf> name only, while the stores hold both that key and the bare leaf as separate rows, so the bare row was discarded silently. source-control:babysit-prs reported 97 invocations against an actual 475. A bare key is now attributed when exactly one skill owns that leaf and withheld with its candidates when more than one does, which is the same refusal the custom_skill redaction guard makes. listing_score mirrors the product's own scorer, usageCount * max(0.5 ** (daysSinceUse / 7), 0.1), recovered from Claude Code 2.1.251 and carrying a verification stamp with its basis and recheck trigger. It is fed from native counters under the exact qualified key because the product's scorer does no bare-key fallback either; scoring the merged total would predict a truncation that will not happen. listing.score_basis reports "unscored" when no usage survives to weigh, and competing rows carry confidence "unscored" rather than borrowing "inferential", which would claim more than an alphabetical order supports. Suite 93/93, ruff clean. The shell harness exits 2 both before and after this change, from a pre-existing fixture gap unrelated to it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
The counting-rules additions pushed SKILL.md from 199 to 216 lines, past the soft target, which is the progressive-disclosure signal working. The mechanism, why "least invoked" was the wrong description, why a bare key does not move the band, and the drift posture now live in reference/listing-scorer.md, leaving two tight bullets and a pointer in the hub. SKILL.md lands at 207. The remaining eight lines are the two non-obvious counting rules the section exists to hold, so they stay rather than being cut to hit an advisory number. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
The rewritten drop-order clause pushed description+when_to_use to 1550 chars against the 1536 cap, which truncates the listing entry. Says the same thing in fewer words: descriptions are dropped by a decay-weighted usage score. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1e61d7713d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Matches the citation shape of the ADR's two 2026-08-21 revisions, which carry an issue link. 3534 records the two audit-skill-visibility defects that prompted both revisions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
…mentary verdict audit-skill-visibility's description already routed the one-shot unused-versus-context-cost check to the bundled /doctor, with no store row behind the disclaimer. Record the missing row: verdict complementary, evidence from the installed v2.1.252 binary's doctor Check 1 strings (2026-08-31), and regenerate NATIVE-SURFACES.md via overlap.py generate. Fresh-context verifier concurred with the verdict; self-check degraded only on the pre-existing v2.1.232 extraction staleness. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
Resolves two conflicts, both mechanical: plugin.json: keep 0.39.0. This branch's minor bump supersedes main's 0.38.22 patch; the two changes are independent and both ship. CHANGELOG.md: keep both sections, 0.39.0 above 0.38.22. Neither entry replaces the other. Suite 93/93 on the merged tree. NOTE, unresolved by this merge and tracked separately: main's #3524 added a SKILL.md paragraph and a plugin.json description clause restating the least-invoked-first drop order and citing the official skills page for it. This branch's own description now says decay-weighted, so the merged file states both. An adjudicator is settling the wording; the contradiction is deliberate and visible rather than silently resolved one way here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
|
Claude finished @kyle-sexton's task in 1m 57s —— View job Security review —
|
|
Last security-reviewed head: |
|
Claude finished @kyle-sexton's task in 3m 39s —— View job Claude finished @kyle-sexton's taskRan
Findings1. 2. The default markdown render never reads 3. Bare-key attribution for duplicate qualified names — reviewed Codex's finding, not elevating. Not re-litigatedEverything else in the diff (the ADR revision blockquotes, |
|
Claude has reviewed this PR 5 times. The lane skips further automatic reviews after 5; deleting this comment resets the count. |
…ocate-by-shape The recheck trigger fired the same day it was written: the CLI auto-updated from 2.1.251 to 2.1.252 mid-session. Re-ran the extraction. Both the scorer's formula and the truncator's descending-sort greedy grant come back unchanged. The re-run surfaced a trap worth recording. The minified identifier is not stable across builds: the scorer was zPe in 2.1.251 and WPe in 2.1.252 with a byte-identical body. A recheck that greps the old name finds nothing and would wrongly conclude the mechanism was removed. All three stamps now say to locate by shape, and carry the two greps that do it. Suite 93/93, ruff clean, markdownlint clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
|
Claude finished @kyle-sexton's task in 41s —— View job Security review —
|
|
Claude finished @kyle-sexton's task in 1m 24s —— View job Code review —
|
…opic slice Two gates caught real violations of this repo's own conventions. contract-slice-prune-gate: docs/topics/<slug>/ is Contract tier per docs/conventions/topic-docs/README.md, committed on a task branch and pruned before merge. EXPLORE.md was the wrong home from the start; that convention also names EXPLORE.md as a memory-tier file, which never gets committed at all. The durable outcomes are graduated rather than deleted: - The scorer, its drift posture and the locate-by-shape rule already live in reference/listing-scorer.md. - The counters' own semantics now live in a new reference/usage-counters.md, beside the skill that consumes them: the skillUsage throttle and its qualified-versus-bare key split, the pluginUsage install seeding that makes lastUsedAt worthless at zero count, the incomparability of pluginUsage across plugin shapes, agentLastUsed holding nothing usable, and the structural fact that no per-project skill usage exists in this file at all. - The two defects are issue 3534; the mechanism correction is ADR 0016. Every citation of the pruned slice is repointed, so nothing dangles. skill-count-claim-gate: a bullet opening "One skill, two possible usage keys" parsed as a claim that claude-ops ships one skill. Reworded to "Two possible usage keys per skill", same meaning, no leading count word. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
|
Claude finished @kyle-sexton's task in 1m 8s —— View job Code review —
|
|
Claude finished @kyle-sexton's task in 1m 2s —— View job Security review —
|
…re prefix
The adjudicator's independent re-extraction found the mirror wrong on the half I
had not checked. The product's grant loop has NO early exit:
for (let me of W) {
let ge = me.entryLen - (me.cmd.name.length + 2);
if (ge <= pe) pe -= ge; else fe.push(me);
}
So truncation is a greedy first-fit walk over the whole score-descending list,
not a prefix of it. A cheap low-scored description can be granted after an
expensive higher-scored one was refused, which makes description LENGTH a second
ranking input that no prose account of this mechanism mentions. compute_listing
modelled a prefix, which understated the exposure of long descriptions and
overstated it for short ones.
The verdict now mirrors the real walk. The band keeps ranking exposure by score,
and the two are allowed to disagree; a regression test pins exactly that case,
where a band-1 row survives a pass that sheds a better-scored longer row.
Budget accounting is deliberately NOT changed here. It counts description bytes
against the whole budget and ignores the name bytes every entry also pays. That
is the CERTAIN half of the report and a separate correction with its own
evidence.
Also corrects the attribution. The ADR's drop-order sentence tracks the official
documentation, which states the same false claim verbatim ("drops descriptions
starting with the skills you invoke least, so the skills you use most keep their
full text"). The revision now names upstream as the source of the error instead
of implying this repo got it wrong, and the SKILL.md paragraph that cited that
page as authority now says which part of it does not hold. The plugin.json
description clause is corrected the same way.
Suite 94/94, ruff clean, markdownlint clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
|
Claude finished @kyle-sexton's task in 3m 48s —— View job Code review —
|
|
Claude finished @kyle-sexton's task in 1m 1s —— View job Security review —
|
verify-posttooluse-probe.md is a live probe fixture belonging to the in-flight skill-usage-hook investigation, deliberately containing a misspelling and trailing whitespace to trip a PostToolUse hook. It has no business in history. The file stays on disk so the running worker is not disrupted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
|
Claude finished @kyle-sexton's task in 3m 20s —— View job Code review —
|
…, and date it The correction stays at full strength, but "stated wrongly" with no referent put the error on the ADR's author, who transcribed the vendor page near-verbatim. It also dropped the operationally useful fact: the upstream page is still wrong as of 2026-08-31, which is why the same sentence keeps re-entering this repo. It is on main in the skill's SKILL.md and in the plugin manifest, and #3524 added a citation pointing at the wrong page for it. The SKILL.md paragraph now separates the two upstream pages by whether they hold: the settings page owns the budget fraction and per-entry cap and matches; the skills page states the drop order and does not. Readers are routed to the binary for the ordering. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
|
Claude finished @kyle-sexton's task in 48s —— View job Security review —
|
Two more corrections from the adjudicator's re-extraction, both scoped to the grant loop. The grant budget is computed FORWARD from a floor, `budget - V`, never backward from the overflow. V is what the listing costs before any description is granted: every listed entry pays for its own name, the exempt classes pay their full rendering because they are never candidates, and the separators are charged too. A disable-model-invocation skill is absent from the listing entirely, so unlike the other two exempt classes it costs nothing and takes no separator. Without this the grant boundary itself is wrong, which left the first-fit fix in 916367b inert. Ties keep catalog order, not alphabetical. The product's sort is stable, so equal scores stay in input order, and Python's is too. This decides everything in the unscored case, where every score is zero and the tiebreak IS the whole ordering; an alphabetical one disagreed with the product on every row. demand/overflow is deliberately NOT changed. It answers "does the listing overflow", a different question from "which entries win", and only the second needs the floor. Charging name bytes there is real but not urgent: at this repo's 12.7x overflow it cannot flip the verdict, and it would move every test encoding the current arithmetic plus every figure recorded against it. It wants its own evidence, a fleet where the corrected count changes the verdict. Two test expectations move because the old ones encoded the no-floor model, and the corrected answers are what the product actually does. Live run: 171 of 176 competing now starved, up from 167, which is the floor being charged. Also softens listing-scorer.md's "a never-used skill is always shed first". It sorts last, so it sheds first under any material overflow, but first-fit means a zero-scored skill with a very short description can still be granted. Suite 94/94, ruff clean, markdownlint clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
|
Claude finished @kyle-sexton's task in 54s —— View job Security review —
|
docs/CATALOG.md is generated from the plugin manifests, and the drop-order wording fix in the claude-ops description left it stale. Regenerated with scripts/generate-catalog.mjs; the only diff is that one clause. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
Refreshes a stale base the gate correctly refused. main had moved two commits touching the same claude-ops manifest and changelog, which is exactly the overlap that lets a stale-base squash silently revert a landed fix. Both conflicts mechanical again: plugin.json: keep 0.39.0 and this branch's corrected description clause. main still carries "drops descriptions least-invoked-first", which is the upstream wording this branch fixes, so taking theirs would have reverted the correction. CHANGELOG.md: keep all three entries, 0.39.0 above main's new 0.38.23 above 0.38.22. None replaces another. Catalog already in sync, suite 94/94 on the merged tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
|
Claude finished @kyle-sexton's task in 1m 3s —— View job Security review —
|
…se a bare key on duplicate rows Three defects the automated reviewers caught, two of them reintroducing at a different scope the exact failure this PR exists to fix. score_basis was decided over the whole denominator, before competing was filtered from it. A bundled, name-only, or disable-model-invocation skill can carry real native usage while being excluded from the contest entirely, so its score flipped the basis to native-counters while every actual contender sat at zero. Those rows then got confidence "inferential", which is a catalog ordering presenting itself as usage-informed. That is this report's own headline defect, one scope up. The basis is now decided from the competing rows only. The bare-key resolver collapsed owners into a set, so two marketplaces shipping the same plugin, which the report already marks ambiguous-attribution, produced one distinct qualified name. A bare key then passed the single-owner test and was reported on BOTH rows, inventing usage for an attribution the audit knows it cannot make. Owners are counted per entry now. The markdown renderer still said descriptions are dropped "least-invoked-first" and described the order as a likelihood band regardless of basis. It now says lowest-score-first, names decay-weighting and description length as inputs, and on an unscored run states plainly that the order carries no starvation information rather than hedging it as inferential. The 0.39.0 changelog entry also omitted the first-fit, floor and tiebreak changes entirely. Suite 96/96 with two regression tests, ruff clean, markdownlint clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
|
Claude finished @kyle-sexton's task in 58s —— View job Security review —
|
…rop order as fact (#3551) No linked issue `audit-native-overlap` asserted in two places that skill-listing name-only degradation goes "least-invoked-first", sourced to the skills page. #3532 established from the shipped binary that this is wrong, so one skill in this plugin corrected the claim while its sibling still asserted it. The upstream-claims row named "the drop order changes" as its own recheck trigger, and that trigger had fired. It now records both what the docs say and what the binary does, routes the mechanism to `audit-skill-visibility/reference/listing-scorer.md`, and watches the binary rather than only the page. The Budget exposure guidance carried the same false mechanism as an OPERATIVE instruction, which is the line the skill acts on when characterizing an over-budget fleet. Correcting only the claims row would have left that live and made the two sections contradict each other. It now says lowest-score-first and states that raw invocation count is not the exposure ranking and that description length matters. The 1,536-character per-entry cap and the 1% listing budget in that row are unchanged and still hold; the settings page is authoritative for those and matches. Found by a post-merge verifier sweeping merged main for the stale wording after #3532. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d
Closes #3534
Summary
Started as an exploration of what Claude Code tracks in
~/.claude.json, and whether this repo was about to rebuild a counter the product already keeps. It found two defects inclaude-ops:audit-skill-visibility, both fixed here, and established that the mechanism both this repo and the official docs describe is not the one the product implements.Fix
The starvation band carried no usage signal.
compute_listingsorted on ausage_scorethat only the test fixtures ever set. The live collector builds its denominator from a filesystem walk, and usage events were joined afterwards, so every real run scored zero and the band fell through to its tiebreaker while presenting itself as usage-informed. On this machine that rankedadhd:clarify(1 use) as first to lose its description andwork-items:triage(99 uses) among the safest. Scores are now computed before the listing is built.Usage recorded under a skill's bare leaf was silently discarded. Events were looked up by qualified
<plugin>:<leaf>name only, while the stores hold both that key and the bare leaf as separate rows.source-control:babysit-prsreported 97 invocations against an actual 475. A bare key is now attributed when exactly one skill owns that leaf, and withheld with its candidates when more than one does, which is the same refusal thecustom_skillredaction guard already makes.The mechanism, recovered and then corrected twice.
listing_scoremirrors the product's own scorer,usageCount * max(0.5 ** (daysSinceUse / 7), 0.1). Two further corrections came out of an independent re-extraction by a fresh-context adjudicator:budget - V, where V is what the listing costs before any description is granted. Without it the grant boundary itself is wrong, which would have left the first-fit fix inert.unscoredcase, where every score is zero and the tiebreak is the whole ordering.listing.score_basisreportsunscoredwhen no usage survives to weigh, and competing rows carryconfidence: "unscored"rather than borrowinginferential.What is deliberately not changed:
demand/overflowstill ignores name bytes. It answers "does the listing overflow", a different question from "which entries win", and only the second needs the floor. At this repo's 12.7x overflow, charging names cannot flip the verdict, but it would move every test encoding the current arithmetic and every figure recorded against it. It wants its own evidence.ADR 0016 gets two dated revision blockquotes in its own established shape. The core decision is untouched in both.
mainin the SKILL.md and the manifest, and docs: replace in-place skill-frontmatter restatements with upstream references #3524 added a citation pointing at the wrong page for it.skillUsagecannot name never-invoked skills, it carries no take-up attribution), because characterizing the substrate invites a false lift. Lift conditions recorded.Documentation. Two new references beside the skill that consumes them:
reference/listing-scorer.md(the mechanism, its counterexamples, the drift posture) andreference/usage-counters.md(whatskillUsage,pluginUsage,agentLastUsedandprojects[]actually mean, including the 60-second throttle that drops rather than coalesces, thepluginUsageinstall seeding, and the structural absence of per-project skill usage).Verification
scripts/run-ruff.sh checkclean; markdownlint clean on every touched file.score_basis: native-counters,source-control:babysit-prsat 475,cleancorrectly withheld as ambiguous betweendisk-hygiene:cleanandrepo-hygiene:clean.zPebecameWPe, and three sibling symbols moved with it), so a recheck must locate by shape, not by name.skill-quality:checkreports one error and one warning, both pre-existing:audit_skill_visibility.test.shexits 2 before and after this change from an unrelated fixture gap, and the 1024-codepoint description warning predates it (the description is now shorter than it started).Related
docs/adr/0016-source-skill-recommendation-from-the-catalog-not-the-listing.md, amended here but not superseded.docs/conventions/upstream-drift, the convention the two new verification stamps follow.docs/conventions/topic-docs, whose contract-slice rule pruned this work's task doc; its durable outcomes graduated into the two references above, ADR 0016, and audit-skill-visibility: starvation band is alphabetical, and bare usage keys are dropped #3534.docs/native-surfaces/records.jsongains thedoctortoaudit-skill-visibilityrow (complementary), recorded through the generator and independently confirmed.🤖 Generated with Claude Code
https://claude.ai/code/session_015eyw6KUwExd78yyptowV6d