fix(kyberforge): resolve clean-context audit findings on primitive support

primitive-author:
- description excludes read-only review (-> factory-audit)
- validation Gotcha now matches the research: compile never reads
  prompts, install fails only on a bad Copilot hook payload and warns on
  prompt input names and dropped keys
- instruction fold-in into AGENTS.md/CLAUDE.md stated as conditional on
  dedup and --force-instructions
- hook checklist gains the wrapped-shape Must, drops hardlinks, notes
  why executable is stricter than the research, and states the
  separate Copilot-targeted package route instead of a blanket "don't"
- prompt Must 5 keeps the research's Copilot-only-key exception; adds
  model-slug and 250-char Shoulds; descriptions name skills or agents
- placeholder instruction covers both FILL IN and FILL_IN_ tokens

factory-audit: hardlink FAIL scoped to instructions and prompts
(find_hook_files skips symlinks only), with bats cases; prompt-flow
description rubric names skills or agents.

forge: version-bump, apm-routes and sources references updated for the
primitive route.

Refs #94

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-28 17:40:12 +00:00
parent 0d96dc8282
commit 9ac5340e15
11 changed files with 76 additions and 36 deletions

View File

@@ -2,8 +2,8 @@
name: primitive-author
description: >
Use when the user wants an apm hook, instruction or prompt file created, or
audit findings or feedback applied to an existing one.
Not skills -> skill-author. Not agents -> agent-author.
audit findings or feedback applied to an existing one. Not read-only
review -> factory-audit. Not skills -> skill-author. Not agents -> agent-author.
allowed-tools: Bash Read Write Edit
metadata:
version: "0.1.0"
@@ -15,8 +15,8 @@ metadata:
## Gotchas
- `apm compile --validate` is not a gate. apm turns every instruction and prompt problem into a warning and exits 0, and `apm install` never validates at all — `/factory-audit` is the only check that fails.
- Never draft with the real suffix outside `.apm/<type>/`. apm's local discovery globs `**/*.instructions.md` across the whole tree, so a draft or template named that way anywhere in the repo compiles into `AGENTS.md`. The templates carry a trailing `.template` for this reason; drop it only on the final path.
- `apm compile --validate` is not a gate: it reports instruction problems only as warnings, exits 0, and never reads prompts. `apm install` fails only on a hook payload Copilot would reject, and merely warns on bad prompt input names and dropped keys — `/factory-audit` is the only check that fails on the rest.
- Never draft with the real suffix outside `.apm/<type>/`. apm's local discovery globs `**/*.instructions.md` across the whole tree, so a draft or template named that way anywhere in the repo is picked up as a real instruction. The templates carry a trailing `.template` for this reason; drop it only on the final path.
- Never hand-write `.claude/settings.json`, even to test a hook. apm owns that file (ADR-0019), overwrites it outright when it is malformed, and `apm audit --ci` fails on anything it would not have written.
## Step 1 — Dispatch
@@ -38,7 +38,7 @@ Run the reference's **Gate** section before writing anything. A failed gate stop
| Condition | Action |
|---|---|
| No file at the target path | Create: copy the reference's template from `assets/templates/`, drop `.template`, fill every `FILL IN`, and apply the reference's checklist |
| No file at the target path | Create: copy the reference's template from `assets/templates/`, drop `.template`, fill every `FILL IN` and `FILL_IN_` placeholder, and apply the reference's checklist |
| File exists, at least one signal | Improve: read the whole file, then apply each signal against the reference's checklist |
| File exists, no signal | Stop and ask whether the user meant a new file or has feedback to apply |

View File

@@ -1,5 +1,5 @@
---
description: "FILL IN: one user-facing action naming the skills it steers"
description: "FILL IN: one user-facing action naming the skills or agents it steers"
input:
- FILL_IN_name: "FILL IN: what the user supplies"
---

View File

@@ -50,33 +50,38 @@ target:
`${CLAUDE_PLUGIN_ROOT}` also works but ties the source to one harness's name.
- **Claude is the verified target.** apm 0.28.0 passes this nested shape to Copilot without
reshaping it, and whether Copilot CLI runs nested entries or honours `matcher` is unverified. That
gap is apm's to close — do not work around it with a second, Copilot-flat file.
gap is apm's to close. Per-file target routing is deprecated, so a Copilot-native flat hook
(`bash` / `powershell` / `timeoutSec`) can only live in a separate Copilot-targeted package — hand
that to `apm-workflow` rather than adding a second file here.
## Checklist
Must:
1. The file sits directly in `.apm/hooks/`, is not a symlink or hardlink, and parses as a JSON
object. apm skips invalid JSON silently.
2. Every event value is a list of objects, and every nested `hooks` is a list of objects. Anything
1. The file sits directly in `.apm/hooks/`, is not a symlink, and parses as a JSON object. apm
skips invalid JSON silently.
2. Use the wrapped shape `{"hooks": {Event: [...]}}`. If a naked settings slice is used instead,
every top-level value must be a list, with no stray scalar keys anywhere.
3. Every event value is a list of objects, and every nested `hooks` is a list of objects. Anything
else fails the Copilot install outright.
3. Event names are PascalCase (`PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `SessionStart`,
4. Event names are PascalCase (`PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `SessionStart`,
`Stop`, …). An all-lowercase name (`stop`) never warns and never fires; a camelCase name outside
apm's rename map (`userPromptSubmit`) deploys verbatim to Claude and never fires.
4. The script is referenced as `${PLUGIN_ROOT}/…` (package root) or `./…` (hook directory),
5. The script is referenced as `${PLUGIN_ROOT}/…` (package root) or `./…` (hook directory),
exists inside the package, and is executable. No absolute path, and no `$` or backtick in the
path itself. A missing script is only a warning at install time.
5. No `hooks-<target>` or `*-<target>-hooks` filename. That routing is deprecated; reach belongs to
path itself. A missing script is only a warning at install time. Executable is stricter than the
research's Should: a script invoked as the command's first token fails at runtime without it.
6. No `hooks-<target>` or `*-<target>-hooks` filename. That routing is deprecated; reach belongs to
`targets:` (see Gate).
Should:
6. Every handler sets `"type": "command"` and an explicit `timeout` in seconds — apm passes `type`
7. Every handler sets `"type": "command"` and an explicit `timeout` in seconds — apm passes `type`
through but never supplies it.
7. Set `matcher` explicitly on tool events and on `SessionStart` (`startup`, `resume`, …). Omitted,
8. Set `matcher` explicitly on tool events and on `SessionStart` (`startup`, `resume`, …). Omitted,
Claude receives `"*"`.
8. Do not author Copilot's flat `bash` / `powershell` / `timeoutSec` keys in a Claude-shaped file;
9. Do not author Copilot's flat `bash` / `powershell` / `timeoutSec` keys in a Claude-shaped file;
they render onto Claude as stray keys.
9. Quote a script path that may contain spaces: `"${PLUGIN_ROOT}/scripts/my hook.sh"`.
10. Keep helper files in the hook directory non-JSON. Copilot's loader rejects any bundled `.json`
10. Quote a script path that may contain spaces: `"${PLUGIN_ROOT}/scripts/my hook.sh"`.
11. Keep helper files in the hook directory non-JSON. Copilot's loader rejects any bundled `.json`
without a `hooks` key.

View File

@@ -17,8 +17,9 @@ glob. On Claude it deploys to `.claude/rules/<stem>.md` with `applyTo` renamed t
- **A rule for this repo alone** → it belongs in the repo's AGENTS.md, which is the single
always-on source. Stop and hand to `agentsmd-author`.
- **No file pattern fits** → an instruction without `applyTo` is always-on in every session of
every repo that installs this package, and `apm compile` folds it into the global sections of
`AGENTS.md` and `CLAUDE.md`. Say exactly that to the user and continue only on an explicit yes.
every repo that installs this package, and `apm compile` can fold it into the global sections of
`AGENTS.md` and `CLAUDE.md` (skipped when `.github/instructions/` or `.claude/rules/` is already
populated, unless `--force-instructions`). Say exactly that to the user and continue only on an explicit yes.
Legitimate when a package deliberately ships guidance to its consumers; never a default.
- **Procedure the agent follows step by step** → a skill. Stop and hand to `skill-author`.
- **A rule scoped to a file pattern** → continue.
@@ -32,7 +33,7 @@ Must:
1. The path is `.apm/instructions/<stem>.instructions.md`, directly in that directory, not a
symlink or hardlink.
2. `description` is a non-empty string. apm only warns when it is missing.
3. The body is non-empty. apm deploys an empty rule silently.
3. The body is non-empty after trimming whitespace. apm deploys an empty rule silently.
4. `applyTo` is a non-empty glob or comma-separated list — top-level commas only as separators,
alternation inside `{}` (`"**/*.{ts,tsx}"`) — or absent after the Gate's explicit yes.
5. The stem is unique across the package and its dependencies: a `.claude/rules/<stem>.md`

View File

@@ -23,15 +23,16 @@ worse skill on every harness.
afterwards, come back and write it against the new skill.
- **A single-intent message the user would otherwise type repeatedly, steering existing skills or
agents by name** → continue. Confirm each skill or agent it names exists and is not
`disable-model-invocation: true`, which the model cannot invoke.
`disable-model-invocation: true`: the prompt's body reaches the model, and the model cannot
invoke a skill that sets it, so the steering would dead-end (the same check `factory-audit`'s
prompt flow applies).
## Description contract
One plain, user-facing sentence stating the action and naming the skills it steers — "Review the
One plain, user-facing sentence stating the action and naming the skills or agents it steers — "Review the
current PR with `gitea-prs` and `factory-audit`, then summarise the findings." No "Use when"
trigger clause and no `Not X -> Y` boundary: on Claude the description is model-visible, and a
trigger clause invites the router to pick the wrapper over the skills it wraps. 250 characters at
most.
trigger clause invites the router to pick the wrapper over the skills it wraps.
## Checklist
@@ -50,10 +51,14 @@ Must:
4. Every `${input:x}` in the body is declared in `input:`, and every declared name is used. Without
`input:`, no `${input:…}` may appear — it would reach Claude unrewritten.
5. Frontmatter keys stay within `description`, `allowed-tools`, `model`, `argument-hint` and
`input`. Claude drops everything else with only a warning.
`input`. Claude drops everything else with only a warning. The one exception: a Copilot-only key
(`agent`, `tools`, …) that is intended, with its Claude drop accepted and said so.
Should:
6. Spell keys in kebab-case — `allowed-tools`, `argument-hint` — not the camelCase aliases.
7. Omit `argument-hint` when `input:` is set; apm synthesises `<a> <b>` from the input names.
8. Keep one intent per prompt, and write the body as second-person instructions.
9. Keep `description` to 250 characters or fewer.
10. Give `model` a slug the target accepts. Copilot ignores `allowed-tools` and `model`, so neither
constrains a Copilot run.