Skip to content

Releases: Wizarck/ai-playbook

v0.24.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 20:05
baa7132

v0.23.0 removed eleven skills and shipped a migration step: every consumer had to
run the materialiser once, on its current pin, before bumping. That step was
the defect. This playbook is pull-model — consumers bump on their own schedule
and nothing reaches into their repos — so a correctness requirement that must be
executed inside each consumer, in advance, cannot be relied on, and it presumes
the playbook knows who its consumers are. It does not.

Added

  • specs/skills-owned-history.yaml — every slug the playbook has ever
    shipped, 87 of them. It exists so the materialiser can tell a skill the
    playbook REMOVED from a skill the consumer wrote, which is the one thing a
    fresh mirror cannot infer. Seeded from present ∩ desired alone, a removed
    slug is excluded from the owned set by construction and can never be
    classified as stale; with the list, the seed becomes
    present ∩ (desired ∪ ever_owned) and it is cleared on the consumer's first
    ordinary bootstrap.py --update. A skill the consumer authored is in neither
    set and is preserved.

    Append-only: removing a slug re-creates the orphan it exists to clear, and a
    test fails if a shipped skill is missing from it. A playbook copy that predates
    the file degrades to the old behaviour — deletes nothing — rather than failing.

Removed

  • scripts/bump_consumers.py, scripts/_bumper.py, tests/test_bumper.py
    — 1 112 lines. bump_consumers.py walked every project in the developer's
    local registry, moved each one's submodule pin, and with --push --open-pr
    pushed and opened pull requests across them. That is the push pipeline retired
    in v0.19.0, still shipping as a supported script and contradicting
    release.md in the same repository: "No automation reaches into consumer
    repos."
    Nothing else imported the two helpers — the "CI-side" user named in
    their docstrings had already gone.

    The pull-model path is untouched: a consumer bumps itself with
    scripts/rules/update-playbook.rule.py apply --execute.

    The registry itself stays and is unaffected. ~/.ai-playbook/projects.yaml is
    per-dev, gitignored, and built by scanning the developer's own disk; the
    playbook holds no list of who consumes it and now ships nothing that walks one.

Fixed

  • The .gitignore template ignored none of the three skills mirrors while
    skills-distribution.md §3.2 stated that every consumer ignores all three. The
    contract was documented and never implemented, so what a consumer ignored was
    whatever it had hand-written. Repos onboarded under RFC-0001 carry a block that
    ignores .claude/skills/ and .gemini/skills/ but keeps /skills/ — correct
    then, when skills/ was the source and the other two its copies; wrong since
    v0.17.0 collapsed all three into mirrors. Two consumers were found committing
    roughly 1 100 files of playbook content, taking a diff of files nobody there
    wrote on every bump, and silently losing any hand-edit to the next materialise.

    The template now carries all three. Existing repos untrack theirs once with
    git rm -r --cached skills/.

Migration

None. That is the point of the release — a consumer bumps normally and the
eleven skills v0.23.0 removed are cleared on that run. Step 0 of
upgrade-playbook-pin.md is deleted; consumers that already ran it lose nothing
by having done so.

A consumer that already crossed v0.23.0 with an older playbook copy still has the
eleven in its mirrors: bumping to v0.24.0 and re-running bootstrap.py --update
clears them.

v0.23.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 18:41

MINOR, not PATCH: eleven skills are removed from the surface every consumer
materialises, and consumers with no skills manifest need a one-time step
before they bump across this tag. See "Migration" at the end of this entry.

Removed

  • Eleven vendored BMAD skills, 1 056 671 B. An earlier audit proposed cutting
    35 of the 71 on the strength of their being 50.20 % of the repository. That
    number stopped existing when the testing knowledge base was deduplicated, and
    the same 35 skills re-measured at 18.9 %. Re-justified one by one, ten of the
    35 turned out to carry modifications made here — the list had been built from
    reference counts, never from a diff against upstream — and one of them had been
    improved the same week. What survived that scrutiny is a smaller cut on a
    different criterion: not weight, but contradiction.

    The four BMad Builder skills (bmad-agent-builder, bmad-workflow-builder,
    bmad-module-builder, bmad-bmb-setup) build new BMad modules, which this
    fork does not do; they held the skills tree's only uv run dependency and its
    only installer. The story and sprint loop (bmad-create-story,
    bmad-dev-story, bmad-sprint-planning, bmad-sprint-status,
    bmad-quick-dev) was replaced by OpenSpec changes — a substitution the runbook
    documents rather than an inference from disuse. And two had named successors:
    bmad-generate-project-context (now AGENTS.md + the submodule) and
    bmad-agent-tech-writer (its only output is reachable from
    bmad-agent-analyst).

    The testing family stays, at 7.2 %. It holds most of this repository's
    modifications to vendored skills, it is where upstream moves fastest, and one
    of its skills had been improved the same week the list proposed deleting it.
    The rest of the original candidates — the CIS coaches, the editorial
    utilities, bmad-help, bmad-correct-course, bmad-create-epics-and-stories,
    bmad-distillator, bmad-teach-me-testing — are 364 929 B together and are
    left for a decision about whether the flow routes to them, which is a question
    about use rather than about bytes.

Changed

  • One copy of the testing knowledge base instead of nine. The same 49
    fragments and index sat byte-identical in nine skill directories and were
    copied again into three mirrors per consumer, so a single fragment reached a
    consumer twenty-seven times. It now lives once, in bmad-tea-knowledge, read
    through its index. 6 153 384 B recovered with no skill deleted and no content
    lost.

  • Amelia executes a change, not a story. bmad-agent-dev routed four of its
    seven menu items at deleted skills, and told the agent to read a story file,
    tick its tasks and update its File List. No skill produced that file any more.
    The persona now reads openspec/changes/<id>/proposal.md and tasks.md; the
    discipline is unchanged, only the artefact moved.

  • A second UX spine, for how the product behaves. docs/ux/DESIGN.md is
    about appearance, and everything about behaviour — state patterns, interaction
    primitives, voice and tone, the accessibility floor — was being settled in the
    head comment of whichever mock needed it first. EXPERIENCE.md gives those an
    address, and the track now states which artefact wins when two disagree.

Fixed

  • The test-review score depended on which dimension found the defect. Each of
    the four review workers carried its own severity weights and computed its own
    0–100 score, so the same violation cost a different amount depending on who
    raised it, and the aggregator averaged the four. There is now one deduction
    ledger — CRITICAL 10, HIGH 5, MEDIUM 2, LOW 1, subtracted from 100 — with
    cross-dimension deduplication on file:line:row, and the workers report
    violations instead of scores. The criteria registry that fixes severity per row
    was imported from test-architecture-enterprise 1.24.0.

    The aggregator also computed an A/B/C/D/F grade while the checklist documented
    A+/A/B/C/F. They now agree.

  • bmad-testarch-ci told the agent to do three things the rest of the family
    forbids
    — run tests serially, use a fresh database per test, and wait on hard
    timeouts — each contradicting the isolation and determinism rules the same
    family states elsewhere.

  • The distribution spec described an algorithm the code no longer runs.
    skills-distribution.md §4 documented whole-mirror wipe-and-recopy and promised
    that skills removed upstream "disappear from the mirror in one shot — no orphan
    accumulation". The materialiser has been provenance-gated since the additive
    contract landed: it deletes only what its per-mirror manifest records it as
    having installed. §4 now describes what runs, and §4.1 states the consequence
    that makes the migration below necessary.

  • The bootstrapped propagation workflow was not valid YAML. Three multi-line
    strings sat at column 0 inside a run: | block, closing the block scalar. Every
    consumer created from the template got a dead workflow and a red pre-commit. The
    heredocs are now printf redirections, and the OpenSpec CLI is pinned and
    version-checked instead of installed from two package names that do not exist.

  • doctor warned about environment variables it was reading from the wrong
    file
    , then counted table rows whose "required" cell said things like
    "yes (if X)" as unconditionally required, and treated another project's prefix
    as this one's.

  • The onboarding runbook documented two flags that do not exist in the
    script's argument parser or its schema, and an expected output nobody had run.

  • Six guardrails enforced decisions that never reached telemetry when invoked
    directly, so Dashboard coverage was incomplete and the enforcement docs
    promised more observability than existed. The remaining rule CLIs and both
    sweep CLIs now route through the existing cli_emit / script_emit wrappers,
    exit codes unchanged, and enforcement-status.md states where the
    rule-event/v2 stream ends — GitHub Actions gates stay outside it, because a
    workflow check governs an asynchronous PR transition rather than an agent tool
    call, and mixing the two would combine incompatible denominators.

    The regression guard that keeps this from silently regressing is bound to the
    entry point it protects: a cli_emit call inside the __main__ guard only
    counts when it receives main itself, and a direct main() call in the same
    guard is rejected — otherwise a decoy call satisfied the check while the real
    path ran unobserved. Contributed by @WilliamPersico in #177.

Legal

  • NOTICE added at the repository root. This is a public repository that
    redistributes about nine megabytes of adapted third-party work, most of it the
    skills/bmad-* tree, and carried no attribution outside the changelog. MIT
    requires the copyright and permission notice to travel with any substantial
    portion, so its absence was a defect, not a formality. The notice names every
    origin, its licence and holder, what was taken, and whether it was vendored,
    wrapped, or adopted as a pattern. It opens by disclaiming affiliation, because
    a fork that keeps upstream's naming should say plainly that upstream did not
    write it and is not answerable for it.

  • The upstream version is named rather than guessed. The BMAD installation
    manifest that a consumer still carries records installer 6.3.0 on 2026-04-19,
    with core 6.3.0, bmm 6.3.0, bmb 1.5.0, cis 0.1.9 and tea 1.7.2; the import here
    is dated 2026-04-26 and its skill set matches that installation exactly, one
    own-work skill aside. The notice says "imported", which is a fact, and declares
    that a byte-level comparison against the published packages was not run.

Fixed

  • Two licence claims were wrong. Graphify relicensed from MIT to Apache 2.0
    and moved organisation; the README still called it MIT. And
    skills/openspec-apply-parallel, which is this repository's own work, declared
    Apache-2.0 inside an MIT repository.

  • The hybrid-planning section named a dormant skill family. The README listed
    bmad-testarch-* as part of the flow while the skills inventory marked the same
    family as not participating in any documented phase.

  • Ported skills now name the copyright holder they were ported from, and the
    two rules shaped by third-party work carry that credit in the rule itself
    instead of only in this file, so it survives being read on its own.

Migration

Consumers whose repository has no .ai-playbook-state/skills-manifest.json must
run the materialiser once on their current pin, before checking out this tag:

python .ai-playbook/scripts/materialise_skills.py --quiet

The materialiser deletes only what its manifest records it as having installed.
With no manifest it seeds ownership as present ∩ desired, which by construction
excludes a skill this tag no longer wants — so the eleven removed skills would
never be classified as stale, on that run or any later one, and would sit in all
three mirrors permanently. Seeding first records what you have today, so the bump
can remove them. Consumers that already have a manifest need nothing.

Full procedure: step 0 of docs/runbooks/upgrade-playbook-pin.md.

v0.22.22

Choose a tag to compare

@github-actions github-actions released this 28 Aug 06:19

Fixed

  • docker compose (v2, with a space) walked straight through the gate.
    _KILL_PATTERNS only carried the v1 hyphenated spelling docker-compose, so
    docker compose down -v — which destroys named volumes — was never matched.
    Found the hard way: an agent ran exactly that against a live stack and the
    gate stayed silent, while the same agent was correctly blocked on
    docker rm. A safety rule with a hole is worse than a known-absent one,
    because the blocks it does issue imply a coverage it does not have.

    Both spellings are now matched by one pattern, and the verb is reachable
    behind global flags (docker compose -p proj -f a.yml down), which is the
    form that actually slipped through.

  • kubectl delete was ungated entirely. Deleting a live workload is the
    cluster-native equivalent of docker rm. Now matched.

Non-terminating neighbours stay allowed and are covered by tests:
docker compose up -d, docker compose ps, docker compose logs,
kubectl get, kubectl rollout restart.

v0.22.19 — rules using @dataclass could not be imported at all

Choose a tag to compare

@github-actions github-actions released this 16 Aug 22:23
bfe2717

Fixed

  • _load_rule_module never registered the module in sys.modules.
    @dataclass combined with from __future__ import annotations resolves its
    field types through sys.modules at class-creation time, so any rule using
    that pair — which is every rule in this repo — died with
    AttributeError: NoneType has no attribute __dict__.

    except Exception: mod = None then turned that into a None, and the
    caller's continue made an unimportable rule indistinguishable from a rule
    with no hook for this event
    .

    Measured cost: jira-closure-evidence declares a dataclass. It was
    status: enforced, appeared in --list, was not disabled, and matched its
    trigger — and had NEVER ONCE RUN since it shipped in v0.22.14. Two later
    explanations for it not firing (a matcher missing transition, then absent
    Atlassian credentials) were both true and both downstream of this one.

    Import failures are now recorded in _LOAD_ERRORS instead of vanishing. The
    behaviour is still fail-open — a broken rule must not wedge the hook path —
    but its absence is no longer silent.

    A failed import also no longer leaves a half-executed module behind in
    sys.modules, which would be the worse failure: importable by name from
    anywhere else in the process, and appearing to work.

Verification

  • New census test: every shipped hardrule must import. A NEW rule that fails
    fails this file rather than going quietly missing.
  • Mutation-verified: removing the registration reddens the census AND the
    dataclass test.
  • The dataclass test's first draft used a plain @dataclass and passed with
    the fix reverted — measured that only the deferred-annotations form
    reproduces (@dataclass(slots=True) does not either). Corrected, because a
    test that cannot reproduce its own subject is the defect this repo is about.

v0.22.18

Choose a tag to compare

@github-actions github-actions released this 16 Aug 21:55
603d1e9

Added

  • jira-closure-evidence now enforces via a receipt. The comment is judged
    where it is writtenaddCommentToJiraIssue, whose payload carries the body
    and whose comment demonstrably persists — and a small local receipt records
    the verdict. A transition into a declared Done id then requires a fresh
    passing receipt.

    This is the design that replaces the one v0.22.17 recorded as failed. Carrying
    the comment inside the transition does not work: Jira accepts the ADF, returns
    success, moves the issue to Done, and silently drops the comment.

    No credentials, no network, and it validates text that actually exists.

  • AIPLAYBOOK_CLOSURE_DONE_TRANSITIONS — the opt-in. Transition ids are
    per-workflow, so without credentials the gate cannot tell a closure from a
    move to In Progress. Unset means the rule does nothing: a consumer joins by
    naming its own workflow, not by minting a token. Where credentials do exist,
    the live status category still wins.

  • New clause C4 — no blank halves. If a closure enumerates its work as an
    ordered list, every item must carry its own artefact. Catches the error the
    rule was built for (GPLO-1469, closed after verifying 1 requirement of 3)
    using only the comment. Ordered lists only — bullets carry prose.

Changed

  • Old C4 (path fidelity) → C5; old C5 (requirement count) → C6. Both
    remote-only, skipped without credentials, and their absence is stated in the
    refusal rather than left to be assumed.
  • Commenting is never blocked, only recorded. Refusing a comment that merely
    says FIXED would be a false positive on the most common verdict word.
  • status back to enforced — accurate now.

Verification

  • 45 tests. Mutation-verified twice, each killing only its own cluster: killing
    the verdict matcher reddens the six recording tests; removing the receipt
    check at transition time reddens the four enforcement tests.
  • The verdict matcher shipped in a draft with literal backspace bytes instead of
    word-boundary escapes, so it matched nothing and no receipt was ever
    written — a silent no-op with a green suite.
    test_a_verdict_token_is_what_makes_it_a_closure exists specifically to
    redden on that.

v0.22.17

Choose a tag to compare

@github-actions github-actions released this 16 Aug 21:24
f7abe6f

Fixed

  • The documented rework for jira-closure-evidence described a mechanism that
    does not persist.
    The plan was to carry the closure comment inside the
    transition (update.comment[].add.body). Measured against a real closure
    before building on it:

    1. A markdown string is rejected — "Operation value must be an Atlassian
      Document"
      . That error comes from Jira's own validator, proving the MCP
      server does forward update.
    2. Proper ADF is accepted: the call returns success, the status moves to
      Done, and the comment is silently dropped. comment.total stays 0,
      re-read twice minutes apart, while a sibling ticket returns its comments
      through the identical call.

    Almost certainly hasScreen: false on the transition — Jira drops field
    operations for a screenless transition. That is a per-workflow property, so
    even where it works it is not something a consumer could rely on.

    Shipping it would have been worse than shipping nothing: the gate would
    demand the comment ride in the transition, the author would comply, Jira would
    return success, and the ticket would land in Done with no comment at all.

    The lesson is this rule's own subject turned on its author — the API accepting
    a field is not the field persisting. Acceptance is not persistence.

    The doc now records the failed experiment so nobody rebuilds it, and points at
    a receipt-based design instead: validate the comment on
    addCommentToJiraIssue (whose payload both carries the body and persists it),
    write a local receipt, and require a recent valid receipt at transition time.
    The credential-free opt-in idea survives intact.

v0.22.16

Choose a tag to compare

@github-actions github-actions released this 16 Aug 20:53
7751a4d

Fixed

  • jira-closure-evidence is now status: advisory. It was shipped as
    enforced and described as live. It was not blocking anything, and could not.

    The clauses need the ticket's description and its latest comment.
    Neither is in the event: the MCP transition payload carries only
    issueIdOrKey and transition.id. So the hardrule fetches the issue over
    REST, which needs ATLASSIAN_URL / ATLASSIAN_USERNAME /
    ATLASSIAN_API_TOKEN in the hook process's environment. Measured in the
    consumer repo it was written for: those are in neither the SOPS dev secrets
    nor OpenBao, and a hook subprocess cannot borrow the agent's MCP OAuth. The
    rule ran, found no credentials, and failed open on every transition.

    A rule advertising enforced while verifying nothing is the exact defect this
    rule exists to catch. It spent a day being one.

    advisory here means "cannot be relied on to fire", not "will never
    fire"
    — the dispatcher routes on triggers: alone and ignores status:, so
    a consumer who does export those variables still gets full C1-C5 blocking.
    Both halves are documented, because either alone is the misleading half.

Known gap

  • The shape is wrong for an agnostic playbook, not merely unconfigured.
    Every other hardrule here judges the event payload — the direct twin
    jira-ticket-standard reads tool_input["description"], needing no
    credentials and staying inert for anyone who does not use that tool. This is
    the only rule that reaches out, and the dependency is not "Jira": it is
    needing state the event does not carry, which would break identically
    against GitHub Issues or Linear. A public, tracker-agnostic playbook must not
    require every consumer to mint a long-lived API token for a capability most of
    them will never use.

    The rework, tracked for a later release: carry the closure comment inside
    the transition (update.comment[].add.body, which Jira's API and the MCP tool
    both accept). C1-C3 then judge from the payload like the twin; C4-C5 change
    from verify against the remote ticket to show your work — the comment must
    enumerate the requirements it closes, each with its own artefact. Weaker in
    principle, sufficient for the failure it was built for (forgetting a half, not
    lying about one), and it makes the evidence atomic with the act it
    justifies
    rather than whatever comment happened to land last.

v0.22.15

Choose a tag to compare

@github-actions github-actions released this 16 Aug 19:59
2720cee

Fixed

  • shared-test-db-mutex could not tell a dead holder from a live one on
    Windows.
    _pid_alive returned True unconditionally there, and the comment
    justifying it called that "the safe direction" because the TTL would release a
    genuinely dead holder. That reasoning was wrong for the platform this actually
    runs on: with no liveness probe, the TTL was the only release mechanism, so
    any interrupted run — Ctrl-C, a killed background task, a crashed worker —
    locked the database for three hours.

    It was hit fifteen minutes after the rule was first wired to its event: a
    probe process exited, its lock outlived it, and the next legitimate command was
    refused by a holder that no longer existed.

    This is the failure mode the rule's own doc warns about in a different costume.
    A gate that fires where it cannot judge honestly gets bypassed; a mutex whose
    cost is a three-hour outage gets switched off, and then it protects nothing.

    Liveness is now probed on both platforms — signal 0 on POSIX,
    OpenProcess + GetExitCodeProcess on Windows. Two asymmetries are
    deliberate: ERROR_ACCESS_DENIED counts as alive (the process exists and
    we may not inspect it — treating it as dead would let one user's run take
    another's lock), and any unexpected condition counts as alive (holding a lock
    too long is recoverable; releasing a held one corrupts a running suite
    silently).

    Pinned by four tests against real processes, two of which fail against the
    previous behaviour: a process that has exited is not alive, a running one is
    (negative control), and a fresh lock naming a dead pid no longer blocks —
    asserted with the TTL deliberately far from expiry, so only liveness can
    satisfy it.

v0.22.14

Choose a tag to compare

@github-actions github-actions released this 16 Aug 17:28
895eef8

Fixed

  • branch-name-validator asked for a file the playbook gitignores. The gate
    told authors to "create the proposal at openspec/changes/<id>/ and commit it
    to this branch" — and openspec/ is ignored here on purpose ("the playbook
    does not commit its own proposals/tasks/archive", .gitignore, #79). That
    remedy could never be followed by anyone.

    Nobody was blocked, which is why it lasted. The third remedy — "use a
    chore/* branch" — was the only one that worked, so every PR merged after
    #158 used chore/*, including ones titled feat(...): #163, #162, #161,
    #159, #157. The prefix stopped meaning "this is maintenance" and came to mean
    "this is the only prefix that passes", while the check reported green the
    whole time. A gate whose remedy is impossible does not stop the work; it
    quietly retrains everyone to route around it.

    The requirement is still correct for a consumer repo that commits openspec/,
    so it is not deleted — it is made conditional on being satisfiable, tested
    with git check-ignore on the exact path the error message names. The
    branch-NAME pattern is untouched and still enforced.

    The first attempt at that test asked "does this repo track anything under
    openspec/" and was wrong: gitignoring a directory does not untrack what was
    already committed, and legacy proposals from before #79 are still in the tree.
    It answered "satisfiable" while a new proposal still could not be added.
    Caught by running the condition rather than reasoning about it —
    test_the_repo_this_runs_in_actually_triggers_the_exemption now pins it.

Added

  • tests/test_branch_name_validator_workflow.py — 13 tests, the workflow's
    first coverage. Negative controls carry the weight: an exemption that swallows
    the gate would satisfy every "must not block" assertion while removing the
    enforcement, so the branch-name pattern, the success path and the failing
    verdict are each asserted to still exist.

v0.22.12

Choose a tag to compare

@github-actions github-actions released this 02 Aug 19:05
7ff102f

Added

  • English headings for the three sections that lacked them. contexto,
    alcance and metricas now carry Context / Problem, Scope / Deliverables
    and Metrics (plus short forms) alongside their Spanish aliases.

    This was never a decision anyone made. Four sections already had English
    aliases — Test plan, Steps to reproduce, Expected vs actual,
    Regression test — and three did not, which is simply what the first author
    reached for. Measured 2026-08-02 while drafting a 15-ticket backlog in
    English: a fully English ticket was rejected on exactly those three sections,
    so the standard was not enforcing Spanish, it was forcing tickets to be
    bilingual. Either language is a defensible house rule; half of each is not.

    Canonical stays Spanish so nothing already written moves, and the change is
    purely additive: no description that validated before it stops validating
    after. tests/fixtures/jira_ticket_gplo_1350.md — the real production
    ticket the suite is anchored on — is asserted unchanged as the C regression.

    The negative control is the one worth reading: ## Background is plausible
    English prose for the same content and is still rejected. The matcher
    resolves against the closed alias list, not against "English", so the contract
    is still a contract.