feat(kyberforge): add primitive-author for apm hooks, instructions and prompts
New skill that creates or improves an apm hook, instruction or prompt. Its SKILL.md holds the shared procedure (dispatch on primitive, boundary gate, create-or-improve, factory-audit close); one self-contained reference per primitive carries its gate, checklist and template, drawn from the microsoft-apm research docs and ADR-0029. forge gains a route row sending a hook, instruction or prompt to primitive-author through author-routes.md, and no longer lists hooks as unroutable. factory-audit's description adds the primitive-author boundary now that the target resolves. Fixes #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:
@@ -0,0 +1,82 @@
|
||||
---
|
||||
source_keys:
|
||||
- apm-cli-installed-source
|
||||
- apm-docs-llms-full
|
||||
---
|
||||
|
||||
# Authoring an apm hook
|
||||
|
||||
Reached from `SKILL.md` Step 1 for a hook. Run the Gate, then write against the shape and the
|
||||
checklist, then return to `SKILL.md` Step 3.
|
||||
|
||||
## Gate
|
||||
|
||||
A hook is a runtime callback the harness fires inside its own tool loop — "this must always
|
||||
happen at this event", enforced deterministically rather than left to the model. apm's own
|
||||
guidance is to reach for a skill, instruction or prompt first, and to treat hooks as opt-in
|
||||
surface: they ship to a strict subset of harnesses and are silently skipped everywhere else.
|
||||
|
||||
- **Procedure, know-how, or anything the model should decide to do** → a skill. Stop and hand to
|
||||
`skill-author`.
|
||||
- **The hook must reach only some harnesses** → reach is set by the package `apm.yml` `targets:`,
|
||||
never by the hook file. Stop and hand to `apm-workflow`. `targets:` is package-wide, so a
|
||||
harness-specific hook in a multi-target package means either a separate package or accepting that
|
||||
the other targets receive it too.
|
||||
- **A runtime callback** → continue.
|
||||
|
||||
## Shape
|
||||
|
||||
One JSON file per concern at `.apm/hooks/<name>.json`, with a plain name. Copy
|
||||
`assets/templates/hook.json.template`. Write the canonical shape apm documents and renders per
|
||||
target:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{"type": "command", "command": "${PLUGIN_ROOT}/.apm/hooks/check.sh", "timeout": 10}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **`${PLUGIN_ROOT}`** is the target-neutral token; apm rewrites it per target
|
||||
(`"${CLAUDE_PROJECT_DIR}/.claude/hooks/<pkg>/…"` on Claude, repo-relative elsewhere).
|
||||
`${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.
|
||||
|
||||
## 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
|
||||
else fails the Copilot install outright.
|
||||
3. 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),
|
||||
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
|
||||
`targets:` (see Gate).
|
||||
|
||||
Should:
|
||||
|
||||
6. 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,
|
||||
Claude receives `"*"`.
|
||||
8. 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`
|
||||
without a `hooks` key.
|
||||
Reference in New Issue
Block a user