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:
@@ -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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user