Repository navigation
Rename murat → qa-lead and make it @-addressable - #1279
Conversation
Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Review limit reached
Next review available in: 30 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (12)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request renames the "murat" persona to "qa-lead" across documentation, manifests, and evaluation files. It also introduces validation logic in validate-personas.py to ensure that persona addressing handles are unique and that their team slugs match the persona IDs. A review comment points out a potential IndexError crash in the validator if the handle is misconfigured without a / separator, suggesting a safer parsing approach.
There was a problem hiding this comment.
Pull request overview
This PR updates the worked-example persona from a person-name (murat) to a role-based, org-team addressable persona (qa-lead), and extends the persona validator + tests to enforce the new addressing invariants.
Changes:
- Add
address.handleslug==idand global handle/alias uniqueness checks topersonas/validate-personas.py, with new Bats coverage. - Rename persona/eval content from
murat-*toqa-lead-*(IDs, paths, docs), and introduce theqa-leadpersona README/manifest content. - Update BMAD orchestration and initiative pipeline docs to reference
bmad-tea/qa-leadinstead of the upstream person-name.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_validate_personas.bats | Adds hermetic tests for addressing invariants (slug==id, uniqueness, case-insensitive collisions). |
| personas/validate-personas.py | Implements new cross-invariants: handle slug==id and global handle/alias uniqueness. |
| personas/qa-lead/persona.yml | Renames the persona to qa-lead and adds the address contract block. |
| personas/qa-lead/README.md | New role-based persona documentation and addressing guidance. |
| personas/murat/README.md | Removes the old murat persona README (rename/move). |
| evals/qa-lead/holdout/cases.jsonl | New holdout eval cases with qa-lead-* IDs. |
| evals/qa-lead/dev/cases.jsonl | New dev eval cases with qa-lead-* IDs. |
| evals/murat/holdout/cases.jsonl | Removes old holdout eval cases under murat (rename/move). |
| evals/murat/dev/cases.jsonl | Removes old dev eval cases under murat (rename/move). |
| prompts/bmad/scrum-master.md | Updates prose references to the upstream agent skill id / qa-lead persona. |
| prompts/bmad/README.md | Updates vendored skill documentation to reflect bmad-tea behind qa-lead. |
| docs/initiatives/idea-to-initiative-pipeline.md | Updates initiative-planner documentation to reference bmad-tea / qa-lead. |
The qa-lead persona (PR #1279 in .github-private) introduces `address` for @-mention routing. The canonical schema had additionalProperties:false and no address entry, causing the validate-personas CI check to reject the new field. Adds an optional `address` object property with: - handle (required): org/team-slug — the router resolves it by stripping the org prefix; slug MUST equal persona id - aliases (optional): additional team handles (e.g. from renames) Companion to petry-projects/.github-private#1279.
Dev-Lead — waiting on PR blockers (intent: review-changes)PR: #1279 |
|
Note @don-petry I reviewed this PR and no code changes were needed, but it still has blocking checks or reviews (failing or cancelled checks, or changes-requested reviews), so I cannot mark it done yet. I'll re-check automatically. |
…alization
Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]`
raises IndexError on a handle with no '/', crashing the validator instead of
emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string
handle/alias.
Both are unreachable in CI: schema validation runs before check_invariants, and
the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$`
after .github#752's own review) guarantees a well-formed string. They are worth
fixing anyway because the validator accepts `--schema` — a test double or a
stale local copy lets a malformed handle through, and this module's documented
contract is "non-zero with a diagnostic", not a traceback.
- New `handle_slug()` validates 'org/team-slug' and returns the slug, failing
with a diagnostic on a non-string or slash-less value.
- check_invariants now also validates `address` is a mapping and `aliases` is a
list of well-formed handles. Alias shape is checked THERE, not in
check_handles_unique, so that by the time the latter runs — after every
manifest has cleared check_invariants — it can treat handles and aliases as
well-formed strings by construction. Documented in its docstring.
Copilot also caught a capitalization slip my rename sed introduced:
"...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead".
Only that one instance followed a period; the other three follow semicolons and
are correct. Description text only — no `input`/`expected` touched.
Verification:
- bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare
handle, non-string handle, malformed alias, non-list aliases — each asserting
a clean diagnostic AND the absence of a traceback).
- validate-personas.py against the LIVE merged schema on .github@main (with the
tightened petry-projects-anchored pattern): OK, invariants hold.
- python3 evals/validate-cases.py: OK, 5 skills / 46 cases.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed the review findings in 9d1411a. Gemini (high) + Copilot —
Copilot — capitalization. Correct; my rename Verification
|
donpetry-bot
left a comment
There was a problem hiding this comment.
Automated review — APPROVED ✓
Risk: MEDIUM
Reviewed commit: 3748c6b676bcc3a8576369055a6106debb911a77
Cascade: triage → deep (triage: haiku 4.5 → deep: opus 4.8 + duck: o4-mini → audit: fable 5)
Summary
Renames the murat worked-example persona to the role-based, @-addressable qa-lead and adds two hermetic cross-invariants (handle slug==id, handle/alias global uniqueness) to validate-personas.py with 6 new passing bats tests. The IndexError/AttributeError flagged by triage/gemini/copilot in the new validator code is an unreachable false positive: the .github@main schema pins handle to a string pattern requiring 'petry-projects/' and aliases to a string array, and fail() sys.exit(1)s on the first schema violation before check_invariants and check_handles_unique ever run, so every value reaching split('/')[1]/.lower() is a well-formed string. No secrets, no downstream consumers (none), and all CI gates (validate-personas, gitleaks, CodeQL, SonarCloud, agent-shield, holdout/test-deletion guards, 16/16 bats) are green.
Findings
- MINOR: check_invariants does address['handle'].split('/', 1)[1] with no guard. Flagged HIGH by gemini and copilot as an IndexError crash path, but it is unreachable: the persona schema pins handle to pattern ^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$ (mandates a '/'), and main() runs jsonschema iter_errors -> fail() -> sys.exit(1) before check_invariants, so any handle reaching the split contains a '/'. Even if reachable, the crash is a non-zero exit of a hermetic CI lint that still rejects the bad manifest — no bypass. Optional defensive '/'-check would only improve the diagnostic message. (
personas/validate-personas.py:125) - MINOR: check_handles_unique calls handle.lower() and iterates address.get('aliases', []) without type-guarding (copilot: AttributeError on non-string handle / non-list aliases). Also unreachable: check_handles_unique runs only after the per-manifest loop, and fail()/sys.exit(1) fires on any schema violation first; the schema types handle as string and aliases as array-of-string, so loaded[] only ever holds schema-valid manifests. Same non-blocking diagnostic-quality nit. (
personas/validate-personas.py:151) - INFO: Copilot grammar nit: an eval case description starts mid-sentence with 'the QA Lead' after a period; should be 'The QA Lead'. Description text only — no effect on eval scoring (input/expected are byte-identical to the murat originals; holdout-guard and test-deletion-guard pass). (
evals/qa-lead/holdout/cases.jsonl:3) - INFO: Merge-order dependency noted by author: .github#752 (schema + standard) must merge before this PR, since validate-personas.py fetches the address schema from .github@main. The schema at main already carries the address block (verified during review), so the dependency appears satisfied. mergeStateStatus BLOCKED reflects REVIEW_REQUIRED only, not a failing check.
Reviewed by the PR-review cascade (triage: haiku 4.5 → deep: opus 4.8 + duck: o4-mini → audit: fable 5). Reply if you need a human review.
Dev-Lead — review-changes (applied)Changes committed and pushed. |
68c389d
|
Dev-Lead — waiting on PR blockers (intent: review-changes)PR: #1279 |
|
Note @don-petry I reviewed this PR and no code changes were needed, but it still has blocking checks or reviews (failing or cancelled checks, or changes-requested reviews), so I cannot mark it done yet. I'll re-check automatically. |
donpetry-bot
left a comment
There was a problem hiding this comment.
Automated review — APPROVED ✓
Risk: MEDIUM
Reviewed commit: 68c389db9ffaffed9a33b77fdea93204d378bef3
Review mode: triage-approved (single reviewer)
Summary
Renames the worked-example persona murat → qa-lead (role, not person) per persona-standards §1.6, adds an org-team addressing contract (§4.1), and extends validate-personas.py with two cross-file invariants (handle slug == id; fleet-wide handle/alias uniqueness, case-insensitive) plus 11 new hermetic bats tests. Companion to petry-projects/.github#752. All prior review findings (Gemini high IndexError, Copilot type-safety and capitalization) are verifiably fixed at head.
Linked issue analysis
No linked issues (closingIssuesReferences is empty). The PR is a companion to petry-projects/.github#752 (the standard + schema), which is already merged — the validate-personas CI job passes against the live canonical schema.
Findings
Confirmation review of the triage assessment — the triage tier's low-risk clearance holds, with one nuance:
- Prior findings all resolved at head. Gemini's high-priority finding (
split("/", 1)[1]IndexError on a slash-less handle) and Copilot's two findings (.lower()on non-string; capitalization in an eval description) were fixed in9d1411a: the newhandle_slug()helper validatesisinstance(str)and"/" in valuebefore splitting and emits afail()diagnostic,check_invariantsvalidatesaddressmapping/alias shapes, and 4 new bats tests assert diagnostics without tracebacks. bats: green in CI. - One review thread is technically unresolved (Gemini's IndexError thread) but it is substantively addressed: the fix is in the head commit with test coverage, and the author replied in-thread with specifics. Gemini threads are not auto-resolvable by the fix author; not treating this as blocking.
- Validator logic is sound.
check_handles_uniqueruns only after every manifest clearscheck_invariants, aliases share the namespace with handles, and collisions are case-insensitive — matching how GitHub resolves team mentions. Ordering invariant is documented in the docstring. - Rename hygiene is good.
persona.ymlmoved viagit mv(62% similarity, history preserved); eval case ids renamed with content otherwise unchanged (holdout-guard green);frameworks/untouched as claimed — vendored upstream names correctly retained; no thin caller stubs modified. - Addressing contract declared, not served — no router/runtime exists, persona stays
status: draft,address.handleis an org team (never a user account, avoiding the real@qa-leadGitHub user). Live team properties are explicitly deferred to a networked CI job; validator stays hermetic. Sensible separation. - Secret scan: the
run_secret_scanningMCP tool is not available in this environment; the gitleaks CI check passed and the diff contains no credential-like content.
Risk is MEDIUM (non-trivial validator logic + cross-module rename), not HIGH: no auth/secrets/migrations, no workflow files touched, no standards violations.
CI status
All checks green for head 68c389d: shellcheck, ShellCheck, actionlint, bats, unit-tests, validate-personas, holdout-guard, gitleaks, CodeQL (actions + python), Agent Security Scan, agent-shield, SonarCloud quality gate, caller-stub-freeze, template-drift, gh-aw-compile, CodeRabbit. A few CANCELLED entries (review/review, dev-lead/dispatch, dev-lead/ci-relay) are concurrency-superseded duplicate runs — each has a later SUCCESS (ci-relay intentionally skipped via [skip ci-relay]). Dependency-audit jobs skipped (no matching ecosystems changed).
Reviewed automatically by the PR-review agent (single-reviewer mode: fable 5). Reply if you need a human review.
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>
* feat(personas): rename murat -> qa-lead and make it @-addressable Companion to petry-projects/.github#<A> (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example. Renames (git mv, history preserved): personas/murat/ -> personas/qa-lead/ evals/murat/ -> evals/qa-lead/ plus id/name/opt_out_label/canary.agent/evals.path and the eval case ids. The role, not the cast list The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it. `@petry-projects/qa-lead` says who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only by `framework.skill: bmad-tea`. Nothing under frameworks/ is touched. Of ~40 fleet-wide "murat" hits, all but six are vendored and correctly keep the name; this PR changes only our layer. Addressing address: handle: petry-projects/qa-lead aliases: [] The handle is an org TEAM, never a user account. `@qa-lead` is a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos. Note this PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the mention surface. qa-lead stays `status: draft`. That separation is deliberate. Validator (personas/validate-personas.py) Two new cross-invariants the JSON schema cannot express: * address.handle's team slug == id — so the router resolves a mention by stripping the org prefix, and the role name is written exactly once. * handles and aliases are unique across ALL personas (case-insensitive). A collision is not cosmetic: the router resolves a handle to exactly one persona, so two claimants means a dropped mention or a double-fire. Aliases share the namespace with handles, so a rename's leftover alias cannot shadow a live role. Live team properties (exists / privacy: closed / notifications_disabled) are deliberately NOT checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD. Verification - bats tests/test_validate_personas.bats: 16/16 pass (10 pre-existing + 6 new covering slug==id, handle collision, alias collision, case-insensitivity, distinct handles, and no-address personas). - validate-personas.py against the §4.1 schema: OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases, no cross-split id overlap (eval ids renamed murat-* -> qa-lead-*; only ids/descriptions changed, every `input`/`expected` is byte-identical, so the held-out set tests exactly what it did before). - markdownlint-cli2 clean. - holdout-guard passes: it fails only PRs authored by github-actions[bot] (the skill-proposer); this PR still needs CODEOWNER review over evals/. Merge order: .github#<A> must merge first — validate-personas.py fetches the schema from .github@main, and `address` fails main's schema until then. Follow-up, deliberately not here: prompts/bmad/scrum-master.md still opens with "You are Bob". Same violation, but that prompt drives the live initiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review — not a rider on a rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(personas): address review — handle-shape diagnostics + case capitalization Gemini (high) and Copilot both flagged that `address["handle"].split("/", 1)[1]` raises IndexError on a handle with no '/', crashing the validator instead of emitting a diagnostic. Copilot additionally flagged `.lower()` on a non-string handle/alias. Both are unreachable in CI: schema validation runs before check_invariants, and the canonical schema's pattern (now `^petry-projects/[a-z0-9]+(-[a-z0-9]+)*$` after .github#752's own review) guarantees a well-formed string. They are worth fixing anyway because the validator accepts `--schema` — a test double or a stale local copy lets a malformed handle through, and this module's documented contract is "non-zero with a diagnostic", not a traceback. - New `handle_slug()` validates 'org/team-slug' and returns the slug, failing with a diagnostic on a non-string or slash-less value. - check_invariants now also validates `address` is a mapping and `aliases` is a list of well-formed handles. Alias shape is checked THERE, not in check_handles_unique, so that by the time the latter runs — after every manifest has cleared check_invariants — it can treat handles and aliases as well-formed strings by construction. Documented in its docstring. Copilot also caught a capitalization slip my rename sed introduced: "...no behavior change. the QA Lead must NOT manufacture risk" -> "The QA Lead". Only that one instance followed a period; the other three follow semicolons and are correct. Description text only — no `input`/`expected` touched. Verification: - bats tests/test_validate_personas.bats: 20/20 (16 + 4 new covering bare handle, non-string handle, malformed alias, non-list aliases — each asserting a clean diagnostic AND the absence of a traceback). - validate-personas.py against the LIVE merged schema on .github@main (with the tightened petry-projects-anchored pattern): OK, invariants hold. - python3 evals/validate-cases.py: OK, 5 skills / 46 cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: dev-lead update (review-changes) [skip ci-relay] --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com>



Summary
Companion to petry-projects/.github#752 (the standard + schema). Applies the new "a persona is a role, not a person" rule (§1.6) and the new addressing contract (§4.1) to the worked example.
Renames via
git mv(history preserved):plus
id/name/opt_out_label/canary.agent/evals.pathand the eval case ids.The role, not the cast list
The vendored agent this persona wraps has a person-name upstream. It keeps it; we do not import it.
@petry-projects/qa-leadsays who is being addressed and why in a PR comment — the upstream cast list does not — and swapping the agent underneath now never changes how the persona is addressed. Upstream is referenced only byframework.skill: bmad-tea.Nothing under
frameworks/is touched. Of ~40 fleet-widemurathits, all but six are vendored and correctly keep the name. This PR changes only our layer — which is the point.Addressing
The handle is an org team, never a user account.
@qa-leadis a real, live GitHub account owned by someone unrelated to us — a workflow keying on the bare role would fire correctly, look healthy, and notify a stranger on every mention from a fleet of bots across public repos.This PR declares the addressing contract but does not serve it: there is no router and no runtime yet, so nothing dispatches on the
mentionsurface.qa-leadstaysstatus: draft. The separation is deliberate.Validator
Two new cross-invariants the JSON schema cannot express:
address.handle's team slug ==id— so the router resolves a mention by stripping the org prefix, and the role name is written exactly once.Live team properties (exists /
privacy: closed/notifications_disabled) are deliberately not checked here — they need the network, and this validator is hermetic with a hermetic bats suite. Those belong to a CI step and the §7 DoD.Verification
bats tests/test_validate_personas.bats— 16/16 pass (10 pre-existing + 6 new: slug==id, handle collision, alias collision, case-insensitivity, distinct handles, no-address personas).validate-personas.pyagainst the §4.1 schema: OK, invariants hold.python3 evals/validate-cases.py— OK, 5 skills / 46 cases, no cross-split id overlap. Eval ids renamedmurat-*→qa-lead-*; only ids and descriptions changed — everyinput/expectedis byte-identical, so the held-out set tests exactly what it did before.markdownlint-cli2clean.github-actions[bot](the skill-proposer). This PR still needs CODEOWNER review overevals/— that gate is intact.Notes for reviewers
Merge order:
.github#752must merge first —validate-personas.pyfetches the schema from.github@main, andaddressfails main's schema until then.Deliberate follow-up:
prompts/bmad/scrum-master.mdstill opens with "You are Bob" — the same §1.6 violation. That prompt drives the liveinitiative-planner, so changing its persona-adoption text is a behavior change that deserves its own PR and review, not a rider on a rename. The("Murat")prose references in that file are fixed here, since they are pure prose with no runtime effect.🤖 Generated with Claude Code