Skip to content

feat(governance): add disk->index coverage gate so skills cannot silently become unroutable (M4) - #900

Merged
notque merged 1 commit into
mainfrom
m4-index-integrity
Jul 25, 2026
Merged

feat(governance): add disk->index coverage gate so skills cannot silently become unroutable (M4)#900
notque merged 1 commit into
mainfrom
m4-index-integrity

Conversation

@notque

@notque notque commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Audit first, then the fix

The brief expected ~40 unroutable skills and 11 phantom index entries. Measured against the tree, neither holds:

Claim Measured
40 skills in neither index 0 genuinely missing. 20 on-disk skills are absent from INDEX.json, and every one carries promoted_to — the generator skips folded husks on purpose (generate-skill-index.py:407-409) so a husk cannot shadow its umbrella.
skills/meta/do unroutable do has promoted_to: native-router. Same intentional exclusion. Same for worktree-agent (-> do), roast (-> multi-persona-critique), cve-source-check (-> service-health-check).
11 phantoms on pre-migration flat paths All 11 are private skills. Their file field is deploy-relative, and all 11 resolve at ~/.claude/skills/<name>/SKILL.md. INDEX.local.json mirrors the deployed layout by design.

After regenerating both indexes: 98 active skills, 98 indexed, 0 gaps, 0 unresolvable paths. Both indexes are gitignored generated artifacts, so the regeneration itself is local and not part of this diff.

So the real defect was not that coverage was broken — it was that nothing enforced coverage, so it could rot silently at any time.

The gate

validate-index-integrity.py check 1 walks index -> disk. Nothing walked disk -> index. New check 3 does: every skills/*/*/SKILL.md must have an INDEX entry, with promoted_to husks exempt so the check agrees with the generator rather than demanding entries it will never emit.

The validator already runs in CI after index generation (test.yml:22,29) — no new workflow step needed.

Verified in all three directions:

1. clean tree              -> Check 3: PASS
2. unindexed skill seeded  -> Check 3: FAIL, names the skill and the fix command
3. same skill + promoted_to -> Check 3: PASS (correctly exempt)

Tests

scripts/tests/test_validate_index_coverage.py — 6 cases: indexed skill passes, unindexed errors, promoted husk exempt, all gaps reported at once, missing skills/ does not crash, and a live-tree assertion that current coverage is complete.

Gates

  • ruff check / ruff format --check (0.15.12) — pass
  • pytest scripts/tests/ — 4643 passed, 1 skipped, 138 xfailed
  • validate-skill-frontmatter.py — 119 OK
  • validate-index-integrity.py — 0 errors, PASS
  • validate-doc-counts.py — all claims agree

…ntly become unroutable

validate-index-integrity.py check 1 walks index -> disk and catches entries
whose file vanished. Nothing walked the other way, so a SKILL.md that exists,
parses, and is healthy but was never registered stayed invisible to the
router with no signal anywhere.

Check 3 closes that direction: every SKILL.md under skills/*/*/ must have an
INDEX entry. Skills carrying promoted_to are exempt, matching the generator,
which skips folded husks on purpose so a husk cannot re-enter routing and
shadow the umbrella that replaced it.

The validator already runs in CI after index generation (test.yml:22,29), so
this needs no new workflow step. The error names the fix, not just the fault.

Audit result behind this change: the current tree has zero coverage gaps and
zero phantoms once both indexes are regenerated. All 20 on-disk skills absent
from INDEX.json carry promoted_to and are excluded by design; all 11
INDEX.local.json entries that look like dead paths are private skills whose
repo-relative path resolves under the deployed root. The defect was that
nothing enforced this, not that it was currently broken.
@notque
notque merged commit 148d3bc into main Jul 25, 2026
11 checks passed
@notque
notque deleted the m4-index-integrity branch July 25, 2026 02:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant