# CorvinOS SkillForge — Canonical Skill Format

The authoritative format for every skill created via SkillForge (Layer 7),
whether hand-authored or generated by `skill_create`. Grounded in the actual
`registry._render_skill_md()`, the `skill_create` MCP schema, and `linter.lint()`
— a skill written to this spec will pass the fail-closed linter and load
correctly in every engine.

See also: [skills.md](skills.md) (lifecycle/runtime), [skill-template.md](skill-template.md)
(copy-paste scaffold).

---

## 1. The two layers of the format

A skill is **one Markdown file** (`SKILL.md`) = YAML front-matter + Markdown body.
There are two valid front-matter profiles:

| Profile | Fields | When |
|---|---|---|
| **Minimum** | `name`, `description` | Bundle/operator skills, the engine-facing slot mirror, hand-authored skills |
| **Forge-canonical** | `name`, `type`, `description`, `claim`, `references` | Everything created through `skill_create` (the registry renders these) |

> Rule of thumb: **author at least the Minimum; prefer the Forge-canonical set**
> for anything the registry manages (so promotion/grading and provenance work).
> Lifecycle data (grades, sha256, timestamps) is **never** in front-matter — it
> lives in `meta.json`, written by the system.

---

## 2. Front-matter fields

```yaml
---
name: <string>                 # REQUIRED
type: <enum>                   # REQUIRED for forge-canonical, omit for minimum
description: <string>          # REQUIRED
claim: {}                      # OPTIONAL (evidence map)
references: []                 # OPTIONAL (supporting paths/URLs)
---
```

### `name` — REQUIRED
- 1–128 chars, charset **`[A-Za-z0-9._]`** only.
- No `/`, no `..`, no leading/trailing `.`.
- Dotted namespaces are idiomatic: `corvinOS.review.backend`, `trading.score_reviews`.
- Must start with the namespace your persona is allowed to write (namespace gate;
  e.g. `code.*` for the code persona) — otherwise `skill.namespace_denied`.

### `type` — REQUIRED (forge-canonical)
Exactly one of:
- **`domain`** — domain knowledge / checklists / procedures (most common).
- **`persona-style`** — how a persona should sound or behave.
- **`repo-context`** — facts about a specific codebase/project.
- **`learned-experience`** — a lesson distilled from a past task outcome.

### `description` — REQUIRED
- One line. This is the **trigger surface**: the engine decides relevance from it.
- State **when to use** it and the **trigger words** (incl. German + English if the
  skill fires in chat). Good: *"Use when reviewing backend changes — state
  machines, error handling, deps. Triggers: 'review', 'backend', 'audit'."*
- Quote with single quotes if it contains `: @ # | & * ! [ ] { }` or a newline.

### `claim` — OPTIONAL
- Map of evidence backing the skill (`{}` if none). Keys/values stringified into YAML.
- Example: `claim: {source: "security-review", verified: "2026-06-15"}`.

### `references` — OPTIONAL
- List of doc/file paths or URLs that ground the skill (`[]` if none).
- Example: `references: ["docs/claude-ref/layer-security.md"]`.

---

## 3. Body authoring rules

The body is plain Markdown. Make it **instruction-shaped** — it is prompt-injected
into a future subprocess turn as **advisory domain knowledge**, not executable code.

Recommended section skeleton:

```markdown
# <Skill Title>

## When to use
Concrete triggers + when NOT to use.

## What to do
Numbered, imperative steps. The load-bearing rules.

## Constraints / invariants
Hard limits, must-nots, fail-closed rules.

## Example (optional)
One short, illustrative example.
```

Body guidance:
- **≤ 8192 bytes UTF-8** (hard cap — see §4). Split large topics into multiple skills.
- **< 40 % fenced code** (warning, not a reject). If a skill is mostly code, it
  should be a **Forge tool**, not a skill.
- Write WHY/HOW, not a code dump. Tight, imperative, deduplicated.
- English for repo-committed skills (repo language rule); runtime chat skills may
  be bilingual in the body.

---

## 4. Hard constraints (linter — FAIL-CLOSED, blocks creation)

The linter normalizes the body (NFKC + Cyrillic-confusable mapping, case-insensitive)
before matching. Any **error** rejects the skill; warnings only log.

| Check | Rejects when body contains | Severity |
|---|---|---|
| **Prompt-injection** | `ignore previous instructions`, `disregard the above`, `you are now`, `<\|im_start\|>`, `<\|im_end\|>`, a line starting `system:`, or a base64-like block ≥ 64 chars | ERROR |
| **Secret-leak** | AWS key `AKIA…`, GitHub PAT `ghp_…`, Anthropic key `sk-…`, PEM `-----BEGIN … PRIVATE KEY-----` | ERROR |
| **Persona-boundary** | `you can now use bash`, `bypass permissions`, `--dangerously-skip-permissions`, `you may execute` | ERROR |
| **Length** | body > 8192 bytes (UTF-8) | ERROR |
| **Code density** | > 40 % of body is fenced code | WARNING |

Authoring implication: never paste secrets, never embed framing tokens, never
instruct the agent to escalate its own permissions, never dump a long base64 blob.

### Scope of the linter — `skill_create` only

The linter runs **inside `registry.create()`** — i.e. only for skills generated
through SkillForge (`skill_create`). **Hand-authored bundle/operator skills**
(`operator/bundle/skills/…`, `operator/<plugin>/skills/…`) are loaded directly
by the bundle/plugin loader and are **not** passed through `lint()`, so the
8192-byte cap is **not enforced** on them. Several mature LDD bundle skills
legitimately exceed it (e.g. `loop-driven-engineering` ≈ 13 KB). They should
still honour the prose/secret/injection rules; if one is ever migrated into
SkillForge via `skill_create`, it must first be split to pass the cap.

---

## 5. Lifecycle metadata — system-managed (NOT authored)

Written by the registry into `meta.json` / `skills_registry.json`. Do **not** put
these in front-matter:

```jsonc
{
  "name": "...", "type": "...", "description": "...", "claim": {}, "scope": "session",
  "created_at": 1718400000.0,
  "created_by": "<persona>",
  "sha256": "<16-hex content hash>",   // content drift detection
  "grades": [ {"run_id": "...", "score": 0.7, "ts": 0, "notes": ""} ],
  "meta": {}
}
```

- **scope** (`task → session → project → user`) is chosen at create time
  (auto-detected if omitted) — not a front-matter field.
- **grades** drive promotion (see §6); auto-grade ≈ 0.7 (mention/paraphrase),
  outcome-grade 0.1–0.9 (rejection→approval).
- **sha256** lets the loader detect content drift → re-lint → suspend on failure.

---

## 6. Promotion gates (scope ladder)

| From → To | Gate |
|---|---|
| task → session | ≥ 1 grade with score > 0 |
| session → project | ≥ 3 grades **and** mean score ≥ 0.5 |
| project → user | operator `force=True` |

Only **project** and **user** scope are mirrored to the engine-facing slot
(`skills/dyn/<sanitized_name>/SKILL.md`, dots → underscores, `name`+`description` only).

---

## 7. Storage layout (source of truth)

```
<scope_root>/
├── skill-forge/
│   ├── skills_registry.json          # manifest {name: spec}
│   └── skills/<name>/
│       ├── SKILL.md                  # front-matter + body (THIS format)
│       └── meta.json                 # grades, sha256, created_at, scope
└── audit.jsonl                       # shared chain (ONE level up)
```

Audit events (metadata-only, hash-chained): `skill.create`, `skill.grade`,
`skill.delete`, `skill.namespace_denied`, `skill_forge.content_drift`,
`skill_forge.content_rehash`, `skill_forge.injection_suspended`.

---

## 8. `skill_create` call (forge-canonical)

```json
{
  "name": "corvinOS.review.backend",
  "type": "domain",
  "description": "Use when reviewing backend changes — state machines, error handling, deps. Triggers: 'review', 'backend', 'audit'.",
  "body_md": "# CorvinOS Review: Backend\n\n## When to use\n...",
  "claim": {"source": "security-review"},
  "scope": "session",
  "overwrite": false
}
```

Required: `name`, `type`, `description`, `body_md`. Optional: `claim`, `scope`,
`overwrite`. The registry renders front-matter + body, lints (fail-closed), writes
`SKILL.md` + `meta.json`, emits `skill.create`, and notifies `tools/list_changed`.
