Files
zernie__vigiles/docs/rules/plugin-dir-layout.md
zernie 1767bd3990 feat!: audit Safety ring + seven deterministic harness checks, README to 5/5
Adds a Safety ring + seven deterministic, FP-safe harness checks to `vigiles audit`
(lethal-trifecta, delegation-trifecta, skill-resource-resolves, skill-missing-fence,
plugin-dir-layout, hook-block-ineffective, hook-matcher), each wired into both audit
and lint from one shared detector. Declares every rule's decidability bucket in a
RuleMeta registry, documents the enforcement model, and lands the README at 5/5.

BREAKING CHANGE: the HarnessDialect field `blockingHookEvents` is renamed to
`noEffectHookEvents` (plus a new `permissionDecisionHookEvents`), and the public
surface gains the RuleMeta registry exports. Adapters that referenced
`blockingHookEvents` must use `noEffectHookEvents`.
2026-06-30 14:17:42 +05:00

3.7 KiB

plugin-dir-layout

Flag functional surface directories (skills/, agents/, commands/, hooks/) that are nested inside the manifest directory (e.g. .claude-plugin/) instead of at the plugin root. Surfaces placed there are completely invisible to the harness — the harness resolves them relative to the plugin root, not the manifest directory. Only plugin.json belongs inside .claude-plugin/. Same detector vigiles audit uses (pluginDirLayoutIssues in src/core/plugin-dir-layout.ts).

What it flags

A surface directory found nested inside the manifest dir:

✗ my-plugin: "skills/" is inside ".claude-plugin/" where the harness can't see it
  — only plugin.json belongs there; move "skills/" to the plugin root.

Example bad layout — skills are invisible:

my-plugin/
├── .claude-plugin/
│   ├── plugin.json          ✓ correct
│   └── skills/              ✗ harness can't see this
│       └── review/
│           └── SKILL.md

Correct layout — skills at the plugin root:

my-plugin/
├── .claude-plugin/
│   └── plugin.json          ✓
└── skills/                  ✓ harness finds this
    └── review/
        └── SKILL.md

Why

The harness resolves surface directories (skills/, agents/, commands/, hooks/) relative to the plugin root — the directory where the agent is launched — not relative to the manifest directory. A skills/ directory nested inside .claude-plugin/ will never be loaded: the agent gets no skills, hooks never wire up, and there is no error or warning from the harness to explain why. This is the #1 plugin-author mistake in the OSS ecosystem (pain #E1): the directory structure looks reasonable, every file exists on disk, and yet nothing works.

vigiles audit catches this with a pure filesystem check — no model, no key, offline — before a user ever runs the plugin.

Configuration

{ "rules": { "plugin-dir-layout": "warn" } }

Severity

Value Behavior
"error" vigiles lint exits non-zero (2) on a misplaced surface directory
"warn" (default) Prints a warning, exits 0
false Skip the check

Default is warn. Raise to "error" to make a misplaced surface directory a hard CI failure — it is always a bug, so "error" is the recommended setting once you've confirmed there are no pre-existing violations.

Scope

Applies wherever a manifest directory exists alongside a plugin root — Claude Code (.claude-plugin/plugin.json) and any other harness that separates its manifest from the plugin root. The surface directory names checked are injected from the active harness's layout, so the rule is harness-agnostic and will work correctly for Codex and future adapters.

Fix

Move the misplaced directories from inside the manifest directory to the plugin root:

# Example: move skills/ out of .claude-plugin/ to the root
mv .claude-plugin/skills ./skills

Then recompile (npx vigiles compile) and rerun npx vigiles audit to confirm the rule no longer fires.

See also

  • hook-script-exists — flag a hook whose script file is missing on disk (a related class of invisible-surface bug).
  • skill-frontmatter — recommend explicit name + description for a reliable skill trigger surface.
  • untested-skill — require a test or eval beside every SKILL.md.