feat(kyberforge): primitive-author and factory-audit support for apm hooks, instructions and prompts #144

Open
Claude wants to merge 19 commits from feat/primitive-author into main
12 changed files with 325 additions and 22 deletions
Showing only changes of commit 0d96dc8282 - Show all commits

View File

@@ -2,12 +2,12 @@
name: factory-audit
description: >
Use when a skill, agent, or apm hook, instruction or prompt needs auditing,
including "is this ready to ship", or after hand-editing one outside its
author skill. Not applying skill fixes -> skill-author.
Not applying agent fixes -> agent-author.
or "is this ready to ship". Not fixing a skill -> skill-author.
Not fixing an agent -> agent-author.
Not fixing a hook, instruction or prompt -> primitive-author.
allowed-tools: Bash Read
metadata:
version: "1.1.0"
version: "1.1.1"
category: factory
source_keys:
- agentskills-home

View File

@@ -2,13 +2,12 @@
name: forge
description: >
Use when the user wants to build or improve something but has not yet named
the artifact type — skill, agent, plugin, or marketplace entry; "not sure if
this should be a skill or a plugin", "I have an idea but don't know where it
belongs". Routes to the matching author skill. Do not use when the type is
already named — invoke `skill-author`, `agent-author` or `apm-workflow`
directly.
the artifact type; "not sure if this should be a skill or a plugin", "I have
an idea but don't know where it belongs". Routes to the matching author
skill. Do not use when the type is already named — invoke `skill-author`,
`agent-author`, `primitive-author` or `apm-workflow` directly.
metadata:
version: "1.0.1"
version: "1.0.2"
category: factory
source_keys:
- claude-code-subagents-docs
@@ -18,7 +17,7 @@ metadata:
## Gotchas
- forge is an optional guided entry point, not a gate — `skill-author`, `agent-author`, `factory-audit` and `apm-workflow` all stay directly invokable, and forge never intercepts a direct call to one.
- forge is an optional guided entry point, not a gate — `skill-author`, `agent-author`, `primitive-author`, `factory-audit` and `apm-workflow` all stay directly invokable, and forge never intercepts a direct call to one.
- Claude Code's skill-level `context: fork` frontmatter field and the `/fork` subagent command are opposites despite the shared word: `context: fork` isolates (fresh context, no parent access), while `/fork` inherits the full conversation. The route reference each classification loads spends that distinction: `references/author-routes.md` chooses between the two, `references/apm-routes.md` rules the fork out.
## Step 1 — Grill the intent
@@ -37,12 +36,13 @@ Match the grilled intent against exactly one row — or more than one, if the in
|---|---|---|---|
| A reusable capability the agent loads inline in the main conversation, triggered by description-matching, free to bundle its own `references/`, `scripts/` or `assets/` | Skill | `skill-author` | `references/author-routes.md` |
| A recurring task needs its own reusable definition — dedicated system prompt, tools and description, invokable by name across sessions | Agent / subagent | `agent-author` | `references/author-routes.md` |
| A runtime callback at a harness event, a rule scoped to a file pattern, or a reusable user-typed message steering existing skills | Hook / instruction / prompt (apm primitive) | `primitive-author` | `references/author-routes.md` |
| A new distributable unit — no existing plugin is the right home for the skill, agent, hook or MCP server being built, or the bundle needs its own manifest, versioning and install lifecycle | Plugin | `apm-workflow` (`apm plugin init`) | `references/apm-routes.md` |
| The plugin already exists and only its marketplace-facing metadata changes — a first listing, or a version/description update, never the plugin's contents | Marketplace entry | `apm-workflow` (`apm marketplace package add`) | `references/apm-routes.md` |
The table classifies what to build, not how to run it: a one-off task that merely needs an isolated or context-inheriting run is not an artifact and has no row here. If the intent stays genuinely ambiguous between rows after grilling, ask the user rather than guessing.
A real artifact that matches no row — a hook, an MCP server, an AGENTS.md, a research doc — has no route here. Say so, hand the user the skill that does own it, and never bend it into a row to make the table fit.
A real artifact that matches no row — an MCP server, an AGENTS.md, a research doc — has no route here. Say so, hand the user the skill that does own it, and never bend it into a row to make the table fit.
When the intent spans several rows, chain the routes in dependency order — an artifact that must exist on disk before another skill can target it goes first, so `apm-workflow` scaffolds the plugin directory before `skill-author` scaffolds a skill inside it.
@@ -51,4 +51,4 @@ When the intent spans several rows, chain the routes in dependency order — an
## Step 3 — Closing gates, common to every route
- **Resolve before closing.** A route is finished only when its verification reports nothing unresolved. An actionable finding reopens the route; it is never reported onward as a caveat.
- **Bump the package version.** A skill route always lands here: `skill-author` moves only a skill's own `metadata.version`, which is not the package `apm.yml`'s number — so read `references/version-bump.md` after one. `agent-author` and the apm routes bump the package themselves at plugin scope; after those, read it only when their output does not say they did.
- **Bump the package version.** A skill route always lands here: `skill-author` moves only a skill's own `metadata.version`, which is not the package `apm.yml`'s number — so read `references/version-bump.md` after one. `agent-author`, `primitive-author` and the apm routes bump the package themselves at plugin scope; after those, read it only when their output does not say they did.

View File

@@ -3,12 +3,13 @@ source_keys:
- claude-code-subagents-docs
---
# Routing a skill or agent to its author skill
# Routing a skill, agent or apm primitive to its author skill
Reached from `SKILL.md` Step 2 when the classified artifact is a skill or an agent/subagent
definition. Route a skill to `skill-author` and an agent to `agent-author`. The two branches
differ on the author skill only — both verify the result with `factory-audit`, which detects the
artifact type itself — and everything below applies to both.
Reached from `SKILL.md` Step 2 when the classified artifact is a skill, an agent/subagent
definition, or a hook, instruction or prompt. Route a skill to `skill-author`, an agent to
`agent-author`, and a hook, instruction or prompt to `primitive-author`. The branches differ on
the author skill only — all verify the result with `factory-audit`, which detects the artifact type
itself — and everything below applies to all of them.
## Choose fork or inline
@@ -25,8 +26,8 @@ Fall back to an **inline invocation** — same conversation, no subagent — whe
## Two-tier verification
Both author skills already close out with their own inline audit, in the same context as the
authoring work: `skill-author` and `agent-author` each invoke `factory-audit` on what they wrote.
Every author skill already closes out with its own inline audit, in the same context as the
authoring work: `skill-author`, `agent-author` and `primitive-author` each invoke `factory-audit` on what they wrote.
That is tier one, and forge does not change it.
Tier two belongs to forge. Once the author skill's run has finished, spin up a separate

View File

@@ -0,0 +1,52 @@
---
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.
allowed-tools: Bash Read Write Edit
metadata:
version: "0.1.0"
category: factory
source_keys:
- apm-cli-installed-source
- apm-docs-llms-full
---
## 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.
- 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
| Target or intent | Primitive | Reference |
|---|---|---|
| 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 primitive — 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.
## Step 3 — Create or improve
| 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 |
| 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.
## Step 4 — Validate and close
1. Run `/factory-audit` on the file, inline in this context; resolve every FAIL before reporting done.
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 primitive, patch for a fix — unless this branch already bumped it for unreleased work. A primitive has no 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.

View File

@@ -0,0 +1,16 @@
{
"hooks": {
"FILL_IN_PascalCaseEvent": [
{
"matcher": "FILL_IN_matcher",
"hooks": [
{
"type": "command",
"command": "${PLUGIN_ROOT}/.apm/hooks/FILL_IN_script.sh",
"timeout": 10
}
]
}
]
}
}

View File

@@ -0,0 +1,7 @@
---
description: "FILL IN: one sentence on what this rule governs"
applyTo: "FILL IN: glob, e.g. **/*.{ts,tsx}"
---
FILL IN: the rule, as direct second-person guidance. Put any rationale Claude needs here — the
description above never reaches Claude.

View File

@@ -0,0 +1,8 @@
---
description: "FILL IN: one user-facing action naming the skills it steers"
input:
- FILL_IN_name: "FILL IN: what the user supplies"
---
FILL IN: the message the user would otherwise type, steering existing skills or agents by name.
Use ${input:FILL_IN_name} where the value belongs.

View File

@@ -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.

View File

@@ -0,0 +1,51 @@
---
source_keys:
- apm-cli-installed-source
- apm-docs-llms-full
---
# Authoring an apm instruction
Reached from `SKILL.md` Step 1 for an instruction. Run the Gate, then write against the checklist,
then return to `SKILL.md` Step 3.
## Gate
An instruction is a scoped rule: it applies when the agent touches files matching its `applyTo`
glob. On Claude it deploys to `.claude/rules/<stem>.md` with `applyTo` renamed to `paths:`.
- **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.
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.
## Checklist
Copy `assets/templates/name.instructions.md.template` and drop `.template` only on the final path.
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.
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`
collision is silently overwritten.
Should:
6. Write `applyTo` as a scalar string, not a YAML list. Copilot receives the source verbatim, and
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.
9. Keep relative markdown links resolvable from the source file.
10. Check the glob against the tree: one that matches nothing never fires, and one broader than the
rule's real scope spends context on every file it touches.

View File

@@ -0,0 +1,59 @@
---
source_keys:
- apm-cli-installed-source
- apm-docs-llms-full
- adr-0029-prompt-house-rule
---
# Authoring an apm prompt
Reached from `SKILL.md` Step 1 for a prompt. Run the Gate, then write against the description
contract and the checklist, then return to `SKILL.md` Step 3.
## Gate
This repo holds a prompt to ADR-0029, which is stricter than apm: apm calls a prompt "a callable
program", but on Claude it deploys as a command that is a skill in every respect except that it
keeps fewer frontmatter keys, apm drops `disable-model-invocation` so it can never be made
user-only, and Codex receives no prompts at all. A prompt that carries procedure is therefore a
worse skill on every harness.
- **Reusable know-how, steps, gotchas, bundled files, or anything the model should find on its
own** → a skill. Stop and hand to `skill-author`; if a short steering message is still wanted
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.
## Description contract
One plain, user-facing sentence stating the action and naming the skills 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.
## Checklist
Copy `assets/templates/name.prompt.md.template` and drop `.template` only on the final path.
Must:
1. The path is `.apm/prompts/<name>.prompt.md`, directly in that directory, not a symlink. `<name>`
is a safe path segment and unique across `.apm/prompts/` and the package root; it becomes the
Copilot filename and the Claude `/command` name.
2. `description` is present and non-empty, per the contract above.
3. Every `input:` name matches `^[A-Za-z][\w-]{0,63}$`, written in the object form
`- pr_number: "The PR to review"`. Never copy apm's published `- name: pr_number` /
`description: …` example: apm reads the map's keys, so it produces the arguments `name` and
`description`.
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.
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.

View File

@@ -0,0 +1,26 @@
# Sources
## apm-cli-installed-source
- **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`
- **Contributing files:** SKILL.md, references/hook.md, references/instruction.md, references/prompt.md
- **Status:** `extracted`
## apm-docs-llms-full
- **URL:** https://microsoft.github.io/apm/llms-full.txt
- **Research doc:** plugins/kyberforge/docs/research/docs/microsoft-apm/sources.md
- **Description:** Published apm docs bundle — the "Hooks and commands" guide's canonical hook shape, `${PLUGIN_ROOT}`, reach via `targets:` rather than deprecated filename routing, and "reach for a skill, instruction, or prompt first"; the "Author a prompt" guide's one-intent rule
- **Contributing files:** SKILL.md, references/hook.md, references/instruction.md, references/prompt.md
- **Status:** `extracted`
## adr-0029-prompt-house-rule
- **URL:** docs/adr/0029-prompts-are-thin-user-triggered-steering-messages.md
- **Research doc:** none
- **Basis:** docs/adr/0029-prompts-are-thin-user-triggered-steering-messages.md
- **Description:** The repo's prompt house rule — a prompt is a single-intent, user-triggered steering message with no procedure — and its description contract: one user-facing sentence naming the steered skills, no trigger or boundary clause
- **Contributing files:** references/prompt.md
- **Status:** `extracted`

View File

@@ -39,10 +39,11 @@ Authoring source lives in `.apm/`; it is the only content source and the only th
| Skill | Description |
|---|---|
| `forge` | Grill an unclassified "I want to add something" request, decide whether it's a skill, agent, plugin, or marketplace entry, then route to the matching author skill |
| `forge` | Grill an unclassified "I want to add something" request, decide whether it's a skill, agent, hook, instruction, prompt, plugin, or marketplace entry, then route to the matching author skill |
| `skill-author` | Create or improve a skill from scratch, audit findings, or inline feedback |
| `agent-author` | Author an agent definition file |
| `factory-audit` | Audit a skill directory or an agent definition — structure, provider safety, description and body quality, and provenance; produces a findings report. Auto-detects which of the two it was handed (ADR-0025) |
| `primitive-author` | Create or improve an apm hook, instruction or prompt, gated on whether it should be one at all (ADR-0029 for prompts) |
| `factory-audit` | Audit a skill directory, an agent definition, or an apm hook, instruction or prompt — structure, provider safety, description and body quality, and provenance; produces a findings report. Auto-detects which it was handed (ADR-0025) |
| `apm-install` | Install or upgrade the apm CLI and set up the agent runtimes it drives (Copilot CLI, Codex, Gemini, generic llm) |
| `apm-workflow` | Author apm.yml, scaffold an apm package/marketplace, install dependencies, and compile/pack/publish/audit apm content |