Skip to content

v3.1.0

Latest

Choose a tag to compare

@craigmbooth craigmbooth released this 04 Aug 13:30

MINOR. Three new plugin skills and a new vendored file are additive per
LIFECYCLE.md, and the skill rename below — while a real break
for anyone with the old command in their fingers — cannot turn a consumer's CI
red, which is the test that makes a release MAJOR.

Fixed

  • make coverage-diff did not source .colormath/ci.env
    (Makefile.colormath). CI's diff-coverage gate sources it before running
    pytest; the local mirror of that gate did not. So a consumer whose app needs
    test configuration to import at all — a session secret, a provider key —
    got a passing gate in CI and a collection error locally, from the same
    commit. make preflight is only useful if it runs what CI runs.

    Found while bumping intendent to v3.0.0: make test had been fixed to
    mirror CI, and coverage-diff was the one remaining target still running
    pytest with the wrong environment. The fix uses the same if [ -f … ]; then set -a; . …; set +a; fi form as the CI step, so the two are literally the
    same idiom.

    This closes the half colormath owns, and only that half. Whether the app
    also reads a local .env is the consumer's business: a .env holding
    container-only paths still leaks into host runs, and neutralizing that stays
    with the consumer. So a consumer workaround can shed its ci.env-sourcing
    half at this release, but not necessarily all of it.

  • The two stamped refs could drift, and had (Makefile.colormath,
    .github/workflows/ci.yml, LIFECYCLE.md). gates.yml's colormath-ref
    default is how CI fetches its gate scripts; Makefile.colormath's
    COLORMATH_REF is how make preflight fetches the same ones. The release
    checklist stamped only the first, so COLORMATH_REF sat at v2.0.0 while
    CI moved to v3.0.0 — preflight fetching scripts from a tag three releases
    behind the workflow.

    Harmless so far purely by luck: audit-deps.sh, migrations-sync.sh and
    diff-coverage.sh are byte-identical between v2.0.0 and v3.0.0. The
    first script change would have made local and CI disagree with no signal.

    The refs-lockstep job added here was the right instinct and the wrong
    check. It compared the two stamps to each other and never asked whether the
    ref resolved — so when this release was stamped v3.1.0 before the tag
    existed, it passed while every consumer's make preflight would have 404'd
    on every gate script. See the release-machinery entry below, which replaces
    it and makes hand-stamping impossible in the first place.

  • Releases are now atomic (release/, .github/workflows/release.yml,
    .github/workflows/ci.yml, LIFECYCLE.md). Releasing was a six-step
    checklist, and the steps came apart. An audit of all sixteen published tags
    found nine internally inconsistent: v2.1.0 through v2.4.0 each ship a
    gates.yml that fetches its gate scripts from v2.0.0, and v3.0.0's
    Makefile.colormath points three releases back. This release was itself
    stamped into main and written up here without ever being tagged, leaving
    main advertising a ref that 404s.

    The root cause was stamping forward: a human wrote the next version into
    the tree days before the tag existed, and the window between the two closed
    only if they remembered. Stamps now move only in the release commit, which is
    tagged with the version it stamps in a single git push --atomic — git
    updates both refs or neither, so the window is gone rather than merely
    shortened. In steady state main is stamped at the last released tag and
    every ref in it resolves.

    release/cut.sh is the one gesture: it refuses to start unless the tree is
    clean, synced with origin/main, and green in CI; stamps; re-verifies;
    commits; creates an annotated tag (the history alternates between
    lightweight and annotated); pushes atomically; then publishes the GitHub
    Release from this file's section for that version. A rejected push rolls the
    local commit and tag back, and a failure after the push is resumable, because
    publishing is idempotent.

    release/verify.sh replaces refs-lockstep in CI and is a strict superset:
    it covers plugin.json — a stamp site nothing checked, and which had been
    missed twice — asks whether the stamped ref resolves, and requires
    example/'s vendored copies to stay byte-identical to the root ones.
    --audit-all produced the drift table above and stays advisory, because
    published tags are never rewritten.

  • Documentation no longer names a version (README.md,
    .github/workflows/gates.yml, .github/workflows/review.yml). Nine
    copy-paste pins had rotted — the README told consumers to pin @v2.0.0 and
    the two workflow usage-comments said @v1.1.0 and @v1.0.0, while the repo
    was on v3.1.0. They now read @vX.Y.Z and point at
    /releases/latest, and verify.sh fails the PR if a concrete version
    reappears. Automating the stamping of prose would have worked; deleting the
    data was cheaper and cannot regress.

  • Every tag has a GitHub Release. All sixteen were bare; the notes existed
    only here. release/backfill-releases.sh created them retroactively from
    this file, and cut.sh creates them going forward, so
    /releases/latest is now a real answer to "what should I pin to?"

Changed

  • /colormath:review-ticket is now /colormath:refine-ticket
    (plugin/skills/refine-ticket/). Same skill, same behavior, same contract
    surfaces — only the command name moves, so that the two grooming skills read
    as the pair they are: refine-ticket for a ticket, refine-initiative for
    the initiative above it. "Review" also collided with the other review in
    this plugin — the Thermonuclear Review that ship waits on — which is an
    adversarial audit of a diff, not grooming.

    This breaks muscle memory and any docs that name the old command.
    /colormath:review-ticket stops existing at this release; there is no alias.
    MINOR rather than MAJOR under LIFECYCLE.md's test, which is
    about a consumer's CI going red without them editing anything — a skill is
    invoked by a person, and no gate, workflow input or Makefile target moves
    here. Grep your consumer repos for colormath:review-ticket when you take
    this release; product copy that tells users to run it is the likely hit.

Added

  • AGENTS.colormath.md — a third vendored file, alongside
    Makefile.colormath and eslint.config.colormath.mjs and refreshed by the
    same make colormath-update. It carries the half of a consumer's agent docs
    that is identical in every colormath app: what being a colormath app means,
    the sixteen gates with their local make mirrors and how to pass each, the
    preflight rhythm, the /colormath:ship pipeline, the layering and
    design-token and docstring conventions, the ADR practice, and the guardrails
    (never hand-edit the vendored files, never push to the default branch, never
    touch .env, justify every CVE ignore).

    The problem it solves is drift, and the drift was already there. Surveying
    four consumers, the gate suite was documented four ways — sixteen gates, nine
    gates, nine gates, seven gates — and one repo's doc named two disabled gates
    that were not the two its gates.yml actually disables. Each copy was right
    when written, and nothing marks the ones that stopped being right.

    Consumers keep an AGENTS.md of their own and import this one from it
    (@AGENTS.colormath.md), so app-specific facts stay app-specific. Two things
    deliberately do not move here: which gates a consumer disables — that is
    enable-<gate>: false plus COLORMATH_PREFLIGHT_SKIP, and prose about them
    is exactly what went stale — and where a consumer's design tokens live. The
    shared file states the rule and points at the local file for the value.

    Additive: nothing reads it in CI and no gate, input or target changes. A
    consumer that never vendors it is unaffected; one that does gets the file on
    its next colormath-update, and wires up the import in that same PR.
    example/ carries the wiring as the reference.

  • /colormath:refine-initiative (plugin/skills/refine-initiative/) — the
    layer above refine-ticket. Takes an initiative, reads its feature
    definitions and the tickets already under it, investigates the architecture
    and decision records those features land in, interviews the filer in batched
    concrete rounds, then rewrites the initiative's description and every feature
    so a team could build from them.

    It is deliberately bounded at both ends. It stops short of code: no
    file-by-file steps, no signatures, no DDL — that altitude belongs to
    refine-ticket, per ticket, later. And it never starts building, because
    that transition is one-way, locks the feature list and cuts a ticket per
    feature; there is no MCP tool for it and the skill hands back instead of
    asking for one.

    Contract surfaces it depends on (Abacus MCP): get_ticket returning
    type, initiative_status, features and children; update_ticket;
    add_feature, update_feature, move_feature; add_comment. A rename of
    any of them must ship with a skill update in the same release. It also relies
    on two current asymmetries, and names both rather than working around them:
    there is no delete tool for feature definitions, and update_feature
    replaces both fields.

    Second skill to require the Abacus MCP server, after refine-ticket.

  • /colormath:plan-initiative (plugin/skills/plan-initiative/) — runs
    refine-ticket over every ticket in an initiative, one at a time, in build
    order, injecting what that skill cannot see on its own: the initiative and its
    settled decisions, the ticket's position in the sequence, what came before it
    and what those plans decided, and what comes after it.

    The reason is the seams. Run by hand seven times, refine-ticket grooms seven
    strangers — re-deriving the same background, asking the same question seven
    times, and producing plans that each make locally sensible choices that
    contradict each other where they meet. Answers carry forward, so the questions
    thin out as the run goes.

    A ticket counts as planned only when both plan and qa_plan are set,
    re-read from the tracker rather than assumed from the sub-skill returning.
    Tasks are skipped by design — that type has no plans and the tracker
    refuses to write them. It holds no update_ticket tool, because writing plans
    is refine-ticket's job, and no Edit/Write, so it cannot touch the repo.

    Contract surfaces (Abacus MCP): get_ticket returning type,
    initiative_status, children, plan and qa_plan; add_comment. Plus the
    refine-ticket skill itself — the two ship together and a change to either's
    contract is a change to both.

  • /colormath:implement-ticket (plugin/skills/implement-ticket/) — takes
    a groomed ticket from its plan to a shipped PR, closing the chain the other
    skills start: refine-initiative designs, plan-initiative plans every
    ticket, refine-ticket plans one, this one builds it.

    It executes the plan rather than rewriting it, and the step that earns its
    keep is the one before any code: the plan was written against the codebase
    as it was
    , so every step is walked against the repo first. Where it no
    longer holds, that is a finding for the user — silently improving a plan is
    how a reviewed decision gets replaced by an unreviewed one, and sometimes the
    right outcome is "this plan no longer holds" rather than a PR.

    Then: build on a branch at the layer the plan names, execute the ticket's QA
    plan against the running stack
    (every item observed, ⚠️ when a UI item has
    no browser, failures fixed and re-run rather than shipped with the document
    claiming they passed), make preflight, and hand off to /colormath:ship.
    Deviations land in the PR body and a ticket comment; the plan and qa_plan
    fields are left alone as the record of intent. It does not move tickets
    between lanes, because lane meaning is per board.

    A ticket with no plan is sent back to refine-ticket rather than planned and
    implemented in one breath, which would mean nobody ever reviewed the plan. A
    task is refused: that type carries no plans by design and is not code work.

    Contract surfaces (Abacus MCP): get_ticket returning type, plan,
    qa_plan and the parent initiative; add_comment. Plus /colormath:ship and
    /colormath:qa's recon discipline.