diff --git a/standards/persona-standards.md b/standards/persona-standards.md new file mode 100644 index 000000000..7909c9725 --- /dev/null +++ b/standards/persona-standards.md @@ -0,0 +1,257 @@ +# Agentic Persona Standards + +Standard for defining, onboarding, and rolling out a new **agentic persona** +across the `petry-projects` fleet. + +A persona is a named agent role — Dev-Lead, PR-Reviewer, Bob (Scrum Master), +Mary (Analyst), Murat (Test Architect) — that helps the org **design, build, +test, deliver, and operate** its projects. This standard turns the pattern that +is already implicit across those personas into an explicit, repeatable +**Persona Definition of Done**: a manifest, a trigger checklist, a file layout, +an eval gate, and a canary onboarding step. + +> **Read first.** This standard builds on, and does not restate: +> [`agent-standards.md`](agent-standards.md) (required agent files, AgentShield, +> immutable files), [`ci-standards.md`](ci-standards.md) (stub/reusable tiers, +> channel-tag versioning, canary rings), and +> [`github-settings.md`](github-settings.md) (label taxonomy). Where those apply, +> follow them; this document only adds the persona-level wrapper. + +--- + +## 1. Principles + +1. **The manifest is the index-of-record.** Every persona has exactly one + `persona.yml` (validated against + [`personas/persona.schema.json`](personas/persona.schema.json)). It is the + single place a human can point at to see what a persona *is*, where it is + *defined*, how it is *triggered*, and how it *rolls out*. Everything else + stays where it already lives; the manifest just references it. + +2. **Unify over the frameworks, do not replace them.** We build on BMAD and the + BMAD Test Architecture module and intend to **contribute back** to them. The + manifest layers on top of a vendored framework agent — it points *into* + `frameworks/` at a pinned version and records any local override as an + explicit upstream-contribution candidate. Vendored files are never + hand-edited (see [`prompts/bmad/README.md`](https://github.com/petry-projects/.github-private) + in `.github-private` and each `frameworks/*/VENDOR.md`). + +3. **Advisory by default; write is opt-in.** A new persona is assumed to be + *advisory everywhere* (it comments, reviews, labels) and must **explicitly + opt into write access** per surface, each with a gate label and a trust + floor. This inverts the risk correctly: the blast radius of a new persona + starts at "leaves a comment," not "opens a PR." + +4. **Register once.** Ring membership and gate knobs live in exactly one place — + [`canary-rings.json`](canary-rings.json). The manifest references the registry + entry by `id`; it never restates rings or gates. `release/registry.yml` in + `.github-private` is derived from this registry, not maintained in parallel + (see §6). + +5. **No persona reaches `stable` without an eval gate.** The held-out eval + discipline in `.github-private` `evals/` (dev/holdout split, `holdout-guard.yml`) + is a promotion gate, not a nicety. + +--- + +## 2. Where a persona lives + +Two-repo split, consistent with the org boundary in `AGENTS.md` +("What lives where — .github vs .github-private"): + +| Artifact | Repo | Path | +|---|---|---| +| This standard + the manifest schema + the copy-me template | `.github` (org standards) | `standards/persona-standards.md`, `standards/personas/` | +| Ring registry (single source of truth) | `.github` | `standards/canary-rings.json` | +| **Persona instances** (`persona.yml` + layers + evals) | `.github-private` | `personas//`, plus the layer homes below | +| Framework agents & skills (vendored) | `.github-private` | `frameworks//…` | +| Copilot profiles | `.github-private` | `agents/.md` | +| Workflow prompt libraries | `.github-private` | `prompts//` | +| Caller stub + reusable workflow | per `canary-rings.json` `host` | `.github/workflows/…` | +| Eval sets | `.github-private` | `evals//` (dev/ + holdout/) | + +The **manifest is the spine**: `personas//persona.yml` names each layer's +path. A persona may use one layer (e.g. a lone Copilot profile) or several (a +framework agent + a workflow-prompt orchestrator + an eval set), but there is +always exactly one manifest. + +### Canonical instance layout (in `.github-private`) + +```text +personas// + persona.yml # the manifest (required) + README.md # short human note: what this persona is, how to invoke + +evals// # NOT under personas/ — lives in the repo eval tree so it + dev/cases.jsonl # inherits validate-cases.py + holdout-guard.yml for free + holdout/cases.jsonl # (proposer-visible dev split; CODEOWNER-gated holdout) +``` + +Eval sets deliberately live under the existing `evals//` tree rather than +beside the manifest: that reuses the held-out validator and the immutability +guard without forking them (co-locating under `personas//evals/` is possible +later only by extending `HOLDOUT_GUARDED_PREFIXES`, so it is not the default). + +The scaffold to copy is [`standards/personas/TEMPLATE/`](personas/TEMPLATE/). + +--- + +## 3. The definition layers + +We deliberately support more than one definition format, because the runtimes +genuinely differ. The manifest's `definition.layers[]` blesses whichever a +persona uses and points at it: + +| `kind` | What it is | Home | When | +|---|---|---|---| +| `framework-agent` | A vendored BMAD-style agent (`SKILL.md` + `customize.toml`) | `frameworks//…` | Persona is backed by an upstream framework we track and contribute to | +| `copilot-profile` | A single `agents/.md` with `name`/`description`/`tools` frontmatter | `agents/` | Human-invocable from GitHub.com / IDEs | +| `workflow-prompts` | A phase-keyed prompt library filled by a shell orchestrator | `prompts//` | Persona runs as automated CI (like `dev-lead`) | +| `gh-aw` | A GitHub Agentic Workflow `.md` compiled to `.lock.yml` | `.github/workflows/` | Self-contained event-triggered agent | + +**Framework-backed personas (the preferred path where a good upstream exists):** + +- `definition.layers[].framework` records `name`, `upstream_repo`, `vendor_pin` + (which **MUST** match the framework's `VENDOR.md`), and `skill`. +- Never hand-edit vendored files. Local behavior is layered via + `local_overrides` (e.g. a `customize.toml` overlay resolved base → team → user, + or an orchestration wrapper like `prompts/bmad/scrum-master.md` that runs the + vendored skill non-interactively). +- Any override that is a **general** improvement MUST set + `local_overrides.upstream_candidate: true` so it is tracked for contribution + back rather than becoming permanent private drift. + +All `SKILL.md` files still require `name` + `description` frontmatter +(AgentShield `valid-frontmatter`, `agent-standards.md`). + +--- + +## 4. The trigger matrix (the onboarding checklist) + +Every persona's `triggers` block is a **checklist of every surface it may act +on**. Fill one row per surface; anything unlisted falls back to `default_mode`. + +**Surfaces** (the work-item × event families): + +| Surface | Typical events | Notes | +|---|---|---| +| `issues` | `opened`, `labeled`, `commented` | Label gates arm write personas | +| `pull_request` | `opened`, `synchronize`, `ready_for_review`, `review_requested` | | +| `pull_request_review` | `submitted`, `created` (review comment) | | +| `discussion` | `created`, `labeled`, `commented` | Cannot run the agent inline — **must** bridge via `repository_dispatch`/`workflow_dispatch` (see `ci-standards.md`, issue #571) | +| `check_run` | `completed` | CI-failure follow-up | +| `schedule` | cron | Proactive / sweep work | +| `mention` | `@` in a comment | Reviewer-assignment counts here too | + +**Rules:** + +1. **`default_mode` is `advisory` or `off` — never `write`.** Write is only ever + an explicit per-surface opt-in. +2. **Every `mode: write` surface MUST declare a `gate_label`** (the label that + arms the persona, e.g. `dev-lead`) — schema-enforced. +3. **Every surface honours a trust floor.** Default `[OWNER, MEMBER, + COLLABORATOR]`; verify `author_association` before consuming agent quota + (this is the same trust boundary the PR-review and dev-lead paths already + apply). +4. **Every persona defines an `opt_out_label`** (`:hands-off`) that removes + an item from its automation entirely. +5. **Most personas should be advisory on most surfaces.** The working hypothesis + — a persona can usefully weigh in on nearly every work-item type — holds *for + advisory participation*. Reserve `write` for the few surfaces where the + persona genuinely authors changes, and gate each one. + +Label gates and trust checks are defined operationally in `ci-standards.md` +(§ agent-trigger labels) and must reuse the existing labels rather than mint +near-duplicates. + +--- + +## 5. Trust, permissions, and safety + +- **Trust floor** — set `trust.author_association_floor`; per-surface + `trust_floor` may tighten it but never loosen below it. +- **Permissions superset** — if the persona ships a caller stub + reusable, + `runtime.permissions` records the full set the reusable requests, and the + **caller stub MUST grant a superset** or the whole fleet fails with + `startup_failure` (`ci-standards.md`). The stub carries the standard + `SOURCE OF TRUTH` header, `secrets: inherit`, and channel-tag pin. +- **Immutable files** — a persona never modifies the files in + [`workflow-exemptions.json`](workflow-exemptions.json) (currently + `agent-shield.yml`). +- **AgentShield** — every definition layer is subject to the AgentShield scan; + a persona that fails the scan cannot be onboarded. + +--- + +## 6. Canary onboarding (the last step) + +Rollout is unchanged from the existing model (`ci-standards.md` §Canary rings, +`canary-rings.json`, `scripts/canary-rollout.sh`) — the persona standard only +makes the **registration** a single action. + +1. **Cut** an immutable `/vX.Y.Z` at a merged, CI-passed commit on the host. +2. **Register once** — add one entry under `agents.` in + [`canary-rings.json`](canary-rings.json): `host`, `reusable`, `run_workflow`, + `rings[]`, `gate`. This is the **only** place ring membership is written. +3. **Point the manifest at it** — `persona.yml`'s `canary.agent` MUST equal + `` and `canary.registry` MUST reference `canary-rings.json`. The manifest + restates nothing from the registry. +4. **Stage outward** one ring at a time — `next → ring0 → ring1 → stable` — each + a single central channel-tag move, gated by the soak/dwell defaults in the + registry. Roll back by moving the tag to the prior immutable `vX.Y.Z`. +5. **Eval gate** — the persona's `evals.path` must exist and pass by + `evals.required_before` (recommended: `stable`). + +### Single source of truth for rings + +`canary-rings.json` (public, in `.github`) is the **only hand-authored** ring +registry. Because all org repos are public, there is no topology to hide, so: + +- `.github-private` `release/registry.yml` is **derived from** + `canary-rings.json`, not maintained alongside it. Do not add a persona to both + by hand. +- `stable`'s `"*"` member is **resolved at runtime** (all org repos minus the + repos named in earlier rings). New consumers that adopt the caller stub join + the `stable` ring automatically; they need not be enumerated. + +This eliminates the historical drift between the two registries (divergent +`run_workflow` names and soak encodings) by construction. + +--- + +## 7. Definition of Done — the onboarding checklist + +A persona is "done" (ready for `stable`) when all of the following are true: + +- [ ] `personas//persona.yml` exists and validates against `persona.schema.json`. +- [ ] `definition.layers[]` names every implementing layer; framework layers pin + a `vendor_pin` matching the framework's `VENDOR.md`, and any local override + sets `upstream_candidate` truthfully. +- [ ] `triggers` fills one row per surface the persona acts on; `default_mode` + is `advisory`/`off`; every `write` surface has a `gate_label`; an + `opt_out_label` is defined. +- [ ] `trust.author_association_floor` is set; write surfaces gate on it. +- [ ] If it ships a reusable: caller stub grants a **superset** of + `runtime.permissions`, carries the `SOURCE OF TRUTH` header, and pins a + channel tag. +- [ ] AgentShield passes on all layers; no immutable file is touched. +- [ ] `evals//` holds `dev/` + `holdout/` splits with ≥ `evals.min_cases` + held-out cases; `validate-cases.py` passes and the eval gate is green. +- [ ] Exactly one `agents.` entry exists in `canary-rings.json`; + `persona.yml` `canary.agent` points at it and restates nothing. +- [ ] The persona has soaked through `next → ring0 → ring1` before `stable`. + +Copy [`standards/personas/TEMPLATE/`](personas/TEMPLATE/) to start; the worked +example is **Murat** (`personas/murat/` in `.github-private`), which wraps the +vendored `bmad-test-architecture` Test Architect. + +--- + +## 8. References + +- [`agent-standards.md`](agent-standards.md) — required agent files, AgentShield, immutable files +- [`ci-standards.md`](ci-standards.md) — stub/reusable tiers, channel-tag versioning, canary rings, trigger labels +- [`canary-rings.json`](canary-rings.json) — the ring registry (single source of truth) +- [`github-settings.md`](github-settings.md) — canonical label taxonomy +- [`personas/persona.schema.json`](personas/persona.schema.json) — the manifest schema +- [`personas/TEMPLATE/`](personas/TEMPLATE/) — copy-me scaffold diff --git a/standards/personas/TEMPLATE/README.md b/standards/personas/TEMPLATE/README.md new file mode 100644 index 000000000..03e9fc3b7 --- /dev/null +++ b/standards/personas/TEMPLATE/README.md @@ -0,0 +1,65 @@ +# Persona scaffold — copy me + +Copy `persona.yml` into **`.github-private/personas//`** to onboard a new +agentic persona. It is governed by +[`standards/persona-standards.md`](https://github.com/petry-projects/.github/blob/main/standards/persona-standards.md) +and validated against +[`persona.schema.json`](https://github.com/petry-projects/.github/blob/main/standards/personas/persona.schema.json). +(Links are absolute so they still resolve after this file is copied into the +`.github-private` repo.) + +## What's here + +```text +persona.yml # the manifest (index-of-record) — fill every CHANGE-ME +README.md # this file — replace with a short note about your persona +evals/ # STARTER dev/ + holdout/ pair — copy it OUT to the repo + # eval tree (see below); do not keep it in personas// +``` + +A persona **instance directory** (`.github-private/personas//`) holds only +`persona.yml` + `README.md`. Every implementation layer lives in its **canonical +repo-level home**, and the manifest just points at it: + +| Layer | Canonical home (in `.github-private`) | +|---|---| +| Framework agent | `frameworks//…` (vendored) | +| Copilot profile | `agents/.md` | +| Workflow prompt library | `prompts//` | +| Eval set | `evals//` (`dev/` + `holdout/`) | + +Author those files directly in their homes — don't nest `agents/` or `prompts/` +inside `personas//`. + +## Onboarding order + +1. **Copy & rename.** `cp standards/personas/TEMPLATE/persona.yml .github-private/personas//persona.yml` (and add a short `README.md`). +2. **Pick your definition layer(s)** in `persona.yml` `definition.layers[]`, each + `path` pointing at its canonical home above. Prefer wrapping a vendored + framework agent where a good upstream exists (pin `vendor_pin` to its + `VENDOR.md`); layer local behavior via `local_overrides`, never by + hand-editing `frameworks/`. A Copilot profile goes at + `.github-private/agents/.md`; a prompt library at + `.github-private/prompts//`. +3. **Fill the trigger matrix.** One row per surface. `default_mode: advisory`. + Every `write` surface needs a `gate_label`. Define an `opt_out_label`. +4. **Set the trust floor** (`[OWNER, MEMBER, COLLABORATOR]` unless you have a + reason to differ). +5. **Add evals.** Copy the `evals/` starter into `.github-private` `evals//`: + ≥ `min_cases` held-out cases under `evals//holdout/` and proposer-visible + cases under `evals//dev/`. See `.github-private` `evals/README.md`. +6. **Register the canary entry** — one `agents.` block in + `standards/canary-rings.json` (the *only* place rings are written). Point + `persona.yml` `canary.agent` at it. +7. **Validate & scan.** Manifest validates against the schema; AgentShield passes; + no immutable file touched. +8. **Roll out** `next → ring0 → ring1 → stable`, eval gate green before `stable`. + +The full gate is the **Definition of Done** checklist in +[`persona-standards.md` §7](https://github.com/petry-projects/.github/blob/main/standards/persona-standards.md). + +## Worked example + +**Murat** (`.github-private/personas/murat/`) wraps the vendored +`bmad-test-architecture` Test Architect — the reference for the +*wrap-a-vendored-agent*, advisory-everywhere path. diff --git a/standards/personas/TEMPLATE/evals/README.md b/standards/personas/TEMPLATE/evals/README.md new file mode 100644 index 000000000..35a31f0d7 --- /dev/null +++ b/standards/personas/TEMPLATE/evals/README.md @@ -0,0 +1,14 @@ +# Held-out eval set — STARTER (copy me out) + +This `dev/` + `holdout/` pair is a **starter**. When you copy the persona +scaffold into `.github-private`, move this eval set to that repo's +**`evals//`** tree — **not** into `personas//evals/`. Only under +`evals/` does it inherit the existing held-out validator +(`evals/validate-cases.py`) and the immutability guard (`holdout-guard.yml`). + +- `dev/cases.jsonl` — proposer-visible split. +- `holdout/cases.jsonl` — CODEOWNER-gated; the gate scores against this. + +One JSON object per line; every case needs a unique kebab-case `id`; a case must +never appear in both splits. See `.github-private` `evals/README.md` and +`evals/case.schema.json`. diff --git a/standards/personas/TEMPLATE/evals/dev/cases.jsonl b/standards/personas/TEMPLATE/evals/dev/cases.jsonl new file mode 100644 index 000000000..8d7852a94 --- /dev/null +++ b/standards/personas/TEMPLATE/evals/dev/cases.jsonl @@ -0,0 +1 @@ +{"id": "example-dev-case", "description": "Proposer-visible example; replace before use.", "input": "…", "expected": {}} diff --git a/standards/personas/TEMPLATE/evals/holdout/cases.jsonl b/standards/personas/TEMPLATE/evals/holdout/cases.jsonl new file mode 100644 index 000000000..f02c5227c --- /dev/null +++ b/standards/personas/TEMPLATE/evals/holdout/cases.jsonl @@ -0,0 +1 @@ +{"id": "example-case", "description": "Replace with real held-out cases before promotion to stable.", "input": "…", "expected": {}} diff --git a/standards/personas/TEMPLATE/persona.yml b/standards/personas/TEMPLATE/persona.yml new file mode 100644 index 000000000..56150643f --- /dev/null +++ b/standards/personas/TEMPLATE/persona.yml @@ -0,0 +1,143 @@ +# Persona manifest — the single index-of-record for one agentic persona. +# Copy this directory to `.github-private/personas//` and fill it in. +# Validate: it must conform to petry-projects/.github standards/personas/persona.schema.json +# Standard: petry-projects/.github standards/persona-standards.md +# +# Golden rules while editing: +# • The manifest POINTS AT things; it does not duplicate them. +# • Ring membership + gate knobs live ONLY in canary-rings.json — reference, never restate. +# • Vendored framework files are never hand-edited — layer overrides instead. +# • Advisory is the default; every `write` surface must carry a gate_label. + +schema_version: 1 + +# Stable kebab-case id. MUST equal this directory's name AND the agents. +# in canary-rings.json AND the channel-tag agent name. +# (lowercase kebab-case — the schema enforces ^[a-z0-9]+(-[a-z0-9]+)*$) +id: change-me + +name: CHANGE-ME # display name, e.g. "Murat" +title: CHANGE-ME # role title, e.g. "Master Test Architect & Quality Advisor" +summary: >- + One sentence: what this persona is for and where it helps + (design / build / test / deliver / operate). + +# Lifecycle: draft → canary → ring0 → ring1 → stable (→ retired). +# Set to the furthest channel this persona has actually reached. +status: draft + +# Owning team, org/team form (matches CODEOWNERS). Team ownership only. +owner: petry-projects/CHANGE-ME + +# --- How the persona is actually implemented ------------------------------ +definition: + layers: + # Pick one or more. Delete the kinds you do not use. + + # A) Backed by a vendored framework agent (BMAD etc.) — the preferred path + # where a good upstream exists and we intend to contribute back. + - kind: framework-agent + path: frameworks/CHANGE-ME/src/agents/CHANGE-ME # the SKILL.md dir + framework: + name: CHANGE-ME # dir under frameworks/ + upstream_repo: CHANGE-ME/CHANGE-ME + vendor_pin: vX.Y.Z # MUST match VENDOR.md + skill: CHANGE-ME # framework agent id + # local_overrides: # optional + # path: prompts/CHANGE-ME/wrapper.md + # upstream_candidate: false # true if it belongs upstream + # notes: why this override exists + + # B) A human-invocable Copilot profile + # - kind: copilot-profile + # path: agents/CHANGE-ME.md + + # C) An automated workflow prompt library (like dev-lead) + # - kind: workflow-prompts + # path: prompts/CHANGE-ME/ + + # D) A self-contained GitHub Agentic Workflow + # - kind: gh-aw + # path: .github/workflows/CHANGE-ME.md + +# --- Capabilities the persona can invoke (advisory index) ----------------- +skills: [] +# To add skills, replace the empty array above with a block sequence: +# skills: +# - name: change-me +# path: frameworks/change-me/src/…/SKILL.md +# source: vendored # vendored | first-party + +# --- The trigger matrix: one row per surface the persona acts on ---------- +# default_mode governs any surface not listed. It MUST be advisory or off. +# Write is never a default — it is an explicit per-surface opt-in with a gate_label. +triggers: + default_mode: advisory + opt_out_label: change-me:hands-off + surfaces: + - surface: pull_request + events: [opened, ready_for_review, synchronize] + enabled: true + mode: advisory # advisory = comments/reviews only; write = opens/pushes PRs + # gate_label: CHANGE-ME # REQUIRED if mode: write + # trust_floor: [OWNER, MEMBER, COLLABORATOR] # defaults to trust.author_association_floor + notes: "" + + - surface: issues + events: [labeled, commented] + enabled: true + mode: advisory + + - surface: mention + events: [created] + enabled: true + mode: advisory + notes: "e.g. @ weigh in" + + # discussion surfaces cannot run the agent inline — declare the bridge: + # - surface: discussion + # events: [labeled] + # enabled: true + # mode: advisory + # bridge: repository_dispatch + + # - surface: check_run + # events: [completed] + # enabled: false + # mode: advisory + + # - surface: schedule + # events: [] + # enabled: false + # mode: advisory + +# --- Trust boundary (checked before spending agent quota) ----------------- +trust: + author_association_floor: [OWNER, MEMBER, COLLABORATOR] + +# --- CI wiring (omit entirely for advisory personas that run inside an +# existing workflow and ship no reusable of their own) ------------------ +# runtime: +# host: petry-projects/.github-private +# reusable: .github/workflows/CHANGE-ME-reusable.yml +# caller_stub: .github/workflows/CHANGE-ME.yml +# run_workflow: CHANGE-ME # MUST match canary-rings.json run_workflow +# permissions: [contents:read, pull-requests:write] # caller stub grants a superset + +# --- Held-out eval gate ---------------------------------------------------- +evals: + required_before: stable # ring0 | ring1 | stable + path: evals/change-me/ # under the repo eval tree (validate-cases.py + holdout-guard.yml) + judge: evals/judge.md + min_cases: 5 + +# --- Canary pointer (single source of truth is canary-rings.json) --------- +# Do NOT restate rings/members/gate here — reference the registry entry by id. +canary: + registry: petry-projects/.github/standards/canary-rings.json + agent: change-me # MUST equal `id` + +references: [] +# To add references, replace the empty array above with a block sequence: +# references: +# - "ADR: docs/initiatives/change-me-adr.md" diff --git a/standards/personas/persona.schema.json b/standards/personas/persona.schema.json new file mode 100644 index 000000000..84a2a9cc1 --- /dev/null +++ b/standards/personas/persona.schema.json @@ -0,0 +1,257 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/petry-projects/.github/standards/personas/persona.schema.json", + "title": "Agentic Persona Manifest", + "description": "The single index-of-record for one agentic persona. It ties together the persona's identity, the definition layer(s) that implement it, the skills it uses, its trigger surface, its trust/permission posture, its eval gate, and a pointer to its canary-rings entry. The manifest does NOT duplicate ring membership or gate knobs — those live in petry-projects/.github standards/canary-rings.json (the single source of truth). See standards/persona-standards.md.", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "id", "name", "title", "summary", "status", "owner", "definition", "triggers", "trust", "canary"], + "allOf": [ + { + "if": {"properties": {"status": {"const": "stable"}}, "required": ["status"]}, + "then": {"required": ["evals"], "description": "A persona at status 'stable' MUST carry an evals block (the held-out gate). See persona-standards.md §1."} + } + ], + "properties": { + "schema_version": { + "type": "integer", + "const": 1, + "description": "Manifest schema version. Bump only when the schema shape changes." + }, + "id": { + "type": "string", + "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$", + "description": "Stable kebab-case identifier. MUST equal the persona directory name AND the channel-tag agent name used in canary-rings.json (e.g. 'murat', 'dev-lead')." + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Human display name (e.g. 'Murat'). For framework-backed personas this MUST match the locked framework identity." + }, + "title": { + "type": "string", + "minLength": 1, + "description": "Role title (e.g. 'Master Test Architect & Quality Advisor')." + }, + "summary": { + "type": "string", + "minLength": 10, + "description": "One-sentence statement of what this persona is for." + }, + "status": { + "type": "string", + "enum": ["draft", "canary", "ring0", "ring1", "stable", "retired"], + "description": "Lifecycle stage. 'draft' = not yet cut; canary/ring0/ring1/stable mirror the furthest channel this persona has been promoted to; 'retired' = decommissioned." + }, + "owner": { + "type": "string", + "pattern": "^@?[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$", + "description": "Owning team in 'org/team' form (matches CODEOWNERS). Team-based ownership only, per codeowners-standard.md." + }, + "definition": { + "type": "object", + "additionalProperties": false, + "description": "Where and how the persona is actually implemented. A persona MUST declare at least one layer. Per persona-standards.md §Definition, the manifest is the unifier ON TOP of these layers — it points at them, it does not replace them.", + "required": ["layers"], + "properties": { + "layers": { + "type": "array", + "minItems": 1, + "description": "Ordered list of definition layers that implement this persona.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "path"], + "properties": { + "kind": { + "type": "string", + "enum": ["framework-agent", "copilot-profile", "workflow-prompts", "gh-aw"], + "description": "framework-agent: a vendored framework agent (BMAD etc.). copilot-profile: an agents/.md profile. workflow-prompts: a prompts// library driven by a shell orchestrator. gh-aw: a GitHub Agentic Workflow .md compiled to a .lock.yml." + }, + "path": { + "type": "string", + "description": "Repo-relative path to the layer's root (the SKILL.md dir, the profile .md, the prompts dir, or the gh-aw .md)." + }, + "framework": { + "type": "object", + "additionalProperties": false, + "description": "Required when kind == 'framework-agent'. Records the upstream provenance and pin so the manifest can point INTO the framework rather than fork it.", + "required": ["name", "upstream_repo", "vendor_pin"], + "properties": { + "name": {"type": "string", "description": "Framework directory under frameworks/ (e.g. 'bmad-test-architecture')."}, + "upstream_repo": {"type": "string", "description": "Upstream source repo (e.g. 'bmad-code-org/bmad-method-test-architecture-enterprise')."}, + "vendor_pin": {"type": "string", "description": "Vendored version/tag, MUST match the framework's VENDOR.md (e.g. 'v1.19.0')."}, + "skill": {"type": "string", "description": "The framework skill/agent id backing this persona (e.g. 'bmad-tea')."} + } + }, + "local_overrides": { + "type": "object", + "additionalProperties": false, + "description": "Local customization applied on top of a vendored layer (never hand-edit vendored files). Overrides that would benefit upstream should be flagged for contribution back.", + "required": ["path", "upstream_candidate"], + "properties": { + "path": {"type": "string", "description": "Repo-relative path to the override file(s) (e.g. a customize.toml overlay or an orchestration wrapper)."}, + "upstream_candidate": {"type": "boolean", "description": "true if this override is a general improvement that SHOULD be proposed back to the upstream framework rather than kept local forever."}, + "notes": {"type": "string"} + } + } + }, + "allOf": [ + { + "if": {"properties": {"kind": {"const": "framework-agent"}}}, + "then": {"required": ["framework"]} + } + ] + } + } + } + }, + "skills": { + "type": "array", + "description": "Capabilities the persona can invoke, consumed by path (vendor-neutral). Advisory index for humans; the authoritative wiring is inside the definition layers.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name", "path", "source"], + "properties": { + "name": {"type": "string"}, + "path": {"type": "string"}, + "source": {"type": "string", "enum": ["vendored", "first-party"]} + } + } + }, + "triggers": { + "type": "object", + "additionalProperties": false, + "description": "The trigger matrix — the checklist of every surface this persona may act on. Per persona-standards.md §Triggers, default is advisory (read-only) everywhere; write access is opt-in per surface and MUST carry a gate label and a trust floor.", + "required": ["default_mode", "opt_out_label", "surfaces"], + "properties": { + "default_mode": { + "type": "string", + "enum": ["advisory", "off"], + "description": "The mode for any surface not explicitly listed. MUST be 'advisory' or 'off' — write is never a default, only an explicit per-surface opt-in." + }, + "opt_out_label": { + "type": "string", + "minLength": 1, + "description": "Label that opts an item out of this persona entirely (convention: ':hands-off')." + }, + "surfaces": { + "type": "array", + "description": "Per-surface trigger declarations.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["surface", "enabled", "mode"], + "properties": { + "surface": { + "type": "string", + "enum": ["issues", "pull_request", "pull_request_review", "discussion", "check_run", "schedule", "mention"], + "description": "The work-item surface / GitHub event family." + }, + "events": { + "type": "array", + "items": {"type": "string"}, + "description": "Specific event actions in scope (e.g. ['opened','labeled'] for issues)." + }, + "enabled": {"type": "boolean"}, + "mode": { + "type": "string", + "enum": ["advisory", "write"], + "description": "advisory: comments/reviews/labels only, no code changes. write: opens/pushes PRs or mutates repo content." + }, + "gate_label": { + "type": "string", + "minLength": 1, + "description": "REQUIRED when mode == 'write': the label that arms the persona on this surface (e.g. 'dev-lead')." + }, + "trust_floor": { + "type": "array", + "items": {"type": "string", "enum": ["OWNER", "MEMBER", "COLLABORATOR", "CONTRIBUTOR", "NONE"]}, + "description": "Minimum author_association allowed to trigger on this surface. Defaults to trust.author_association_floor when omitted." + }, + "bridge": { + "type": "string", + "description": "Optional repository_dispatch/workflow_dispatch bridge type when the surface cannot run the agent inline (e.g. 'discussion' events)." + }, + "notes": {"type": "string"} + }, + "allOf": [ + { + "if": {"properties": {"mode": {"const": "write"}}}, + "then": {"required": ["gate_label"]} + } + ] + } + } + } + }, + "trust": { + "type": "object", + "additionalProperties": false, + "description": "Trust boundary applied before the persona consumes agent quota.", + "required": ["author_association_floor"], + "properties": { + "author_association_floor": { + "type": "array", + "minItems": 1, + "items": {"type": "string", "enum": ["OWNER", "MEMBER", "COLLABORATOR", "CONTRIBUTOR", "NONE"]}, + "description": "The default set of author associations allowed to trigger the persona. Convention: [OWNER, MEMBER, COLLABORATOR]." + } + } + }, + "runtime": { + "type": "object", + "additionalProperties": false, + "description": "How the persona is wired into CI. Optional for advisory-only personas that run entirely inside an existing workflow.", + "properties": { + "host": {"type": "string", "description": "Repo that owns the reusable (matches canary-rings.json host)."}, + "reusable": {"type": "string", "description": "Repo-relative path to the reusable workflow, if any."}, + "caller_stub": {"type": "string", "description": "Repo-relative path to the thin caller stub."}, + "run_workflow": {"type": "string", "description": "Display name of the workflow whose run history feeds the health gate. MUST match canary-rings.json run_workflow for this id."}, + "permissions": { + "type": "array", + "items": {"type": "string"}, + "description": "The full permission set the reusable requests. The caller stub MUST grant a superset of this or the fleet gets startup_failure." + } + } + }, + "evals": { + "type": "object", + "additionalProperties": false, + "description": "The held-out eval gate. A persona MUST NOT reach the 'stable' ring without a passing eval set. See evals/README.md (dev/holdout split, holdout-guard.yml).", + "required": ["required_before", "path", "min_cases"], + "properties": { + "required_before": { + "type": "string", + "enum": ["ring0", "ring1", "stable"], + "description": "The ring by which the eval set must exist and pass. Recommended: 'stable' at minimum." + }, + "path": {"type": "string", "description": "Repo-relative path to the persona's eval set (holds dev/ and holdout/ splits). Canonically 'evals//' so it inherits validate-cases.py and holdout-guard.yml."}, + "judge": {"type": "string", "description": "Path to the judge rubric (e.g. 'evals/judge.md')."}, + "min_cases": {"type": "integer", "minimum": 1, "description": "Minimum held-out cases required before promotion to required_before."} + } + }, + "canary": { + "type": "object", + "additionalProperties": false, + "description": "Pointer to the single source of truth for ring rollout. This manifest MUST NOT restate rings, members, or gate knobs — only reference the registry entry by id.", + "required": ["registry", "agent"], + "properties": { + "registry": { + "type": "string", + "description": "Path to the canonical ring registry (petry-projects/.github standards/canary-rings.json)." + }, + "agent": { + "type": "string", + "description": "The agents. in canary-rings.json for this persona. MUST equal `id`." + } + } + }, + "references": { + "type": "array", + "items": {"type": "string"}, + "description": "Relevant docs, ADRs, or issues (e.g. the ADR that introduced this persona)." + } + } +}