fix(factory-audit): flag unbraced plugin-root tokens and close hook check gaps

Why: PR #144 review round 4 reproduced hooks referencing $PLUGIN_ROOT or
${PLUGIN_ROOT} without a path separator passing the audit, although apm
only rewrites ${TOKEN}/ and the deployed hook points nowhere.

- FAIL unbraced or unseparated plugin-root tokens
- check the exec bit for scripts run via an interpreter -c string
- skip env NAME=value prefixes when locating bare relative paths
- correct input: and empty-frontmatter messages, depth-walk applyTo braces
- INFO on unrecognised targets; failing-case tests for untested checks
- document tiers, blind spots and crash exit 2; drop rtk from portable flow
- restore the after-a-hand-edit trigger; pin upstream apm source URL

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkT7RSDwDbmrM9T34b6sTi
This commit is contained in:
2026-09-29 08:00:17 +00:00
parent 5d0f988ed8
commit 9285b29e3c
11 changed files with 363 additions and 43 deletions

View File

@@ -58,7 +58,7 @@ suite must mean a differently named script.
Each entry point decides for itself what it was handed. ADR-0025 states the
skill and agent rule: a directory containing `SKILL.md` takes the skill flow; an
`.agent.md` file, or a file under a directory named `agents/`, takes the agent
flow. `scripts/validate.sh` adds three primitive shapes: a `*.instructions.md`
flow. `scripts/validate.sh` adds the hook, instruction and prompt shapes: a `*.instructions.md`
or `*.prompt.md` file takes the instruction or prompt flow wherever it sits, and
a `.json` file directly under a `hooks/` directory takes the hook flow. Any
shape outside the five is rejected rather than guessed at. Classification could
@@ -66,17 +66,17 @@ not be wrong before the merge — each script was hard-wired to one artifact typ
— so it is asserted from every side rather than in one place:
- the skill-side suites pin the skill-directory classification and the
neither-shape rejection,
no-shape rejection,
- the agent-side suites pin the two agent rules *separately* — `.agent.md` in a
directory that is not `agents/`, and a plain `.md` under `.apm/agents/` — so
that a detector implementing only one of them cannot pass both. Plus a control
asserting an agent file never picks up a skill-only gate.
- `validate-primitive.bats` pins the hook, instruction and prompt shapes,
including the precedence that makes a `*.instructions.md` or `*.prompt.md`
under `agents/` take the primitive flow rather than the agent flow, a hook
under `agents/` take the instruction or prompt flow rather than the agent flow, a hook
file under a package-root `hooks/`, and a `.json` outside `hooks/` matching
no shape. It also pins the primitive exit tiers: every negative case asserts
exit 1 (`assert_failure 1`), and a missing python3 or PyYAML asserts exit 2.
no shape. It also pins their exit tiers: every negative case asserts
exit 1 (`assert_failure 1`), and a missing python3 or PyYAML, or a crash inside the checks, asserts exit 2.
Both skill-side suites additionally pin the `SKILL.md` **file** path, not just
the directory: a pre-commit `files:` hook matches files, so every hook-driven