From 5e296bcfef6f7c79c5d9e473752a0f8687f63184 Mon Sep 17 00:00:00 2001 From: Defame1297 Date: Tue, 11 Aug 2026 16:39:54 +0000 Subject: [PATCH] docs(kyberforge): research all five APM primitive schemas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit skill-author/agent-author's #89 retarget needs to know exactly how each .apm/ primitive compiles to Claude Code and Copilot CLI output. The existing microsoft-apm corpus only had a full schema for skills and one minimal example for agents, and nothing for prompts/instructions/hooks. Deepened via APM's own Python source (not just docs) where prose was thin. Key finding for #89: agents have no per-target integrator, so apm compile does a naive verbatim copy to both Claude and Copilot, unlike prompts/instructions/hooks which each get real per-target reconstruction. That means the agent primitive's `tools:` field can't express both harnesses' incompatible vocabularies at once — a real upstream gap, not something we can schema our way around. --- .../microsoft-apm/agent-primitive-schema.md | 65 ++++++++++++++++++ .../microsoft-apm/hooks-primitive-schema.md | 59 +++++++++++++++++ .../instructions-primitive-schema.md | 66 +++++++++++++++++++ .../microsoft-apm/prompt-primitive-schema.md | 53 +++++++++++++++ .../research/docs/microsoft-apm/sources.md | 9 ++- 5 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 plugins/kyberforge/docs/research/docs/microsoft-apm/agent-primitive-schema.md create mode 100644 plugins/kyberforge/docs/research/docs/microsoft-apm/hooks-primitive-schema.md create mode 100644 plugins/kyberforge/docs/research/docs/microsoft-apm/instructions-primitive-schema.md create mode 100644 plugins/kyberforge/docs/research/docs/microsoft-apm/prompt-primitive-schema.md diff --git a/plugins/kyberforge/docs/research/docs/microsoft-apm/agent-primitive-schema.md b/plugins/kyberforge/docs/research/docs/microsoft-apm/agent-primitive-schema.md new file mode 100644 index 0000000..2587fe0 --- /dev/null +++ b/plugins/kyberforge/docs/research/docs/microsoft-apm/agent-primitive-schema.md @@ -0,0 +1,65 @@ +--- +topic: agent-primitive-schema +source_keys: + - context7-microsoft-apm +--- + +## Agent frontmatter is not a fixed APM-defined schema + +APM's design principle (PRINCIPLES.md, P1 "No invented primitive frontmatter") is that APM emits to the canonical schema of each *upstream* harness and does not invent its own frontmatter keys. Consequently there is no single APM-owned "complete field list" for `.apm/agents/*.agent.md` the way there is for `apm.yml`. The docs describe six fields as commonly used, and treat everything else as pass-through content the author writes for whichever harness(es) they target: + +| Field | Required | Purpose | Recognized by | +|---|---|---|---| +| `name` | Recommended | Display identifier; falls back to the filename stem if omitted | Universal, but Kiro derives identity from the deployed file path instead and strips this field | +| `description` | Yes | Used for agent discovery (e.g. by Cascade/Copilot) | Universal | +| `model` | Optional | Model the harness switches to when the agent is invoked | Copilot, Claude, Grok Build, Cursor, OpenCode (not Kiro's runtime behavior beyond passthrough; not meaningfully supported by Codex) | +| `tools` | Optional | Whitelist of callable tools/capabilities | Copilot, Claude, Grok Build, Cursor, OpenCode, Kiro (each with different accepted value shapes — see below) | +| `color` | Optional | Display color in harness UI | Copilot, Claude, OpenCode only | +| `handoffs` | Optional | Named agents (or structured objects) this agent can delegate to | Universal in principle; concretely modeled as a field on the internal `Chatmode` primitive (`list[str \| dict] \| None`), suggesting `.agent.md` is treated as a thin transform target rather than a fully independent, strongly-typed primitive internally | + +**Claude Code fields NOT documented anywhere in APM's corpus:** `isolation`, `maxTurns`, `effort`, `memory`, `permissionMode`, `disallowedTools`, `background`, `initialPrompt`. **Copilot CLI tool-alias shorthand** (`execute`/`read`/`edit`/`search`/`agent`/`web`) is also undocumented in APM's material — Copilot's agent `tools` are documented only as the `ToolName: true/false` map form. This is a genuine documentation gap, not an oversight on our part: APM's stated philosophy is to stay out of the way of harness-specific fields, so anything beyond the six-field table above is simply not modeled, validated, or discussed by APM — it would ride through compilation as opaque frontmatter for "verbatim" targets (see below) and be silently dropped for targets that do field-level reconstruction (Codex, Kiro). + +## Compile-time mapping per target + +`apm compile` reads `.apm/agents/*.agent.md` and reconciles per-target output according to the `targets`/`compilation` block in `apm.yml`. Behavior is verbatim-copy for most targets, with three targets doing real field-level transformation: + +| Target | Output path | Field treatment | +|---|---|---| +| Copilot | `.github/agents/.agent.md` | Verbatim | +| Claude | `.claude/agents/.md` | Verbatim | +| Grok Build | `.grok/agents/.md` | Verbatim | +| Cursor | `.cursor/agents/.md` | Verbatim | +| OpenCode | `.opencode/agents/.md` | Verbatim, but gated by strict validation (see below) | +| Codex | `.codex/agents/.toml` | Reconstructed: only `name` and `description` map into TOML fields; the markdown body becomes `developer_instructions`. `model`/`tools` are **not yet** translated into Codex's MCP/tool transport config — effectively dropped | +| Kiro | `.kiro/agents/.md` | Reconstructed: keeps only `description`, `model`, `tools`; **strips `name` and any unknown field**; identity comes from the file's path, not frontmatter | +| Windsurf | Not deployed as an agent | No agents primitive exists for Windsurf — Cascade auto-invokes skills by `description`, so a separate agent construct would collide; author as a skill instead | +| Gemini | Not deployed as an agent | Gemini has no agents primitive either; content is folded into `GEMINI.md` context instead | + +**No `claude:`/`copilot:` provider-override or passthrough sub-key exists anywhere in APM's schema or docs.** This was checked specifically (frontmatter reference doc, manifest schema, compile reference, prompts pitfalls page) — the only analogous behavior found is for the *prompts* primitive, where APM documents that only five frontmatter keys are preserved across all targets and provider-only keys (`author`, `mcp`, `parameters`) are silently stripped on every provider except Copilot. No equivalent table exists for the agent primitive beyond the six-field summary above and the per-target rows in this table. There is no way for a single vendor-neutral `.agent.md` to carry a provider-only setting (e.g. Claude Code's `isolation` or `permissionMode`) through compilation to just one target — the field would either pass through verbatim (Claude/Copilot/Grok/Cursor, since those are unvalidated copies) or get silently dropped (Codex, Kiro, and OpenCode if it fails that target's strict shape check). + +## The `tools:` field's shape is target-dependent, not fixed + +APM does not enforce one canonical shape. Different targets expect different shapes, and since APM doesn't invent or normalize frontmatter (P1), the author is effectively writing in whichever shape their primary target consumes: + +- **Map of booleans** — `tools: {Read: true, Grep: true}` — the form shown in APM's own canonical example and the form **required** by OpenCode's strict validator (`apm install -t opencode` warns at install time if `tools` is anything else, e.g. a list). +- **List of strings** — `tools: ["file-manager", "git-analysis"]` — appears in APM's own reference examples page; not glob-based and not `mcp__*`-namespaced in any example found. +- **Kiro capability tags** — a closed vocabulary distinct from tool names: `read`, `write`, `shell`, `web`, `subagent`, `knowledge`, `context`, `todo_list`, `@mcp`, `@builtin`, `*`. Kiro **fails closed** (refuses the whole write, no partial deploy) if any value in `tools` falls outside this set. +- No documented support for glob patterns or `mcp__*`-prefixed tool-name patterns as a `tools:` value shape anywhere in the corpus. + +Net effect for retargeting `agent-author`: there's no single `tools:` normalization APM performs for you. If the skill needs to emit a `.apm/agents/*.agent.md` that survives compilation to both Claude Code and Copilot CLI cleanly, it should use the `{ToolName: true}` map form (both targets treat agents as verbatim copies, so whatever is written is what each harness receives directly — meaning the shape must already match what Claude Code's own agent frontmatter and Copilot's agent frontmatter expect natively, since APM performs no translation for either). + +## Validation, naming, and size constraints + +There is no dedicated `Agent`/`AgentPrimitive` validation function or dataclass in APM's own codebase (`apm_cli/models/validation.py`, `apm_cli/primitives/models.py`) — validation logic exists for `apm.yml`, `SKILL.md`, `plugin.json`, hooks, and other primitives, but `.agent.md` is not independently modeled or validated by generic APM code. What constraints exist are documented as authoring guidance rather than enforced checks, plus the per-target strict checks already covered above (OpenCode's tools/color shape check, Kiro's fail-closed tool allowlist): + +- **Naming**: avoid the names `default` or `start` — they collide with `apm run` script name resolution. +- **Body size**: keep the markdown body under ~300 lines to preserve context window budget. +- **Body structure guidance**: open with role and scope in two sentences; define boundaries and expected output artifacts; don't duplicate global/workspace instructions the agent already inherits — override or extend only. +- **File location/extension**: must live at `.apm/agents/.agent.md`. +- No documented uniqueness enforcement, required-field enforcement, or file-size hard limit beyond the above authoring guidance — this is a real gap, not an omission on our part: it simply isn't implemented as a checked rule anywhere in the source. + +## Skill primitive: compilation is a straight copy, confirmed + +`apm install`/`apm compile` never rewrite or rename `SKILL.md` per target — deployment is a literal directory copy (`shutil.copytree`) of the whole skill folder (`SKILL.md` plus `scripts/`, `references/`, `assets/`, `examples/`), with symlinks and cache markers filtered out. `SKILL.md`'s content is treated as immutable; only its *placement* varies by target. This is on top of, not a contradiction of, what's already in `configuration.md`/`examples.md`. + +The one nuance not previously captured: **path routing changed via a "skills convergence."** Most targets (Copilot, Cursor, Codex, Gemini, OpenCode, Windsurf, and the explicit `agent-skills` target) now converge on a single shared `.agents/skills//SKILL.md` location to avoid duplicate copies across harnesses. Three targets still use harness-native paths because their default file scan looks only in their own directory: Claude (`.claude/skills/`), Kiro (`.kiro/skills/`), and Grok Build (`.grok/skills/`). The pre-convergence, per-harness-copy behavior can be restored with `--legacy-skill-paths` or `APM_LEGACY_SKILL_PATHS=1`. `includes:`/`compilation.placement:` (as already documented in `configuration.md`) apply to instructions/prompts placement, not to skills — no skill-specific effect from those keys was found anywhere in the corpus. diff --git a/plugins/kyberforge/docs/research/docs/microsoft-apm/hooks-primitive-schema.md b/plugins/kyberforge/docs/research/docs/microsoft-apm/hooks-primitive-schema.md new file mode 100644 index 0000000..afbcb42 --- /dev/null +++ b/plugins/kyberforge/docs/research/docs/microsoft-apm/hooks-primitive-schema.md @@ -0,0 +1,59 @@ +--- +topic: hooks-primitive-schema +source_keys: + - context7-microsoft-apm + - apm-github-repo +--- + +## File location, naming, and format — confirmed `.json`, not assumed + +`.apm/hooks/*.json` (legacy fallback: bare `hooks/*.json` at package root, still discovered — `_has_hook_json()` checks both `hooks/` and `.apm/hooks/`). This is genuinely JSON, not YAML or Markdown-with-frontmatter like every other primitive — confirmed directly from source (`apm_cli/integration/hook_integrator.py` module docstring: "Integrates hook JSON files...") and from `apm_cli/models/validation.py`, which states a hook-only package's files define "hook handlers per the Claude Code hooks specification" — i.e. the canonical authoring shape APM expects is Claude Code's own native hook JSON shape, not an APM-invented one. This is consistent with APM's general P1 principle (no invented primitive frontmatter/format) extending even to hooks: author in whichever native harness shape you like, and APM normalizes. + +**Accepted input shapes** (APM normalizes both into an internal vendor-neutral IR before rendering per target): + +```json +// "Nested" wrapper (what the docs' canonical example shows) +{ "hooks": { "PreToolUse": [ { "hooks": [ {"type": "command", "command": "./scripts/validate.sh", "timeout": 10} ] } ] } } + +// "Naked" top-level settings-slice (Claude Code settings.json shape, unwrapped) +{ "PreToolUse": [ { "hooks": [ {"type": "command", "command": "./scripts/validate.sh", "timeout": 10} ] } ] } +``` + +Both are accepted; APM's discovery/parsing layer detects and unwraps either. There is no separate `Hook`/`HookPrimitive` dataclass in `primitives/models.py` (unlike `Instruction`) — hooks are represented instead by a dedicated vendor-neutral IR (`apm_cli/integration/hook_ir.py`): `HookHandler(command, platform="all", timeout_seconds, provenance, metadata)` grouped into `HookBinding(event, handlers, matcher, provenance, metadata)` grouped into `HookDocument(bindings)`. This IR is populated during install-time integration, not during the generic primitive-discovery pass used for instructions/contexts/agents. + +**Event names are case-convention-sensitive by target and get remapped, not just passed through.** Author in either PascalCase (Claude convention: `PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `SessionStart`, `Stop`) or camelCase (Copilot convention: `preToolUse`, `postToolUse`, etc.) — `_HOOK_EVENT_MAP` per-target dictionaries translate between them during merge/deploy. An event name whose casing doesn't match the target's expected convention *and* has no explicit mapping entry triggers a non-fatal warning at install time (`_emit_hook_event_diagnostics`) — not a hard failure, but a real signal that the event likely won't fire. + +**Script path placeholders** are rewritten per target during deploy: `${CLAUDE_PLUGIN_ROOT}/path`, `${CURSOR_PLUGIN_ROOT}/path`, `${PLUGIN_ROOT}/path`, and bare `./path` all get resolved relative to the package root and rewritten to whatever the target expects; bare system commands (no path separators) pass through unchanged. + +## Compile-time mapping per target — both are real reconstruction, differently shaped + +Neither Claude nor Copilot receives a byte-verbatim copy of the source hook JSON — this is a genuine, structural transform on both sides, driven by `apm_cli/integration/hook_native_formats.py` and `hook_integrator.py`. + +**Claude Code — merged into `.claude/settings.json`, not a standalone file.** `claude` is registered in `_MERGE_HOOK_TARGETS` with `config_filename="settings.json"`, `schema_strict=True`. Behavior (per the integrator's own class docstring: "Claude: Merged into .claude/settings.json hooks key + .claude/hooks//"): +- Event bindings are merged into the `"hooks"` key of `.claude/settings.json`, using Claude's native nested-matcher-group shape (`{"hooks": {"PreToolUse": [{"hooks": [{"type": "command", "command": "...", "timeout": N}]}]}}`), with PascalCase event names. +- Any referenced script files are physically copied to `.claude/hooks//`, and the `command` field is rewritten to point at the copied location. +- An ownership sidecar (`apm-hooks.json`) tracks which entries in the shared `settings.json` were APM-installed, so `apm install`/uninstall can cleanly add/remove only its own entries without clobbering hand-authored hooks a user already had in that file. + +**Copilot CLI — dedicated per-file deployment, flat/camelCase, field-renamed.** `copilot` is deliberately **not** in `_MERGE_HOOK_TARGETS` (confirmed in `_hook_dropped_targets.py`: "Names not registered in `_MERGE_HOOK_TARGETS` (e.g. `copilot`, which uses per-file, not merged, hook deployment...)"). Instead `PrimitiveMapping("hooks", ".json", "github_hooks")` deploys a dedicated file per source hook file. The native Copilot shape differs structurally from Claude's, per the module docstring: +```json +{ + "version": 1, + "hooks": { "preToolUse": [ {"type": "command", "bash": "./scripts/validate.sh", "timeoutSec": 10} ] } +} +``` +Differences from the Claude/source shape: flat arrays (no nested matcher-group wrapper), camelCase event keys, a required top-level `"version": 1`, and handler commands split by platform (`bash` / `powershell` keys) instead of a single `command` key, with `timeoutSec` replacing `timeout`. + +## Compile-time file placement + +| Target | Output location | Mechanism | +|---|---|---| +| Claude Code | `.claude/settings.json` (`"hooks"` key, merged) + scripts copied to `.claude/hooks//` | Merge into existing shared config file, ownership tracked via `apm-hooks.json` sidecar | +| Copilot CLI | `.github/hooks/.json` | Dedicated per-file deploy, reshaped to Copilot's flat/camelCase/`version:1` schema | + +## Validation constraints and gotchas + +- **Copilot's native payload has an enforced shape** (`_validate_copilot_payload`): top-level `"version"` must equal `1`; `"hooks"` must be an object; each event's value must be a list; each entry must be an object; if an entry has a `"hooks"` key, its value must be a list of objects. These errors are collected and surfaced before any file is written (fail before mutation, not after). +- **Malformed existing config fails closed, not silently.** If `.claude/settings.json` (or another merge target's config) is unreadable/malformed JSON, APM leaves it **byte-identical** and logs an actionable warning rather than overwriting or corrupting it — the same fail-closed posture applies to orphaned `apm-hooks.json` sidecars when their native JSON counterpart is already gone. +- **Dropping a target from `apm.yml`'s `targets:` list does not auto-clean its merged hook entries** unless `apm install`/reconcile logic explicitly walks the complement set (`reconcile_dropped_targets`) — a real, documented gap the code works around rather than a design guarantee; relying on "just remove the target and hooks disappear" is not safe without a fresh `apm install`. +- **Event-casing mismatches are warnings, not errors** — a hook authored with the wrong casing for a target and no applicable rename mapping will silently not fire at runtime; APM only logs a warning at install time, it does not block the install or refuse to deploy the file. +- **No dedicated `Hook`/`HookPrimitive` validation dataclass** exists comparable to `Instruction.validate()` — validation is distributed across `_validate_copilot_payload` (Copilot-shape-specific) and general JSON-parseability checks, not a single primitive-level contract. This mirrors the same "no independent validation model" gap already documented for the agent primitive. diff --git a/plugins/kyberforge/docs/research/docs/microsoft-apm/instructions-primitive-schema.md b/plugins/kyberforge/docs/research/docs/microsoft-apm/instructions-primitive-schema.md new file mode 100644 index 0000000..9799d1a --- /dev/null +++ b/plugins/kyberforge/docs/research/docs/microsoft-apm/instructions-primitive-schema.md @@ -0,0 +1,66 @@ +--- +topic: instructions-primitive-schema +source_keys: + - context7-microsoft-apm + - apm-github-repo +--- + +## File location, naming, and frontmatter + +`.apm/instructions/*.instructions.md`. Confirmed as the genuine required extension (not assumed) via APM's own discovery glob in `apm_cli/primitives/discovery.py`: `**/.apm/instructions/*.instructions.md` (and the `.github/instructions/` mirror, plus a bare `**/*.instructions.md` fallback). + +Unlike prompts and hooks, instructions **do** have a small, concretely modeled dataclass — `apm_cli.primitives.models.Instruction` — because instructions feed APM's own compile pipeline (they get folded into root context files), not just pass-through deployment: + +```python +@dataclass +class Instruction: + name: str + file_path: Path + description: str + apply_to: str # from frontmatter key "applyTo"; empty means global/unconditional + content: str + author: str | None = None + version: str | None = None + source: str | None = None +``` + +Frontmatter fields: `description` (required by convention — its absence is a validation error) and `applyTo` (a glob or comma-separated glob list, or a YAML sequence — APM normalizes all three input shapes into one canonical comma-separated form internally via `normalize_apply_to`/`parse_apply_to`). No `applyTo` means the rule is treated as **unconditional** — folded into root context files as always-on guidance rather than scoped to specific paths. + +`Instruction.validate()` produces these built-in errors/warnings: +- Missing `description` → error: `"Missing 'description' in frontmatter"`. +- Missing `applyTo` → warning-level: `"No 'applyTo' pattern specified -- instruction will apply globally"` (not fatal — it's accepted, just broad). +- Empty body → error: `"Empty content"`. + +## Compile-time mapping: two entirely different mechanisms per target + +This is the biggest divergence from the agent/skill/prompt primitives, and the one most likely to surprise: **Claude Code does not get a verbatim copy of the `.instructions.md` file at all.** + +**Copilot CLI — verbatim, native primitive.** `PrimitiveMapping("instructions", ".instructions.md", "github_instructions")` on the `copilot` target has no `output_compare` flag, so `InstructionIntegrator` copies content through unchanged, preserving the original `applyTo:` frontmatter byte-for-byte (per the integrator's own docstring: "Copilot: `.github/instructions/` (verbatim, preserving applyTo:)"). This is deployed by `apm install`, not `apm compile`. + +At **Copilot user scope only** (`~/.copilot/`), individual files are not deployed — Copilot CLI at user scope reads a single `copilot-instructions.md`, so APM concatenates all instructions into that one file instead (`user_primitive_overrides: {"instructions": PrimitiveMapping("", ".md", "copilot_user_instructions")}`). Project-scope behavior (per-file, `.github/instructions/`) is unaffected. + +**Claude Code — real reconstruction into `.claude/rules/`, with field-dropping.** `PrimitiveMapping("rules", ".md", "claude_rules", output_compare=True)` marks this as one of APM's four "rule formats" (`RULE_FORMATS = {cursor_rules, claude_rules, windsurf_rules, kiro_steering}`) that transform their source rather than copy it. `InstructionIntegrator._convert_to_claude_rules()`: + +- Parses the source frontmatter and pulls only `applyTo` — **`description` is dropped entirely**, not carried into the output in any form. +- Converts `applyTo` into a `paths:` YAML list (one `parse_apply_to()`-split glob per line), e.g. `applyTo: "**/*.py"` → `paths:\n - "**/*.py"`. +- If there was no `applyTo` (unconditional instruction), the output has **no frontmatter at all** — just the raw body, matching Claude's convention that files without `paths:` in `.claude/rules/` apply unconditionally. +- Filename is renamed: `.instructions.md` → `.md` (the primitive's `extension` field, `.md`, replaces the source suffix — this is the general rule for every `output_compare=True` "rule format"). + +This is architecturally the same category of lossy, real transformation the prior agent-primitive research found for Codex/Kiro agents — except here it's the default behavior for Claude specifically (not an opt-out edge case), and it applies even though Claude and Copilot are both first-class, actively-supported targets. + +## Compile-time file placement + +| Target | Output path | Transform | +|---|---|---| +| Copilot CLI (project scope) | `.github/instructions/.instructions.md` | Verbatim byte copy, `applyTo:` preserved as-is | +| Copilot CLI (user scope, `~/.copilot/`) | `~/.copilot/copilot-instructions.md` | Concatenated — all instructions merged into one file, because Copilot CLI at user scope reads only that single file | +| Claude Code | `.claude/rules/.md` | Reconstructed: `applyTo` → `paths:` YAML list; `description` dropped; no frontmatter at all if unconditional | + +Additionally, **`apm compile`** (distinct from `apm install`) can also fold instruction content directly into root context files — `AGENTS.md` (single-file or per-directory "distributed" mode) and the Claude-specific parallel format `CLAUDE.md`/per-directory `CLAUDE.md` — grouped by directory using `applyTo` pattern analysis (`context_optimizer.optimize_instruction_placement`). To avoid duplicating content between the native `.claude/rules/`+`.github/instructions/` deployment (from `apm install`) and this root-context fold-in (from `apm compile`), a `skip_instructions` config flag (and `compilation.placement.min_instructions_per_file` in `apm.yml`) actively suppresses the redundant copy in AGENTS.md/CLAUDE.md once native per-target files exist — `apm compile --target claude --force-instructions` overrides this dedup when an author explicitly wants both. + +## Validation constraints and gotchas + +- **The `description` field is real for Copilot but silently discarded for Claude.** An author who relies on `description` to explain *why* a rule exists (common practice, since Copilot's `.instructions.md` UI can surface it) gets that context deleted on every Claude compile — there's no config to keep it as a comment or otherwise. +- **No content-level validation for the `paths:` conversion** — if `applyTo` contains a pattern `parse_apply_to` can't split sensibly, the resulting `paths:` list is whatever falls out; no dedicated schema check catches a malformed glob before deploy. +- **Directory-distribution logic for AGENTS.md/CLAUDE.md is heuristic, not declarative** — `context_optimizer.optimize_instruction_placement` picks placement directories from `applyTo` patterns algorithmically; `compilation.placement.min_instructions_per_file` in `apm.yml` (default effectively 1) is the only tuning knob, and setting it above 1 causes under-populated directories to have their instructions bubbled up to the parent directory rather than dropped. +- **Same "no dedicated primitive validation function" gap noted for agents** — `Instruction.validate()` in `primitives/models.py` is the only validation, and it is invoked as part of the generic primitive-discovery/compile pipeline, not as a standalone `apm audit` check comparable to what exists for `apm.yml` itself. diff --git a/plugins/kyberforge/docs/research/docs/microsoft-apm/prompt-primitive-schema.md b/plugins/kyberforge/docs/research/docs/microsoft-apm/prompt-primitive-schema.md new file mode 100644 index 0000000..b5331bc --- /dev/null +++ b/plugins/kyberforge/docs/research/docs/microsoft-apm/prompt-primitive-schema.md @@ -0,0 +1,53 @@ +--- +topic: prompt-primitive-schema +source_keys: + - context7-microsoft-apm + - apm-github-repo +--- + +## File location, naming, and frontmatter + +`.apm/prompts/*.prompt.md` (also discovered at the package root). Filename minus the `.prompt.md` suffix becomes the prompt's identity — used verbatim as the Copilot filename and, after transformation, as the Claude command name. No required-extension ambiguity: it is genuinely `.prompt.md`, confirmed both in docs and in APM's own `PromptIntegrator.find_prompt_files` (`*.prompt.md`) and `CommandIntegrator.find_prompt_files` (same glob). + +There is no single closed frontmatter schema — APM's P1 "no invented primitive frontmatter" principle applies here too, so a prompt author writes whatever keys their primary target needs and APM passes or drops per-target. Keys seen in APM's own docs/examples: + +| Field | Purpose | +|---|---| +| `description` | Shown in Copilot's prompt picker / used for discovery | +| `input` | List of parameter names (simple list, or list of `{name: description}` objects) referenced in body as `${input:name}` | +| `allowed-tools` (or `allowedTools`) | Tool allowlist for the prompt's execution | +| `argument-hint` (or `argumentHint`) | Human-readable hint for expected arguments | +| `model` | Model override when the prompt runs | +| `author`, `mcp`, `parameters` | Cursor/other-target-specific metadata — **not preserved** by the shared Claude/Cursor command transformer (see below) | + +**Workflow-prompt-only keys** (Copilot App / Copilot Workflows, not Copilot CLI): `name`, `interval` (`manual`/`hourly`/`daily`/`weekly`), `schedule_hour` (0–23 UTC), `schedule_day` (0–6, weekly only), `mode` (`interactive`/`plan`), `reasoning_effort`. These are flat top-level keys on the same `.prompt.md` file, consumed only by the Copilot App scheduler integration — irrelevant to Claude Code / Copilot CLI compilation and should not be treated as universal prompt schema. + +## Compile-time mapping: verbatim for Copilot, real reconstruction for Claude + +**Copilot CLI target — verbatim copy.** `PrimitiveMapping("prompts", ".prompt.md", "github_prompt")` on the `copilot` target profile carries no `output_compare` flag, and `PromptIntegrator.copy_prompt()` reads the source file and writes it out unchanged (only markdown link targets get rewritten) via `copy_prompt: "Copy prompt file verbatim with link resolution."`. Every frontmatter key — including `author`, `mcp`, `parameters` — survives. Filename is untouched (`get_target_filename` returns `source_file.name`, "no -apm suffix"). + +**Claude Code target — real reconstruction into a slash command, with field-dropping.** There is no `prompts:` key at all in Claude's `TargetProfile.primitives` dict; instead prompts route through the shared `CommandIntegrator`, which transforms `.prompt.md` → Claude custom slash command markdown. `CommandIntegrator._transform_prompt_to_command()`: + +- Strips the `.prompt.md` suffix from the filename to derive `command_name`. +- Builds an entirely new frontmatter object containing **only** these preserved keys: `description`, `allowed-tools` (accepts `allowedTools` alias), `model`, `argument-hint` (accepts `argumentHint` alias). +- Maps APM's `input:` list to Claude's `arguments:` list, and synthesizes `argument-hint` from it if not already set. +- Rewrites body placeholders `${input:name}` / `${{input:name}}` to Claude's native `$name` syntax via regex substitution. +- Computes `dropped_keys = source_frontmatter_keys - preserved_keys` and surfaces it as an install-time diagnostic warning — so `author`, `mcp`, `parameters`, and any other non-listed key are silently dropped from the compiled output but *not* silently dropped from the user's awareness (a warning fires). +- Cursor reuses this exact same transformer (`claude_command` format_id) — same preserved-key set, same drops. + +## Compile-time file placement + +| Target | Output path | Transform | +|---|---|---| +| Copilot CLI | `.github/prompts/.prompt.md` | Verbatim byte copy (links resolved) | +| Claude Code | `.claude/commands/.md` | Reconstructed: only `description`/`allowed-tools`/`model`/`argument-hint`/`arguments` survive; `input:` → `arguments:`; `${input:x}` → `$x` | + +Invocation surface differs correspondingly: Copilot exposes it via the prompts picker UI (select by name); Claude exposes it as `/ ` (same pattern Cursor, OpenCode, Gemini CLI, and Windsurf's workflows menu use for their own compiled copies). + +## Validation constraints and gotchas + +- **Input-name validation is real, not just documentation.** `_extract_input_names()` enforces `[A-Za-z][\w-]{0,63}` on every name pulled from `input:`; anything that fails is dropped from `arguments:` and reported as a warning listing up to 5 rejected names (`input: rejected N invalid name(s) ... `). A malformed `input:` entry does not fail the install — it silently loses that one argument. +- **Filename-derived identity is security-checked.** `integrate_commands_for_target` calls `validate_path_segments(base_name, context="command filename")` specifically to reject a package shipping a `.prompt.md` file with a manipulated relative name (e.g. `../../evil.prompt.md`) that would otherwise escape the target commands directory. +- **The dropped-key warning is the only signal a Claude-only author gets** that Cursor-specific frontmatter (`author`, `mcp`, `parameters`) never reached the deployed file — there is no error, no hard failure, and no config flag to preserve those keys for Claude; the shared transformer's preserved-key list is fixed in code (`_PRESERVED_COMMAND_KEYS`), not configurable per package. +- **No dedicated `Prompt`/`PromptPrimitive` validation class exists** in `apm_cli/models/validation.py` or `apm_cli/primitives/models.py` — same gap pattern documented for the agent primitive. `apm.yml`'s `type: prompts` package-content-type ("Commands/prompts only, no instructions or skills") is validated at the package-type-detection level, not the individual-prompt level. +- Slash commands and prompts share one source directory and one glob (`.apm/prompts/*.prompt.md`) — there is no separate `.apm/commands/` primitive; "command" is purely a per-target compiled *name* for the same source file, not a distinct authoring primitive. diff --git a/plugins/kyberforge/docs/research/docs/microsoft-apm/sources.md b/plugins/kyberforge/docs/research/docs/microsoft-apm/sources.md index 2c7ddf5..a198d17 100644 --- a/plugins/kyberforge/docs/research/docs/microsoft-apm/sources.md +++ b/plugins/kyberforge/docs/research/docs/microsoft-apm/sources.md @@ -4,5 +4,12 @@ - **URL:** context7:/microsoft/apm - **Description:** Microsoft APM (Agent Package Manager) — open-source dependency manager for AI agent configuration (skills, prompts, instructions, agents, hooks, MCP/LSP deps), applying a declare/lock/install/audit workflow. -- **Contributing files:** overview.md, installation.md, configuration.md, cli-reference.md, examples.md, troubleshooting.md, testing-and-validation.md, marketplace-and-registries.md, monorepo-and-repo-shapes.md +- **Contributing files:** overview.md, installation.md, configuration.md, cli-reference.md, examples.md, troubleshooting.md, testing-and-validation.md, marketplace-and-registries.md, monorepo-and-repo-shapes.md, agent-primitive-schema.md, prompt-primitive-schema.md, instructions-primitive-schema.md, hooks-primitive-schema.md +- **Status:** `extracted` + +## apm-github-repo + +- **URL:** https://github.com/microsoft/apm +- **Description:** APM's own Python source (`src/apm_cli/`) read directly for schema/compile-mapping ground truth where Context7's doc snippets were thin — specifically `primitives/models.py` and `primitives/discovery.py` (primitive dataclasses and discovery globs), `integration/prompt_integrator.py`, `integration/command_integrator.py`, `integration/instruction_integrator.py`, `integration/hook_integrator.py`, `integration/hook_native_formats.py`, `integration/hook_ir.py`, `integration/_hook_dropped_targets.py`, `integration/targets.py` (`KNOWN_TARGETS`/`PrimitiveMapping` per-target deploy config), `compilation/claude_formatter.py` and `compilation/distributed_compiler.py` (instruction fold-in to CLAUDE.md/AGENTS.md), and `models/validation.py`. +- **Contributing files:** prompt-primitive-schema.md, instructions-primitive-schema.md, hooks-primitive-schema.md - **Status:** `extracted`