fix(kyberforge): resolve PR #144 review and audit round 2

- factory-audit: ./ and bare/absolute script checks scoped to command
  position (no false FAILs on ./src or printf); hook sources limited to
  .apm/hooks or package-root hooks/; Kiro-aware lowercase events;
  unfilled template placeholders FAIL; repo-only instructions FAIL at
  any scope; Vale description FAIL documented; bats 367 -> 378
- primitive-author: split-quote/spaced paths and handler-less entries
  promoted to Must; Step 4.2 renders into a scratch consumer instead of
  a no-op dry run; dispatch and gate hand-off trimmed
- apm-workflow 1.0.2: mutual boundary with primitive-author
- forge: no double package bump; gotcha wording
- skill-author: create keeps seeded 0.1.0 (ADR-0022); portable,
  retry-safe new-skill.sh; template and flow consistency fixes
- hook docs: cite the ADR-0019 correction; guard caveat

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 20:50:22 +00:00
parent df28351d3e
commit 965208bddd
29 changed files with 462 additions and 156 deletions

View File

@@ -15,8 +15,8 @@ metadata:
## Gotchas
- `apm compile --validate` is not a gate: it only warns on instructions, exits 0, and never reads prompts. `apm install` exits 1 only on a hook payload Copilot would reject or on critical hidden Unicode, which also blocks the package's deployment; bad prompt input names and dropped keys merely warn — `/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.
- `apm compile --validate` is not a gate: it only warns on instructions, exits 0, and never reads prompts. `apm install` exits 1 only on a hook payload Copilot would reject or on critical hidden Unicode; bad prompt input names and dropped keys merely warn — `/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 named that way anywhere in the repo becomes 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
@@ -26,13 +26,12 @@ metadata:
| A hook — `.apm/hooks/<name>.json`, or "run X whenever Y happens" | hook | `references/hook.md` |
| An instruction — `.apm/instructions/<name>.instructions.md`, or a rule for files matching a pattern | instruction | `references/instruction.md` |
| A prompt — `.apm/prompts/<name>.prompt.md`, or a reusable message the user types to kick off work | prompt | `references/prompt.md` |
| A skill or an agent | — | stop: route to `skill-author` or `agent-author` |
Read only the reference matching the resolved type — each is self-contained. If the target sits inside a git worktree, capture `rtk git log --oneline -1` before touching the filesystem; Step 4 needs it.
## Step 2 — Boundary gate
Run the reference's **Gate** section before writing anything. A failed gate stops this skill: name the owner it points to — `skill-author` for procedure, `agentsmd-author` for a repo-only rule, `apm-workflow` for reach or `targets:` — and hand over. Never bend the artifact to pass the gate.
Run the reference's **Gate** section before writing anything. A failed gate stops this skill: hand over to the owner the Gate names. Never bend the artifact to pass the gate.
## Step 3 — Create or improve
@@ -42,11 +41,11 @@ Run the reference's **Gate** section before writing anything. A failed gate stop
| 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 |
Signals: grill output, `/factory-audit` findings, inline feedback, session context describing what went wrong. Group findings by root cause and fix the cause once.
Signals: grill output, `/factory-audit` findings, inline feedback, session context. Group findings by root cause and fix the cause once.
## Step 4 — Validate and close
1. Run `/factory-audit` on the file, inline in this context; resolve every FAIL before reporting done, including the `### Prose` FAILs Vale raises on an instruction or prompt body.
2. Run `rtk apm install --dry-run` from the repo root and read what each target will receive. On a feature branch, discard `apm.lock.yaml` churn afterwards (`rtk git checkout -- apm.lock.yaml`).
3. Bump the owning package's `apm.yml` `version:` — minor for a new hook, instruction or prompt, patch for a fix — unless this branch already bumped it for unreleased work. None of these has a version of its own.
4. **Commit verification.** Inside a git worktree, once the audit is clean, run `rtk git add` and `rtk git commit`, then re-run `rtk git log --oneline -1` and confirm the hash changed from Step 1's. Staged-but-uncommitted work is silently lost if the tree is cleaned up. Outside a worktree, report done on a clean audit and name that as the reason.
1. Run `/factory-audit` on the file, inline in this context; resolve every FAIL before reporting done, including the `### Prose` FAILs Vale raises on an instruction or prompt body or description.
2. Render it: in a fresh `mktemp -d` directory, run `rtk apm install <absolute path to the owning package> --target <its targets:>`, then read what each target received — `.claude/settings.json` and `.github/hooks/`, `.claude/rules/` and `.github/instructions/`, or `.claude/commands/` and `.github/prompts/`. A local path deploys the working tree; `--dry-run` renders nothing, and a repo-root install resolves the remote's `main`, so never use either.
3. Bump the owning package's `apm.yml` `version:` — minor for a new hook, instruction or prompt, patch for a fix — unless this branch already bumped it for unreleased work. None of these is released on its own version — bump the package even if an instruction carries an optional `version:` key.
4. **Commit verification.** Inside a git worktree, once the audit is clean, run `rtk git add` and `rtk git commit`, then confirm `rtk git log --oneline -1` changed from Step 1's hash: staged-but-uncommitted work is lost if the tree is cleaned up. Outside a worktree, report done on a clean audit and name that as the reason.

View File

@@ -48,7 +48,7 @@ target:
- **`${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}` is rewritten identically, so it is valid, but it ties the source to one
harness's name (Should 12).
harness's name (Should 11).
- **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. Per-file target routing is deprecated, so a Copilot-native flat hook
@@ -65,15 +65,25 @@ Must:
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. The file contributes at least one entry: an empty one
deploys nothing, with only a warning.
else fails the Copilot install outright. The file contributes at least one entry, and every
entry carries at least one handler: an empty list or a handler-less entry deploys nothing, with
only a warning.
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.
`Stop`, …). An all-lowercase name never warns: only Kiro's rename map covers one (`stop` →
`Stop`), and every other target deploys it verbatim, where it never fires. A camelCase name
outside apm's rename map (`userPromptSubmit`) likewise deploys verbatim to Claude and never
fires.
5. The script is referenced as `${PLUGIN_ROOT}/…` (or `${CLAUDE_PLUGIN_ROOT}/…`, see Shape) for
the package root, or `./…` for the hook directory, and exists inside the package. No absolute
path and no bare relative path (`scripts/x.sh`): apm bundles and rewrites neither. No `$` or
backtick in the path itself. A missing script is only a warning at install time.
the package root, or `./…` for the hook directory, and exists inside the package. The script is
the command's first token or the first argument after an interpreter (`bash`, `sh`, `zsh`,
`python`, `python3`, `node`, `pwsh`, `ruby`, `perl`); in either position, no absolute path and
no bare relative path (`scripts/x.sh`): apm bundles and rewrites neither. No `$` or backtick in
the path itself, and no space. When quoting, quote the whole token —
`"${PLUGIN_ROOT}/scripts/my-hook.sh"`, never `"${PLUGIN_ROOT}"/scripts/x.sh`: apm rewrites
`${PLUGIN_ROOT}` only when a path separator follows it directly, and only up to the next space
or quote, so a split quote is left unrewritten and a spaced path is cut short. This is stricter
than the research's Should, as with Must 6: either defect fails every time the hook fires. A
missing script is only a warning at install time.
6. A script run directly as the command's first token is executable. This is stricter than the
research's Should: without it the hook fails every time it fires. A script passed to an
interpreter (`bash ${PLUGIN_ROOT}/x.sh`) needs no executable bit.
@@ -86,14 +96,10 @@ Should:
Claude receives `"*"`.
9. Do not author Copilot's flat `bash` / `powershell` / `timeoutSec` keys in a Claude-shaped file;
they render onto Claude as stray keys.
10. Keep script paths free of spaces, and when quoting, quote the whole token:
`"${PLUGIN_ROOT}/scripts/my-hook.sh"`. apm rewrites `${PLUGIN_ROOT}` only when a path separator
follows it directly, and only up to the next space or quote — so `"${PLUGIN_ROOT}"/scripts/x.sh`
is left unrewritten and a spaced path is cut short.
11. Keep helper files in the hook directory non-JSON. Copilot's loader rejects any bundled `.json`
10. Keep helper files in the hook directory non-JSON. Copilot's loader rejects any bundled `.json`
without a `hooks` key.
12. Prefer `${PLUGIN_ROOT}` over `${CLAUDE_PLUGIN_ROOT}`.
13. No filename that routes by target. Case-insensitively, apm routes a stem of exactly
11. Prefer `${PLUGIN_ROOT}` over `${CLAUDE_PLUGIN_ROOT}`.
12. No filename that routes by target. Case-insensitively, apm routes a stem of exactly
`hooks-<target>` and any stem ending `<target>-hooks` — bare (`claude-hooks`), prefixed
(`x-claude-hooks`) or combined (`claude-codex-hooks`, the union). That routing is deprecated and
reach belongs to `targets:` (see Gate); the research allows it only when deprecated routing is

View File

@@ -18,8 +18,8 @@ glob. On Claude it deploys to `.claude/rules/<stem>.md` with `applyTo` renamed t
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` 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.
`AGENTS.md` and `CLAUDE.md` (CLAUDE.md is skipped when `.claude/rules/` is populated, AGENTS.md
when `.github/instructions/` is, 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.
@@ -47,8 +47,8 @@ Should:
its handling of a list is unverified.
7. Keep frontmatter to `description` and `applyTo`, plus optional `author` and `version`. No target
consumes other keys, and Claude drops them.
8. Put any rationale Claude needs in the body. `description` never reaches Claude — it survives only
for Copilot and as Cursor's index text.
8. Put any rationale Claude needs in the body. `description` never reaches Claude — it survives for
Copilot and as index text in Cursor rules and compiled AGENTS.md/CLAUDE.md.
9. Keep relative markdown links resolvable from the source file.
10. Check the glob against the tree: one that matches nothing here fires only in consumer repos
that have such files, and one broader than the rule's real scope spends context on every file

View File

@@ -56,6 +56,8 @@ Should:
5. Frontmatter keys stay within `description`, `allowed-tools`, `model`, `argument-hint` and
`input`. Claude drops everything else with only a warning. The exception: a Copilot-only key
(`agent`, `tools`, …) that is intended, with its Claude drop accepted and said so.
The research files this as a Must; it is a Should here because only the author can say a
Copilot-only key is intended.
6. The description follows the contract above: one plain sentence, no trigger or boundary clause,
naming the skills or agents it steers.
7. Spell keys in kebab-case — `allowed-tools`, `argument-hint` — not the camelCase aliases.

View File

@@ -4,7 +4,7 @@
- **URL:** file:///root/.local/pipx/venvs/apm-cli/lib/python3.11/site-packages/apm_cli/
- **Research doc:** plugins/kyberforge/docs/research/docs/microsoft-apm/sources.md
- **Description:** Installed apm-cli 0.28.0 source — ground truth for what apm deploys from a hook, instruction or prompt file and what it silently skips or only warns on; every Must/Should checklist item traces to the research docs' Authoring checklists, and the `**/*.instructions.md` local-discovery glob behind the template-suffix Gotcha is `primitives/discovery.py` `LOCAL_PRIMITIVE_PATTERNS`
- **Description:** Installed apm-cli 0.28.0 source — ground truth for what apm deploys from a hook, instruction or prompt file and what it silently skips or only warns on; each Must/Should traces to the research docs' Authoring checklists or audit-only lists, or to ADR-0029, with tier moves annotated inline, and the `**/*.instructions.md` local-discovery glob behind the template-suffix Gotcha is `primitives/discovery.py` `LOCAL_PRIMITIVE_PATTERNS`; the per-target event rename maps are `integration/hook_integrator.py` `_HOOK_EVENT_MAP`; and Step 4.2's render relies on `apm install <local path>` deploying the working tree to every `--target`, verified against 0.28.0
- **Contributing files:** SKILL.md, references/hook.md, references/instruction.md, references/prompt.md
- **Status:** `extracted`