Skip to content

v3.2.6rc1

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 12 Aug 16:21
· 7 commits to main since this release

Warning

3.2.6rc1 is a Release Candidate — an internal, delta build published for validation, NOT an official 3.2.6 release. It ships as a GitHub prerelease and to PyPI as a PEP 440 prerelease, so ordinary installers skip it unless you explicitly opt in with --pre. Do not use it for production or general rollout. The last official release remains v3.2.5; when 3.2.6 is finalized its changelog section supersedes this one.

Install for testing (opt-in only):

pipx install --pre spec-kitty-cli==3.2.6rc1
# or, inside an existing environment:
pip install --pre spec-kitty-cli==3.2.6rc1

Highlights since v3.2.5 — the operator-facing changes worth exercising in this candidate:

  • Breaking — built-in doctrine content relocated to packs/built-in/ with no compatibility shim (mission relocate-builtin-doctrine-packs). Repoint any reference that still targets the old src/doctrine/<kind>/built-in/ path.
  • Breaking — local beads/fp tracker sync now requires a recorded egress decision (mission tracker-egress-refusal-3108). A binding that never recorded hosted-sync consent stops syncing on upgrade until you record tracker.egress: permitted or sync.enabled: true.
  • Breaking — the rtk-search-tooling toolguide is removed, and the 3.2.6_retire_rtk_search_tooling upgrade migration strips it from projects that had it activated (it runs automatically on spec-kitty upgrade and is safe to re-run).
  • Breaking — org packs with an unrecognised agent-profile or DRG key now fail to load (mission doctrine-silence-guards). Run spec-kitty doctor doctrine --json and check skipped_profiles before you upgrade.
  • charter synthesize is now non-destructive — it preserves backed governance content by default, with --prune as the explicit opt-in and --dry-run to preview (mission charter-synthesize-reconciliation; #3270 P0, folds #2777 / #3052). The implement / next boundary no longer hard-blocks until you resynthesize.
  • Approving a work package after a rejection now sticks with no override flag required (mission review-verdict-write-integrity; #3044) — the reject → fix → approve cycle no longer forces --skip-review-artifact-check.
  • Timestamps Spec Kitty writes into your project are now correct aware-UTC instead of local time mislabelled as UTC (mission kernel-clock-single-door; #3305, closes #3289).
  • CLI UX: shell autocompletion, a -h short-help alias, and alphabetical command listing (#2232, #2234, #2235) — additive, with no behavior change to existing commands.

The complete, factual list of changes for this candidate follows in the entries below.

✨ Added

  • Multi-Agent Parallel Development and Orchestrator Quickstart tutorials now open
    with illustrated Mission Kitty splashes (#3331).
    Each page adds a decorative hero and
    a one-line caption that points to the authoritative workflow steps in the prose
    below, matching the pattern introduced in #3276.

  • Governed Charter Workflow, Claude Code Integration, and Claude Code Workflow
    tutorials now open with illustrated Mission Kitty splashes, plus TIP cross-links
    and hub related: edges (#3333).
    Each page adds a decorative hero and a one-line
    caption; the Charter tour points at the governance how-tos, and the two Claude
    Code tutorials link each other for slash-command vs CLI/dashboard paths.

  • Three #3276 Mission Kitty heroes are regenerated as decorative-only splashes
    (missions overview, your first mission, when-to-use-modes alternate art) (#3341).

    Replaces caption-mitigated board labels with abstract art; each PNG compressed
    to ≤500 KB. Memphis, four-paths, and corporate-comics assets on when-to-use-modes
    are unchanged.

  • The DRG now carries a whole-graph, action-only reachability companion guard, and six genuine residual
    orphans are wired into the graph (mission drg-reachability-metric-wiring-01KZS5VR; #3009 point 3,
    #1923).
    TestReachabilityCompanionGuard in tests/doctrine/drg/test_reachability.py asserts every
    activatable-kind node reachable from neither the action channel nor the profile channel's
    {requires, specializes_from, suggests} web is correctly partitioned into "both-channel dead" vs
    "profile-delivered" — live totality/disjointness assertions against the graph measured at test time, not
    an exact-membership pin (the ever-growing frozenset-literal pin was softened out during PR #3342 landing,
    consistent with mission assertive-test-suite-sanitation-01KZME3P's "test plausible graph behavior, not
    exact ever-growing membership"). A fixed anti-gaming gate (TestActionUnreachableShippedLedgerCoverage)
    independently proves the thirteen URNs this mission wires are genuinely action-reachable and named in a
    wiring-table ledger row, so a node with outbound edges but no inbound path still cannot pass silently. Six
    traced inbound edges (procedure:refactoring → DISCIPLINED_REFACTORING, DIRECTIVE_024/DIRECTIVE_025
    RECONCILE_CHANGE_SCOPE_TENSIONS, DIRECTIVE_030USE_MUTATION_TESTING_TO_VALIDATE_TEST_QUALITY,
    researcher-robbie → spike-timebox-policy, lexical-larry → glossary-maintenance-workflow,
    minutes-maker-mahad → meeting-minutes-pipeline) move the action-only whole-graph residual 88 → 75 and
    the both-channel-dead subset 38 → 34 (descriptive measurements at time of wiring, not pinned
    assertions). The #1923 DRG orphan-residual record
    (kitty-specs/mission-lifecycle-dispatch-drg-closeout-01KV0S99/drg-orphan-residual.md) is truth-upped
    against the wired graph: every one of the 75 residual members now carries an individual or group
    disposition, toolguide:rtk-search-tooling is retired (already removed from disk), and only the genuinely
    action-reachable members of the prior "6 promoted" claim are recorded as promoted.

  • Documentation pages can now declare who they are for: a canonical audience:
    frontmatter field, plus a Common Docs styleguide and gates, ship in the built-in
    doctrine pack (mission common-docs-convergence).
    Each page states its
    intended reader and Divio type, and projects that activate the pack inherit the
    same Common Docs conventions — the single-root structure and audience/type
    discipline — out of the box.

  • charter synthesize is now non-destructive — it preserves backed governance
    content by default (mission charter-synthesize-reconciliation; #3270 P0,
    folds #2777 / #3052).
    Previously an authoring-only charter edit forced
    operators to run charter synthesize, which silently deleted doctrine graph
    nodes and edges whose backing artifacts still existed on disk — and implement
    / next hard-blocked until you ran it. Now synthesize reconciles against
    the on-disk graph: backed content is retained and the command reports what it
    kept (exit 0); --prune is the explicit opt-in that removes divergent content
    and lists every deletion; --dry-run previews exactly what --prune would
    remove and writes nothing; and a non-zero refusal is reserved for genuinely
    unpreservable states (orphaned removal without --prune, or an unparseable
    overlay). The implement / next boundary auto-refresh now self-heals
    non-destructively and clears the stale signal so you are never trapped, and
    charter activate / deactivate go through the same preserve path. Consumer-pack
    synthesis also emits charter-relevant edges from declared interview evidence
    (#3052) — no more orphaned just-generated directives, and no fabricated edges.

  • spec-kitty accept --json now surfaces stranded-verdict advisories in a
    top-level advisories array (mission verdict-seam-boundary-hardening;
    #3255).
    When a mission carries a review verdict that no longer has a home
    (an SC-008 "stranded verdict"), accept --json reports it as a structured,
    non-error advisory alongside the normal payload instead of staying silent — so
    operators and automation can see and act on it without scraping human-readable
    output.

  • Spec Kitty's own charter now activates the writing-comms & diagramming
    doctrine set - this repository only; no downstream impact (builds on #2918 /
    #3225).

    Spec Kitty now dogfoods "The Magnificent 7" writing-comms doctrine in its own
    .kittify/charter/: audience-oriented writing (DIRECTIVE_047 +
    writing-audience-catalog + the plain-language and
    professional-communications styleguides), the documentation-structure
    styleguides (divio-type-discipline, docs-accessibility, docs-freshness-sla,
    publication-authority), C4 diagramming (USE_C4_MODEL_TECHNIQUES, alongside the
    already-active Mermaid/PlantUML toolguides), the communication-governance
    directives (048 version-governance, 049 agent self-introduction, 050
    credential-handling), the glossary-maintenance-workflow procedure and
    research-citation-discipline styleguide, and the seven writing-comms agent
    profiles (comms-cleo, diagram-daisy, analyst-annie, lexical-larry,
    minutes-maker-mahad, scribe-sally, synthesizer-sam). It also activates
    RECONCILE_CHANGE_SCOPE_TENSIONS to bridge the smallest-viable-diff / Boy Scout /
    locality-of-change tension, explained in a new charter.md section.
    Scope: this changes Spec Kitty's own project governance only. The doctrine
    artifacts already ship in packs/built-in; projects created or upgraded by the
    CLI are unaffected and keep their own charter activations.

  • A new "When to use Spec Kitty modes" guide helps you pick the lightest path
    for the work at hand (#3238).

    docs/guides/when-to-use-modes.md lays out
    four entry points - skip Spec Kitty for a trivial edit, dispatch an ad-hoc
    specialist for a short task, run a bounded adversarial squad at an SDD
    checkpoint, or drive a full mission when acceptance criteria and structured
    review matter - with an illustrated overview, so you match effort to the
    change instead of paying full-mission overhead on every edit.

  • spec-kitty charter pack apply --compile now applies a pack and compiles
    its bundle in one step (mission charter-pack-usage-journey; #3105).

    Previously apply only merged activations into config.yaml and left you to
    discover that a separate charter generate was still needed; the default
    apply output now names that exact next command, and --compile does both at
    once (inheriting generate's git-worktree requirement). charter context --include section:terminology-canon / section:code-review-checklist also no
    longer dead-end when the section is unauthored - they resolve to an honest
    placeholder pointing you at the file to edit (#3095, #3094, #2552).

  • Empty/unconfigured charter now dispatches to a warned generic agent instead
    of silently applying every built-in doctrine artefact (mission
    charter-delivery-finish-context-degod; #3064).
    Previously, running a
    dispatch in a repository with no charter activated silently fell back to
    "all built-ins" - the entire shipped doctrine catalogue was applied in both
    routing and the governance context injected into the prompt. Now, when
    nothing is activated (a composite check across every charter-activatable
    dimension), auto-routing resolves to a generic agent using no charter
    doctrine
    - verified across both routing and the governance block, so no
    directive canon leaks into the prompt - and the user gets a clear warning
    to activate a charter first. The software-dev mission type stays
    always-available, and explicitly passing --profile <name> still resolves the
    specialist even under an empty charter (only the no-hint auto-route path
    changes; the shared activation gate is untouched).

  • A minimal starter charter now ships as a first-class, on-demand charter
    pack (mission charter-delivery-finish-context-degod; #3064).
    It ships
    as src/charter/packs/minimal.yaml, alongside default.yaml. Run
    spec-kitty charter pack list to see the shipped built-in packs,
    spec-kitty charter pack path minimal to resolve the file, or
    spec-kitty charter pack apply minimal to merge its small, curated
    activation set into .kittify/config.yaml instead of authoring one from
    scratch - it is additive by default and never silently overwrites an
    existing activation (--force to overwrite explicitly).

  • Profile-channel suggests-edge delivery is now live - the inert #3063
    doctrine topology actually delivers (mission doctrine-delivery-activation,
    fast-follow to doctrine-delivery-reachability).
    The profile-channel
    reachability walk now follows suggests edges (in addition to requires /
    specializes_from) and surfaces each edge's when clause as the delivered
    doctrine's applicability condition - as when-labelled links, not eager
    bodies (respecting the context-bloat budget). Architect and implementer
    profiles now receive their linked paradigms, tactics, techniques, and
    canvases: e.g. architect-alphonso reaches domain-driven-design, the C4
    model techniques, and the REASONS-canvas guidance; implementer profiles reach
    the DISCIPLINED_REFACTORING refactoring tactics. Companion authoring: a C4
    template:instantiates edge from action:documentation/design, and
    refactoring code-smell anti_pattern nodes wired via REJECTS (grounded in
    each tactic's attested problem/when). The reachability pins, the delivery
    wiring table, and the forward-API dead-symbol allowlist are reconciled to the
    now-live topology, each moved count carrying a composition-ledger entry. (#3063)

  • The asset doctrine kind is now reachable end to end - resolve, deliver,
    and author it (mission doctrine-delivery-reachability).
    Previously a
    shipped asset (executable logic or any blob a pack hands to a downstream repo)
    had no operator surface and no followable how-to, so the documented remedy
    "ship it as an asset" was not actually followable (#3037). Now:

    • spec-kitty doctrine asset list enumerates every resolvable asset with
      its source tier (built-in / org / project), and spec-kitty doctrine asset path <id> resolves one identifier to a filesystem path - exit 0 on
      success, non-zero with the id named on an unknown id or a containment
      refusal. Nothing is installed into the consumer repo; assets resolve from
      packaged data plus the project/org overlays (no auto-install). The one
      built-in asset, common-docs-structural-lint, resolves from any
      installation.
    • spec-kitty doctrine new --kind asset <name> scaffolds an asset with
      the same parity as validate, writing into the directory the resolver reads
      (.kittify/doctrine/assets/).
    • The action doctrine bundle now delivers every resolved kind, including
      procedures and assets. The delivery gate is a total function over kinds:
      activation-gated kinds deliver activated ∩ reachable, while assets are
      delivered-but-not-activation-gated (gate = ALL) - a reachable source
      pulls them in without an activation list. This closes the defect where
      asset_ids = [] was the silently-conforming outcome forever.
    • Docs: Create a doctrine artifact
      gains an executable asset how-to (author a manifest, place the blob, resolve
      it), and Doctrine artifact kinds documents
      the shipped built-in asset and the three delivery categories.
  • New checks that catch a change which looks like it worked and did nothing
    (mission doctrine-silence-guards).
    Four additions, all aimed at the same
    failure mode - a declaration that loads, validates, reports success, and then
    has no effect:

    • A zero-producer lint fails the build when a schema field is declared but
      no code path ever writes it. Three such fields had shipped in this
      repository, one of them inert for months behind passing tests. The findings
      that already exist are frozen as a shrink-only baseline; the list can only
      get smaller, every entry names an owner and the fix it is waiting on, and a
      test refuses to let an entry outlive the work that was meant to remove it.
    • scripts/generate_schemas.py --check now runs in CI, in the always-on
      lint job rather than behind a path filter, so a model change that leaves
      its generated YAML schema behind fails immediately. Seven schemas were
      already stale and are reconciled here.
    • spec-kitty doctor doctrine now reports org-pack edges that point at
      nothing
      (see Breaking Changes).
    • Occurrence maps can protect a single field inside a file that is otherwise
      migrating.
      The do_not_change classification used to accept only
      whole-file path globs, so a file containing both governed keys and keys that
      should be renamed could not be described. It now accepts a YAML field path.
  • CLI UX: shell autocompletion, a -h short-help alias, and alphabetical
    command listing (#2232, #2234, #2235).
    Three additive quality-of-life
    improvements to the spec-kitty command surface, with no behavior change to
    existing commands:

    • Shell autocompletion is now enabled (--install-completion /
      --show-completion), with a latency-safe fast path: every TAB press serves
      command/subcommand candidates from a small committed manifest
      (_completion_manifest.json) instead of importing the whole command tree,
      cutting completion invocation latency from roughly 812 ms to about 76 ms. A
      drift-guard test keeps the manifest in sync with the live command tree.
    • -h is accepted as an alias for --help across the root command and
      every subcommand.
    • The root --help command list is now sorted alphabetically, and a bare
      spec-kitty invocation renders the same ordered help.
  • spec-kitty sync import-history materializes existing local mission history
    into the SaaS projection (#2262).
    A first sync registers a remote
    project/build but leaves it with zero materialized missions - the SaaS
    materializer refuses to fabricate a work package from a status event with no
    prior create. The new command synthesizes the missing
    MissionCreated -> WPCreated[] -> WPStatusChanged[] prefix (INV-3) from local
    history so historical work populates the projection. --dry-run (default)
    runs the whole read-only pipeline and previews the stream; --apply attaches
    a sha256 provenance manifest, runs the offline envelope contract gate, then
    server-preflights the entire stream before uploading anything (fail-closed -
    a rejection leaves the projection untouched) and uploads in chunks.
    Deterministic event ids make re-runs idempotent (the server dedups on
    event_id). Buildable slices Y1-Y5 ship here; Y6/Y7/Y8 remain gated.

  • Doctrine packs can now ship supporting files to consumer repos - the first
    is a docs structural-lint that keeps a project's documentation organized
    (#2302, #2864-#2867).
    A doctrine pack can now carry an arbitrary
    addressable file (a script, a blob, a .docx) that travels with the pack and
    resolves in a consumer repo with no access to our source tree - the new
    built-in asset doctrine kind, shipping its first member. That inaugural
    asset, common-docs-structural-lint, checks that docs land in the right
    section, that redirect stubs and frontmatter follow the house contract, and
    that retired shadow doc-trees don't reappear; it is wired into the
    docs-freshness CI workflow as a blocking gate and is pulled in automatically
    whenever a common-docs artifact is activated. Alongside it, a one-time
    structural-sanity sweep redistributed nine misfiled architecture/ notes,
    retired the plans/notes/ 1.x shadow tree, and repointed every referrer with
    zero dead links; the common-docs styleguide gains a machine-parseable
    structural_lint_config block (with a redirect_stub_description_prefix
    exemption key) as the lint's single source of truth; and the docs
    reference-rewrite helper no longer doubles a destination path (.../x.md/x.md)
    on a file-level move. No CLI product code (src/specify_cli) changed.

  • Fork packaging hooks (entry-point plugins + DistributionProfile).
    Renamed / private-index forks can customize CLI package identity, upgrade
    providers (including built-in PEP 503 SimpleIndexProvider), remediation
    index URLs, data-freshness TTL, and the public-PyPI notifier via
    spec_kitty.cli_package, spec_kitty.upgrade_provider, and
    spec_kitty.distribution_profile - without overlaying src/specify_cli/**.
    Stock public-PyPI / spec-kitty-cli installs with no hooks are unchanged.
    Packager guide: docs/guides/fork-packaging-hooks.md.

  • Doctrine-controlled transition gates - the for_review pre-review gate is now
    declared by the repo's active doctrine, not hardcoded to Spec Kitty's own repo
    shape (#2595, #2596, #2598; epic #2535 half A).
    Scope resolution moves behind a
    layout-agnostic ScopeSource port (GateCoverageScopeSource preserves the
    incumbent pytest/JUnit path byte-identically; DeclaredCommandScopeSource runs a
    consumer's declared review.test_command with baseline-relative verdicts). A
    named GATE_REGISTRY registers the pre-review engine as the first handler; a
    versioned gates binding on the mission_step_contract review contract (with an
    inert handler_kind seam for future executable-asset gates) is resolved through
    charter activation and dispatched by the inverted _mt_run_transition_gates
    hook with deterministic aggregation and per-handler fail-open (two hard-stops
    preserved). A new built-in mission_step_contract.graph.yaml DRG fragment makes
    the gate fire on the source repo. Behaviour parity is enforced by a golden
    captured from the pre-refactor base. Closes the pre-review facet of #2534 (the
    always-on tests.architectural._gate_coverage consumer-repo import is removed -
    a consumer never reaches it, even under erroneous activation) and #2330 (the
    pytest-layout papercut). Half B (executable gate assets, #2599) is out of scope.

  • The pre-review test gate now works correctly for projects that use a custom
    test command, not just pytest (#2873, follow-up to #2535 half A).
    When a work
    package moves to for_review, Spec Kitty runs the project's tests and flags any
    failure the change newly introduced - comparing against a baseline captured
    before the work started. Previously the baseline side and the review side could
    run those tests two different ways, so a project configured with its own
    review.test_command (anything other than pytest) got unreliable results: it
    could be wrongly blocked on failures that were already there, or - worse - a
    genuinely new test failure could slip through unflagged on a clean baseline. Now
    both sides run and interpret the tests through one shared path, so the
    comparison is apples-to-apples and a new failure is reliably caught. When the
    two sides genuinely can't be compared (for example, the test command was
    changed in between), the gate now says so with a clear, non-blocking warning
    (shown as SOURCE_MISMATCH) instead of guessing - it never silently passes and
    never hard-blocks on that case. The captured baseline is now always saved, so
    review no longer gets stuck on an uncommitted baseline file. Internally, ~450
    lines of dead, duplicated test-scoping code left over from an earlier design
    were removed.

  • WP runtime-state evicted into the append-only event log (#2684, #2093).
    Runtime-mutable work-package state - shell_pid, subtask completion,
    ## Activity Log notes, tracker_refs, agent/assignee, and review-cycle
    fields - is folded through a single off-axis InnerStateChanged event and read
    from the reduced status snapshot, so tasks/WP##.md stops mutating on runtime
    events (byte-stable dossier content hash, AC-5). Shipped as dual-write behind
    the phase-1 flag
    (_phase1_dual_write_enabled, default off): the event log is
    the authority at status_phase: 1, while legacy frontmatter remains the default,
    sanctioned migration-window fallback. The corpus backfill -> verify -> cutover,
    the unconditional flag flip, and the legacy-fallback deletion are deferred to
    follow-up #2816.

  • Charter bundle consolidated into an authoritative charter.yaml (#2773).
    The four compiled bundle files (governance.yaml, directives.yaml,
    metadata.yaml, references.yaml) plus config.yaml's activated_* keys
    fold into a single git-tracked, authorable charter.yaml that becomes the
    project's authoritative structured charter; charter.md becomes a curated
    companion. The bundle manifest bumps to schema 2.0.0 (charter.yaml is the
    sole content-hash input), and config.yaml gains a one-line charter:
    pointer. A deterministic, idempotent, fail-loud upgrade migration performs
    the fold, sequenced strictly after the 3.2.6 activation-seed migrations
    (verbatim activation copy; the seeds' post-state - config carries
    activated_* - is the fold's pre-state).

  • charter activate/deactivate --resynthesize opt-in eager refresh (FR-007, #2761).
    Since the config↔derived freshness seam (#2759) made charter activate/deactivate
    visible to the synthesized_drg freshness signal, a routine activation now reports
    stale until the operator reconciles it. The new --resynthesize/--no-resynthesize
    flag (default off) gives operators an escape hatch: passing it re-runs the existing
    charter generate + charter synthesize pipeline right after the config write, so the
    derived bundle/DRG - and the freshness signal - are fresh immediately, without building
    a second reconciliation pipeline. The default (no-flag) path is unchanged: a fast,
    config-only write that spawns zero synthesis calls, locked in by a call-count spy
    regression test (NFR-001); the spec-kitty upgrade migration and org_charter
    promote_activations paths are untouched and remain synthesis-free (NFR-003).

  • Development-assist test cleanup is now a standing wrap-up procedure (#2557).
    A new built-in development-assist-test-cleanup procedure codifies a proactive,
    judge-the-test pass at mission close (identify -> judge -> execute -> scope-the-residue)
    that retires or splits the scaffolding tests a mission wrote to drive its own
    slices - characterization/parity oracles, before/after timing gates, per-WP
    shape / __module__ / delegate assertions - before they land as permanent suite
    bloat. It applies DIRECTIVE_041's keep-vs-delete axis and its
    duplicate-test-knowledge-has-one-source rule as the proactive companion to that
    reactive failing-test rubric, and is scoped distinct from DIRECTIVE_025
    (pre-existing debt in touched areas). It is wired into mission-wrap-up-sequence
    as a new "retire or split the mission's development-assist tests" step after
    acceptance (DRG: +1 node, +5 edges), and the wrap-up hand-off / notes summary now
    records which tests were retired / split / kept-and-renamed.

  • Relocation-hardened architectural dead-code scanners + content-addressed
    ratchet allow-lists (#2546, #2547, #2072, #2548, #2077).
    The tests/architectural/
    ratchet allow-lists are re-keyed off position anchors (line numbers, module::Name)
    onto content descriptors, and the 394-entry dead-symbol allow-list onto a
    relocation-tolerant SymbolKey - content-only by default, escalating a
    byte-identical same-name collision to a module_path tier (or fail-closing) via a
    live, gate-time collision classifier - so a behaviour-preserving relocation no
    longer forces a hand-edit without re-blinding the T004 no-false-negative
    invariant
    . Adds a standing int-to-line-sink meta-guard, a tier-specific
    dangling-entry ratchet, and the full production-path bite battery. The relocation
    promise is honestly downscoped: relocation-proof for simple single-definition
    entries; re-export/facade/fan-out entries stay module_path-keyed (documented, not
    a bug). Ships with a CaaCS change-coupling analysis of the test suite (epic #2071).

  • First-class TEMPLATE + loose-contract ASSET doctrine kinds (#2495, #2469).
    Org-pack templates are now first-class, graph-addressable, edge-wireable
    DRG nodes, and a new loose-contract asset doctrine kind lets a pack ship
    and reference arbitrary addressable files (image, .docx, blob) via a sidecar
    *.asset.yaml manifest (id/mime/path, no blob schema). A single canonical
    _NON_AUGMENTATION_ELIGIBLE_KINDS set makes both kinds node-declarable but not
    augmentation-eligible or charter-activatable - closing the silent-leak defect
    class by construction. Safety is enforced from day one: global URN-uniqueness
    for asset:/template: (a single post-merge scan -> duplicate_asset_id /
    duplicate_template_id), path-containment (asset_path_escape), and mime
    validation
    (asset_mime_invalid). A new totality guard asserts every
    ArtifactKind/NodeKind-keyed mapping table stays exhaustive. The 9 existing
    kinds are unchanged.

  • External orchestrators can submit structured review outcomes without
    forcing lane transitions.
    orchestrator-api transition now accepts
    --review-result-json with reviewer, verdict, and reference fields, allowing
    guarded in_review exits to remain fully host-enforced. The additive contract
    change bumps the orchestrator API to 1.3.0.

  • Dashboard WP cards show subtask progress (#2504). Cards (and the WP
    detail view) previously showed a bare frontmatter count (4 subtasks);
    they now show 2/4 subtasks (with a ✓ at n/n), counted from the canonical
    checkbox rows in the WP body - the same rows the lane-transition guard
    blocks on, via a new shared single definition
    (core/subtask_rows.py, now consumed by both the guard and the dashboard
    so the two can't drift). WPs that don't track completion via checkboxes
    keep the plain count badge (no false 0/N). The kanban task payload gains
    additive subtasks_done/subtasks_total fields; the typed-contract
    baseline is regenerated accordingly.

  • Seven activated governance artefacts are now reachable from the doctrine
    graph, so charter activate --cascade pulls them in.
    Each of these was
    shipped active in the default charter pack but had no inbound edge from
    anything, which meant no cascade, no traversal, and no way for an agent to
    arrive at it except by naming it directly. The authored edges are:
    DIRECTIVE_035 -> occurrence-classification-workflow, DIRECTIVE_003 ->
    decision-marker-capture, DIRECTIVE_030 -> no-parallel-duplicate-test-runs,
    DIRECTIVE_030 -> red-main-release-discipline, python-conventions ->
    python-review-checks, atomic-design-review-checklist -> atomic-design, and
    structured-prompt-driven-development -> reasons-canvas-writing. This
    changes what a cascade brings in for your project:
    activating one of the
    seven source artefacts with --cascade now also activates the target that
    was previously stranded. If you have been relying on a cascade to produce a
    specific, narrow set, re-check it after upgrading - the sets are now larger
    by design, because the targets were always meant to travel with their
    sources.

🐛 Fixed

  • A corrupt, truncated, or wrong-authority meta.json is now rejected loudly by
    every mission read path instead of being silently accepted (mission
    meta-json-fail-closed-routing; closes epic #3259#3228 / #3229 /
    #3230 / #3240).
    A mission's meta.json is its canonical identity and
    VCS-lock record. Several internal read paths — git ref-advance, implement, and
    the acceptance-matrix merge-driver — still decoded it through hand-rolled parsers
    that would quietly accept a malformed or wrong-authority file, exactly the
    split-brain / wrong-authority failure the metadata-authority work exists to close.
    Before, a bad meta.json could let a mission proceed on corrupt identity state
    and surface later as a confusing, hard-to-trace failure; now every remaining read
    routes through one fail-closed decode seam and fails immediately with a clear,
    path-named error. The duplicated VCS-lock comparison — two copies that could
    return contradictory verdicts on the same file — is also unified into a single
    authority.

  • Two blocking CI gates now reflect what a PR actually changed (mission
    ci-scoping-gate-reliability; #3008, #3147).
    Corpus data no longer ships
    unguarded (#3008):
    a PR that changed only non-source corpus data — shipped
    doctrine under packs/**, a mission's planning artifacts under kitty-specs/**,
    or charter config under .kittify/** — never triggered the quality workflow at
    all, so every corpus-reading suite was silently skipped pre- and post-merge and a
    regression in shipped data shipped invisibly. Narrow, discrete trigger globs now
    start the workflow on such changes, and a new blocking fast-tests-corpus job
    runs the corpus-reading suites (selected by a @pytest.mark.corpus marker, so
    already-covered suites are not re-run) and gates the merge. The trigger
    deliberately excludes lifecycle churn (status.events.jsonl, notes, trace) so it
    does not fire on every mission PR. Docs dead-link gate no longer over-fires
    (#3147):
    the blocking dead-link / related-edge check scanned the whole tree and
    failed a docs PR for pre-existing broken links in files it never touched. The
    blocking check is now scoped to the PR's own changed files (fail-closed on an
    unresolvable base ref — a shallow clone or unfetched base errors rather than
    passing trivially), while the unfiltered whole-tree scan is retained as the
    non-blocking push: main backstop so genuine repo-wide rot is still surfaced.

  • The charter and sync modules are cleared of their Sonar maintainability
    backlog, including a super-linear-backtracking regex (mission
    charter-sync-sonar-remediation; #3232-adjacent).
    80 SonarCloud findings
    across the two modules — 27 over-complex functions, 15 duplicate literals, 20
    malformed suppression comments, unused parameters, too-many-parameter
    signatures, and one ReDoS-class regex flagged BLOCKER — were resolved with
    behavior-preserving refactors and no new suppressions. Over-complex
    functions were brought to the ≤15 cognitive-complexity ceiling via tested
    helper extraction (each helper carries a focused test); duplicate literals
    became named constants; the token_budget heading regex was rewritten to
    remove its ./\s ambiguity (proven match-equivalent by a characterization
    test); and the two too-many-parameter event emitters (emit_wp_status_changed
    and emit_token_usage_recorded) were slimmed by bundling their optional tail
    fields into typed params objects (WPStatusChangeMetadata /
    TokenUsageMetadata), with every call site migrated to the params object so
    the tail stays fully type-checked rather than routed through an untyped
    **kwargs bag. Two findings are Sonar false-positives (a Pydantic
    PrivateAttr and an already-single-return method) with no clean code fix;
    they remain for a SonarCloud UI won't-fix. No runtime behavior changes.

  • Signature change for out-of-tree callers of two exported event emitters
    (mission charter-sync-sonar-remediation; #3317).
    emit_wp_status_changed
    and emit_token_usage_recorded (re-exported from the sync facade) no longer
    accept their optional tail fields (causation_id, force, evidence,
    run_id, provider, model, …) as individual keyword arguments. Pass a
    WPStatusChangeMetadata / TokenUsageMetadata object via the keyword-only
    metadata= parameter instead. All in-tree callers are already migrated; only
    external plugins that called these helpers with the old keyword tail are
    affected.

  • Restored an honest red CI gate: main's two standing reds now go green for
    the right reason, and move-task's pre-review gate stops crying wolf
    (test-layer only; epic #3260 — "a red gate must mean a real regression").

    This is a CI/gate-correctness pass with no runtime or behavior change — no
    src/ change, no __init__.py touch, no version bump — so nothing you install
    or run changes. What changes is that the project's own signal is trustworthy
    again:

    • The blocking regression tests job goes green because the last resident
      red-first reproduction (#2782) was retired. That repro asserted agent mission create --json synchronously emits a direct ingress skipped
      diagnostic on stderr — a contract the deferred/offline sync architecture
      cannot satisfy (mission-create queues its lifecycle event and dossier bodies
      to the offline outbox and returns without an in-process ingress attempt, so
      the diagnostic never fires there, even with consent recorded). It was
      redesigned into a green functional test of the architecturally-honest
      mission-create contract (rc=0 + strict-JSON stdout + no diagnostic prose on
      stdout + the #2254 drift guard), moved back beside its strict-JSON siblings,
      and un-marked regression. -m regression now collects nothing, so the
      blocking job passes. (The diagnostic's real firing is still proven at the
      resolver seam by a separate test.)
    • The integration-tests-cli job goes green because a drifted test was
      locking a since-corrected contract at the commit_to_branch seam. Following
      #3269 / commit 793872a19, only a genuine empty changeset maps to
      unchanged; a rejecting pre-commit hook is a real failure. The test was
      rewritten to assert the corrected contract (hook rejection re-raises and
      leaves the artifact dirty).
    • move-task's pre-review gate stops reporting phantom "new failures"
      two false reds that made the reliability gate untrustworthy (both children of
      epic #3260). tests/doctrine/test_hatch_build.py now guards its transitive
      import with pytest.importorskip("hatchling"), so the pre-review gate's
      ephemeral baseline venv (which lacks hatchling outside the test extra)
      skips it cleanly instead of hard-failing collection; the normal test job
      installs the extra and still runs every test (#3224). And
      test_inline_meta_read_gate now relativizes scanned paths against the scanned
      tree's own root rather than the gate file's location, so a cross-tree
      (git-worktree baseline) scan no longer produces absolute paths that silently
      over-count mission_metadata.py as a violation (#3241, PART 2).
  • The #2804 gate-artifact merge invariant is guarded again at the driver level,
    and the doctrine module's Sonar maintainability debt is cleared (mission
    gate-artifact-merge-driver-unit-gate; #3232).
    A prior refactor (b04da00e1)
    deleted the unit test that pinned "a merge must never reset a filled acceptance/
    issue gate artifact back to a scaffold placeholder", and it was never restored
    because it had been written for a whole-file heuristic the row-union authority
    model (#3076) replaced. This restores that guard as a fast, in-memory unit gate
    over the shipped row-union reconcilers: a filled/accepted criterion is never reset
    to the scaffold marker, the accepted evidence handle survives a merge (including
    inside a structured conflict marker), and the merged verdict stays in its
    admissible domain — each with a falsifying control. Bundled opportunistically:
    the src/doctrine/ Sonar backlog is cleared — 37 duplicate-literal findings hoisted
    to named constants, 7 over-complex functions reduced to the ≤15 cognitive-complexity
    ceiling via tested helper extraction, and 3 minor smells resolved — all
    behavior-preserving with no new suppressions. (One 183-complexity function is
    deferred to a dedicated mission.)

  • spec-kitty auth login (and any caller of the SaaS URL helper) now points
    you at the real hosted service when SPEC_KITTY_SAAS_URL is unset, not a fake
    placeholder (#3297, closes #3296).
    Previously, running the command
    without that variable set told you to Set it to your spec-kitty-saas instance URL (e.g. https://api.spec-kitty.example.com) — a .example.com placeholder
    that does not resolve, so a first-time user who copied it got a dead URL. The
    guidance now names the actual hosted URL, https://app.spec-kitty.ai, so the
    example is copy-paste-usable; self-hosted instances still override via the env
    var exactly as before.

  • Timestamps that Spec Kitty writes into your project no longer record local
    time while labelling it UTC (mission kernel-clock-single-door; #3305,
    closes #3289, owns the closed #3288).
    Roughly twenty places across the
    tool — charter backup filenames, status-event stamps, auth-doctor report times,
    and other persisted "now" values — read the machine's local clock and then
    tagged the result as UTC. On any machine not set to UTC the stored time was
    simply wrong: a backup taken at 09:00 CET was filed as …T09-00-00 and read
    back as 09:00 UTC, an hour or two off. Every such value now comes from one
    canonical aware-UTC producer, so stamps are correct and consistent regardless
    of the host timezone. Under the hood all wall-clock reads now go through a
    single kernel.clock "door" that every package can import, and a repo-wide CI
    gate blocks any new raw datetime.now() / time.time() read from
    reintroducing the drift — but the change you can observe is simply: the
    timestamps are right now.

  • spec-kitty doctor auth --fix now reports a real disk failure instead of
    quietly claiming "nothing removed" (mission sonar-bug-blocker-remediation).

    When breaking a stale auth lock hit a genuine I/O error — a full or failing
    disk — force_release mislabeled it as ordinary lock contention, so the
    command told you nothing was wrong while the real fault went unseen. It now
    lets genuine filesystem errors propagate while true lock contention still
    returns cleanly, matching the rest of the locking layer.

  • Activating a slug-named hub directive (e.g. use-c4-model-techniques) in a
    charter now resolves to the real doctrine node instead of a dangling
    identifier (#3009, #3298).
    The directive-id normalizer folded numbered
    slugs (024-...DIRECTIVE_024) but left slug-named directives hyphenated
    and uppercased (use-c4-model-techniquesUSE-C4-MODEL-TECHNIQUES), which
    is not the artifact's canonical node id (USE_C4_MODEL_TECHNIQUES). So
    activating or referencing such a directive by its slug silently pointed at
    nothing. The normalizer now folds hyphens to underscores, matching the
    canonical node id — so slug-hub directives activate and cascade like any other.

  • spec-kitty tracker sync publish on a local (beads/fp) binding now
    prints a clear error instead of crashing with a Python traceback (#3168).

    Local providers have no snapshot-publish transport, but the command delegated
    to the backend unconditionally and hit an uncaught AttributeError, which the
    CLI let escape as a raw traceback. It now exits with a clean
    "not supported for local providers — use tracker sync push instead" message.

  • charter generate is now idempotent — a second run no longer degrades the
    compiled catalog (#3292).
    Two independent active_languages computations
    fed a feedback loop: generate stamped catalog.languages: [] for a
    language-agnostic charter, and the language-scope gate then read that empty list
    back as an authoritative "admit no languages", degrading language-scoped
    styleguide/toolguide titles and summaries to a "Definition unavailable in bundled doctrine" placeholder on the next run. active_languages now has a
    single authority and an empty result means "no signal → admit all" (round-tripped
    as an absent field, not a persisted []), so repeated generates are byte-stable.

  • The reason spec-kitty sync doctor gives for a refused tracker egress can
    no longer disagree with what the gate actually enforced (#3287, #3291).

    The gate decided whether to refuse from one source but re-derived the why
    and the remedy shown to you — from a second, independent consent lookup that
    could drift from the enforced answer, and it repeated a full git-identity
    resolution on every gated sync. The reason now comes from the same evaluation
    that enforces the decision: a refusal's explanation always matches the
    refusal, a consent record that cannot be read is reported as its own state
    instead of masquerading as "no consent recorded", and a gated
    tracker sync resolves your project's consent once instead of twice.

  • A spec commit that genuinely FAILS is no longer silently reported as
    "unchanged" (#3269).
    When a git commit failed for a real reason — a
    rejecting pre-commit hook, a lock error — safe_commit collapsed every
    non-zero exit onto the same "nothing to commit" path, so spec-commit printed
    "Spec artifact(s) unchanged, no commit needed" and the spec silently never
    landed. Emptiness is now decided by the staged tree (git diff --cached),
    not by matching git's output text: a genuine no-op is still reported as
    unchanged, while a real failure surfaces as an error carrying git's own
    diagnostic — even when a failing hook prints its own "nothing to
    commit"-shaped message.

  • spec-kitty no longer crashes when an arbiter override lands on a
    conflict-marked review-cycle file (mission verdict-seam-boundary-hardening;
    #3244).
    An arbiter override written against a review-cycle-N.md that
    still carried unresolved merge-conflict markers used to raise instead of
    resolving the latest cycle; the cycle number is now read from the filename
    alone, so the override applies cleanly.

  • SaaS tracker retry/poll tests no longer flake on CI, and a new gate keeps
    the whole bug class out (mission sync-sleep-count-3136; #3136).
    These
    tests asserted on time.sleep call counts and failed non-deterministically
    with errors like Expected 'sleep' to be called once. Called 179 times. on
    test nodes a change never touched — because patching the shared stdlib
    time.sleep recorded sleeps from anything else running in the same test
    worker. Building on #3187 (which gave SaaSTrackerClient its own
    self._sleep seam), the client now also binds self._monotonic /
    self._randbelow, so its retry/poll timing and jitter are fully isolated from
    process-global state. A new architectural gate
    (tests/architectural/test_shared_module_object_patches.py) refuses any future
    test that patches a shared module object read by a count/equality assertion, so
    this class of flake fails review instead of shipping.

  • Restored green CI on two suites that regressed after the single-PACKS_ROOT
    / charter-activation unification (mission resolution-activation-foundation).

    tests/review/test_pre_review_gate_integration.py and
    tests/merge/test_profile_charter_e2e.py still built bare fixtures that never
    provisioned the default charter / mission-type activations, so once resolution
    moved to a single fail-closed SPEC_KITTY_PACKS_ROOT read the pre-review gate
    degraded to no_coverage (never blocking) and mission-template resolution
    raised MissionsRootNotFound. The fixtures now seed the default activations
    via the production provisioner and mirror the built-in missions leaf under
    the packs root. Test-harness only - no product behaviour changed; the
    fail-closed resolution the sibling mission introduced is intentional.

  • Commands run on a merged coordination mission no longer crash (#3086).
    Once a coordination mission was merged, later commands against it - creating a
    retrospective, resuming implementation, or checking its status - failed
    outright, and this hit every merged coordination mission. Merging now
    finalizes the mission cleanly, so it stays usable afterward.

  • decision widen no longer lets one mission's decision travel under
    another mission's identity (mission egress-refusal-consolidation-3110;
    #3111).
    Previously, ownership was resolved from whatever the decision
    ledger it was handed reported rather than from the mission you actually
    named, so a decision owned by mission A could be widened under a request
    naming mission B and have the egress consent gate treat it as consented -
    laundering A's identifying text out under B's team/token. Widening now
    refuses whenever the acting mission and the decision's recorded owner
    disagree. The governing rule: identity is a detector, never an actuator.

  • The consent-refusal gate deciding whether project-identifying text may
    leave your machine now has one definition site instead of two
    near-identical copies that could drift apart (#3110).

    saas_client/egress_consent.py and tracker/egress_consent.py are
    replaced by src/specify_cli/egress.py.

  • Listing missions on Python 3.14 no longer reports a permission error as
    "no missions found" (#3177).
    An unreadable mission directory was
    silently dropped instead of being recorded, so the CLI told you to git pull for what was actually an EACCES. It now reports the directory as
    unreadable; unaffected on the CLI's supported 3.11-3.13 interpreters.

  • Internal: a stale docstring in the saas-client/egress invocation adapters
    no longer claims re-registration only replaces a factory or resolver "when
    the qualified name matches" (#3109).
    Both always replaced
    unconditionally; only the documentation was wrong, and only the control
    flow used to reach the same assignment differed. Behavior is unchanged.

  • UnknownMissionTypeError no longer labels an activated mission type both
    "unknown" and "registered" (mission
    doctrine-consumer-surface-missions-extraction; #3183).
    When a mission
    type is activated but has no loadable profile, the error now states that
    distinctly, instead of the contradictory "Unknown mission type ''.
    Registered types: ".

  • The built-in-doctrine dead-path architectural gates no longer silently lose
    coverage when a gate file is split, and the shipped doctrine-daphne profile
    no longer carries a repo-local src/doctrine/graph.yaml reference (mission
    doctrine-consumer-surface-missions-extraction; #3036, #3182).
    The
    src/-wide gates and the doctrine-content gate are split by actual scope with
    their discriminator proofs driven from planted synthetic fixtures, and the
    relocated-tree cross-link scan now covers both shipped roots.

  • The dashboard's Charter page no longer misreports a compiled-only project
    as having no charter (mission doctrine-charter-split-unification;
    #3150).
    Before this fix, a project with a compiled
    .kittify/charter/charter.yaml but no display-only charter.md companion
    showed "no charter" in the dashboard sidebar and its API - both the artifact
    scanner and the charter API endpoint still keyed presence on the legacy
    charter.md file. Presence resolution now prefers charter.yaml (the read
    authority) and falls back to charter.md when charter.yaml has not been
    compiled yet, across the dashboard, analysis-report, and
    retrospective-policy surfaces - so a charter.md-only project that never
    ran charter sync/compile still reports a charter, and a compiled project
    still survives charter.md deletion; the prose body the Charter page
    displays still comes from charter.md when one exists. Two exceptions are
    intentionally pinned, not
    residual gaps: charter context's prose-presence gate (documented, C-003)
    and charter status's legacy charter.md-only fallback for
    pre-consolidation bundles (_status_collectors.py, pinned by
    test_status_collectors_legacy_md_shape.py, FR-006).

  • A corrupt or malformed meta.json now always fails closed with a clear
    error, never a raw crash (mission doctrine-charter-split-unification;
    #3140).
    Previously, dozens of call sites across mission-status,
    coordination, migration, and CLI commands could leak an unhandled
    ValueError when a mission's meta.json was corrupted or shaped
    unexpectedly, surfacing as an opaque traceback instead of an actionable
    error. Every one of those readers now routes through a single typed
    MissionMetaReadError (or a documented silent-empty fallback where that was
    always the contract), so a broken mission's metadata degrades predictably
    instead of crashing the command you ran.

  • Internal: doctrine-charter-tests.yml CI now actually covers the charter
    CLI command layer, and the CLI-reference parity check runs instead of
    silently skipping (#3149, #3107, #3102).
    A change under
    src/specify_cli/cli/commands/charter/** previously wouldn't trigger the
    workflow named for it; the docs-parity gate had pointed at a reference file
    that no longer existed, so it never actually ran.

  • Approving a work package after a rejection now actually sticks - no
    override flag required (mission review-verdict-write-integrity; #3044,
    #2275, #2996, #990, #2697, #2646).
    Before this fix,
    move-task --to approved on a previously-rejected WP advanced the lane but
    wrote no record of the approval - the stale rejected verdict stayed
    authoritative, so move-task --to done and spec-kitty merge kept blocking
    on it, forcing --skip-review-artifact-check (an arbiter-override flag) on
    every ordinary reject -> fix -> approve cycle. Separately, the rejection
    writer accepted any file as "feedback" without checking it wasn't itself a
    prior cycle's own review artifact (by path or by a renamed copy), so a
    mistaken re-submission (a reviewer accidentally re-pasting the same feedback
    text) could silently duplicate an old review under fresh, fabricated
    frontmatter. Neither writer ever git-committed its output under any
    topology, so even a successful write could land untracked. Now: approving a
    rejected WP through the normal path persists and commits a real
    verdict: approved review-cycle artifact with a genuine reviewer identity;
    the override flag still works for actual arbiter decisions but is no longer
    needed for an ordinary approval; a rejection's feedback source is refused
    if it's the same or a duplicate of a prior cycle's own artifact (a check
    that a deliberate one-character edit still defeats - it targets accidental
    duplication, not a determined actor); and both move-task write paths now
    commit their review-cycle artifact when auto-commit is resolved on
    (--auto-commit, or auto_commit: true/default in .kittify/config.yaml),
    surfacing a clear error if the commit itself fails rather than silently
    discarding it. Under --no-auto-commit, or with auto_commit: false
    configured, the write still lands but is not committed. A third writer,
    _persist_review_feedback, does not yet thread a commit router at all.
    agent tasks status's stale-verdict display (#2646) is confirmed to close
    as a side effect of the durable writer, with no separate code change needed.

  • A work package's current verdict now has a single authority that wins on
    disagreement - the status event log, not a review artifact's frontmatter
    (mission review-cycle-verdict-seam-rebuild-01KZ2W7W; ADR
    2026-08-03-1).
    Before this fix, "is this WP approved?" had no single
    answer: some readers (the kanban board, move-task's review-readiness
    check) parsed a review-cycle-N.md artifact's verdict: frontmatter field
    directly, so a reader could disagree with the event log about which verdict
    was current if the two ever drifted. Now the event-sourced verdict wins on
    any such disagreement; frontmatter reads remain in place as a fallback for
    the case where the event log carries no opinion (for example, legacy
    coord-topology missions that predate this seam), and no new
    frontmatter-reading path was added - the merge gate still drives its
    readiness loop off a frontmatter reader for that no-opinion case, by design.
    The review-cycle artifact remains authoritative for what the reviewer
    said
    - the reviewer's prose, affected files, reproduction command - never
    for which verdict is current once the event log has an opinion to give.

  • An arbiter's override decision now durably persists and clears the merge
    gate on its own - no separate flag or manual step required afterward
    (mission review-cycle-verdict-seam-rebuild-01KZ2W7W; ADR
    2026-08-03-1).
    Before this fix, the arbiter override writer never
    git-committed its output under any topology, so an override could be
    present in a working tree and still vanish from a fresh clone or a
    branch switch - the exact gap that made --skip-review-artifact-check
    feel unreliable even when it had "worked." Now the override is committed
    through the same seam every other verdict-relevant write uses, so it
    survives a fresh clone and the merge gate accepts it without repeating the
    override flag.

  • An arbiter override can no longer be mistaken for, or silently absorbed
    into, a genuine reviewer approval (mission
    review-cycle-verdict-seam-rebuild-01KZ2W7W; ADR 2026-08-03-1).
    Before
    this fix, nothing distinguished a knowingly-overridden standing rejection
    from an approval a reviewer actually performed - both could present the
    same way to a consumer reading only the latest verdict. Now the override is
    recorded and read back as its own first-class outcome (a stated actor and
    reason, event-sourced on the same partition as the record it annotates),
    never fabricated as an approval review nobody performed.

  • Applying a charter pack no longer leaves your project worse off than doing
    nothing (mission charter-pack-usage-journey; #3104, #3105).
    Before
    this fix, spec-kitty charter pack apply <pack> on an unconfigured project
    disabled the safe generic-agent dispatch fallback - so the next unmatched
    spec-kitty dispatch hard-failed with ROUTER_NO_MATCH - and charter context / charter status still reported the charter as "not found" because
    they gated on the display-only charter.md that apply never writes. Now:
    applying a pack without compiling keeps the warned generic-agent net
    engaged (an unmatched request still falls back, never ROUTER_NO_MATCH), and
    checking that adds no extra doctrine-catalog load to the dispatch hot path
    (#3118); apply --compile does the apply and compile in one step; after you
    compile, charter context / charter status reflect the pack's activated
    governance and keep working even if charter.md is deleted (the read
    authority is the compiled charter.yaml); and the governance resolver reports
    the activated directive set rather than silently falling back to all
    built-in directives. The documented spec-kitty analyze surface and the CLI
    are now guarded to stay in agreement (#3096). Deliberate behaviour
    change:
    the dispatch net now keys purely on whether a compiled bundle or a
    routable profile/org pack exists, not on every activated doctrine dimension -
    so a project that has activated only glossary packs, directives, or other
    non-routing doctrine (no bundle, no routable profile) now falls back to the
    generic-agent net too. This narrows the broader composite-dimension check the
    #3064 fix introduced above; it is a tested, recorded change, not a silent
    regression.

  • Contract note - charter context --json (project_charter.present). The
    read authority for the JSON charter-presence surface moved from the
    display-only charter.md to the compiled charter.yaml: a project that has a
    charter.md but never compiled now reports project_charter.present: false
    (the new charter_md_present / charter_md_path keys expose the display file
    separately, so the information is additive, not lost). The payload also now
    carries a top-level context_schema_version stamp (a versioned tracking
    contract; a full shape freeze + deprecation policy stays deferred to the
    #2519 activation-surface work) so an external consumer can detect a shape
    change instead of breaking silently. External --json consumers should read
    charter.yaml as the authority-of-record and pin context_schema_version
    (#2787).

  • A non-terminating test now fails loudly instead of hanging the CI job
    (mission verification-trust-3115; #3115, #3113).
    pytest.ini
    registered a timeout marker but set no timeout, so a test that never
    returned stalled its whole shard rather than failing - a mechanism reporting
    nothing where it should report a failure. The fast-test CI jobs now carry a
    default per-test timeout (--timeout with the signal method), and a new
    architectural gate (test_ci_fast_jobs_have_timeout.py) keeps the flag from
    being silently dropped, with two documented exemptions (the real-wheel docs
    build and the -n0 real-port orphan-sweep, where a signal mid-teardown is
    unsafe). Also hardens the terminal-render width fold, the egress-consent
    boundary, and the tests/sync leak-guard so each is provable rather than
    asserted. No product code (src/specify_cli) changed - this is a
    test-trust and CI change; Windows and local runs still get no per-test
    timeout (tracked by #3143). (#3130, #3136 referenced.)

  • Fixed: sync could deliver one project's events to another project's
    workspace, including projects that never opted in (#3030).
    A machine
    keeps one shared local event journal across every Spec Kitty checkout on
    it. Sync's consent check only asked "is sync enabled for this checkout?"

    • once yes, it delivered the entire journal, so a single opted-in
      project could ship another, unrelated project's events and metadata to the
      hosted server. Every path that reads from the journal for delivery is now
      gated per-project, consent is checked before events leave the machine
      rather than only at the checkout level, and an unresolvable consent check
      now fails closed (refuses to send) instead of failing open. If you use
      hosted sync for more than one local project, only the project(s) you
      explicitly opted in now have their data sent.
  • Generated agent guidance reads grammatically again (mission
    charter-delivery-finish-context-degod; #3082).
    The "verbatim-or-fetch"
    disclosure lines every agent receives in charter context were frequently
    ungrammatical - e.g. When you designing or reviewing significant code changes, run this command. The authored when clause is now normalized into
    the closed contract lead-in set (When you are about to ...) for every clause
    shape, and the prompt-governance contract is asserted per stanza so a
    regression can't hide behind another matching line. (A second, independent
    render surface in section_bodies.py is tracked separately as #3093.)

  • DRG document-writer blind spot closed (mission doctrine-delivery-activation;
    #3075, #2977).
    All three DRGGraph document-emit sites
    (rewrite_opposed_by, charter.synthesizer.project_drg, and the
    pack_assembler force-dedup path) now route through the canonical
    graph_document_to_dict and are registered DocumentWriter members, guarded
    by a new non-vacuous writer-discovery gate that fails on either an
    unregistered dict-literal or a .model_dump()-shaped emitter. The
    pack_assembler path was additionally crashing yaml.safe_dump on the
    Relation enum (and leaking withheld fields). The repository surfaces are
    typed via a new ArtifactRepository Protocol, removing 12
    # type: ignore[attr-defined].

  • DRGGraphSchemaError now surfaces as a structured doctrine validate
    issue instead of an uncaught traceback
    , and AssetRepository.source_path
    no longer disagrees with get for a manifest that failed validation (a base
    _post_validate success-path hook records the source path only after
    validation) (#3062).

  • The test_every_load_delivery fixture is now hermetic to an ambient
    gitignored .kittify/charter/context-state.json, eliminating a local-only
    false-red on the first_load/bootstrap assertions.

  • Read-side placement seam: mission reads route through the kind-aware seam and
    new bypasses are structurally impossible (#2922, #1878; also #2921, #2966
    part-1).
    The write side already failed loud when an artifact's coordination
    partition was gone, but ~50 modules still read mission artifacts through the
    low-level kind-blind resolvers, so a read could silently substitute the primary
    checkout for a deleted coord branch:

    • Every fail-loud-appropriate caller (72 sites, per a per-site classification
      ledger) now reads via PlacementSeam.read_dir(<kind>) with the correct
      artifact kind. Coord-partition reads - a lane-based merge's event log, the
      decision-log companion read, the doctrine synthesizer - now fail loud instead
      of treating a deleted coordination branch as healthy.
    • 16 diagnostic/audit/SaaS-facing readers (dashboard scan, dossier API,
      retrospective summary, status aggregation, and friends) stay lenient by
      design
      and are recorded as justified allow-list entries - reporting surfaces
      must not start raising.
    • A new whole-tree AST gate reds on any future direct kind-blind read in a
      non-sanctioned module, reusing the same scanner as the write-side gate. The
      allow-list is shrink-only: a staleness twin-guard reds until an entry that is
      no longer needed is deleted.
    • Behavior is unchanged for the healthy case; repair_lane_mismatch no longer
      duplicates frontmatter into the document body (#2921), and
      backfill_runtime_state's mission-id read is anchored on the PRIMARY leg.
    • Fail-loud does not mean crash on the paths that exist to recover from a
      broken coordination branch. spec-kitty implement --recover now reports a
      COORDINATION finding carrying the doctor coordination --fix remediation
      instead of tracebacking, spec-kitty merge aborts with a readable message
      before any state change rather than mid-merge, and agent workflow review
      degrades to an advisory warning instead of aborting while computing one.
    • spec-kitty verify's mission-directory probe is now CWD-invariant: invoked
      from a lane worktree it resolves the main repository's kitty-specs/ rather
      than reporting the mission as absent. Both production callers already
      anchored on the main repo, so this is observationally a no-op for them.
    • spec-kitty accept no longer rewinds finished work packages to
      claimed (#3013, #2985).
      The birth-cutover seed anchored its
      planned -> claimed carrier on a work package's earliest transition,
      which for a force-jumped or pruned WP is its terminal one. The seed
      then collided on timestamp, and because the reducer sorts by
      (at, event_id) the hash-derived seed id outranked the real ULID - folding
      a done WP back to claimed, so a second accept on an unchanged tree
      did not converge. (This corrects the mechanism recorded on #2985: it is the
      sort key, not append order.) accept is also now event-count neutral on
      missions whose runtime state is already canonical. Already-corrupted logs
      do not self-heal
      - they still need the #3003 corpus regenerate, which
      this unblocks.
    • doctor mission-state --fix no longer destroys review_result, log
      order, or dropped duplicates (#3003).
      _build_canonical_row is a closed
      allowlist that omitted review_result - a first-class StatusEvent field
      and a hard FSM guard, since every transition out of in_review is
      rejected without it. Repairing a corpus therefore converted valid history
      into events the reducer could no longer validate: 268 transitions across 44
      missions, with none failing beforehand. The loss was worst-shaped, because
      DoneEvidence.review mirrors the payload only on approval - so the
      changes_requested rows were the irrecoverable ones. The allowlist is now
      gated against StatusEvent.__dataclass_fields__ so a newly added model
      field cannot be dropped the same way. Two further fixes: rows now sort on
      at or timestamp (lifecycle and retrospective rows date themselves
      with the latter, so 839 of them were collapsing to "" and being hoisted to
      the head of an append-only log), and a dropped duplicate event_id row is
      quarantined rather than only hashed.
    • The classification ledger is now the mechanical authority for the
      stay-lenient allow-list - the gate parses it, so the doc and the gate cannot
      drift apart silently. The gate additionally resolves import aliases, and its
      coverage bounds (including the unpoliced primary_feature_dir_for_mission)
      are stated explicitly rather than implied.
  • Almost the entire test suite was being skipped on pushes to main (#2957).
    CI decides which test jobs to start by looking at which paths a change
    touched. That is a sensible optimisation for a pull request, but it was
    applied to pushes to the protected branch as well - so a merge whose diff
    happened to match none of the path filters started 10 of 50 test jobs and
    left 31,547 of 33,822 test cases (93.3%) running nowhere. This was not
    theoretical: four test files that pin frozen contracts were failing on main
    while main CI reported green, because the only jobs that would have run them
    were filtered out. Path filtering is now a pull-request optimisation only. A
    push to a protected branch starts 49 of the 50 suite jobs and leaves no test
    uncollected; pull requests are unchanged. A new check
    (tests/architectural/test_ci_collection_completeness.py) fails if any
    collected test is left with no job that would run it on a push to main. It
    has no baseline and no allowlist - the only way to satisfy it is to make a job
    actually run the test.

  • Doctrine packs could declare things that quietly had no effect (mission
    doctrine-silence-guards; ADRs 2026-07-26-1,
    2026-07-26-2,
    2026-07-26-3).

    The doctrine layer's characteristic failure was silence rather than error: a
    declaration was accepted, validated, reported as loaded, and then dropped. Every
    case below now either works or fails with a message naming what was wrong.

    • Five of the sixteen artifact kinds were missing from the doctrine-graph
      extractor
      - anti_pattern, asset, glossary, glossary_pack and
      glossary_scope were dropped whenever the graph was rebuilt. All sixteen are
      handled, and the mapping is now checked for completeness instead of being
      maintained by hand.
    • Two of the twelve org-pack directory names crashed the merge outright
      with a bare KeyError (mission_types/, glossary_packs/). One consequence
      worth naming: the shipped test fixture called augment-all-kinds-pack could
      not be merged at all, so the "all kinds" fixture never exercised all kinds.
    • Edges derived automatically from an org pack's agent-profile fields never
      reached the graph.
      The producer emitted fully-qualified <kind>:<id>
      endpoints; the code that consumed them looked up bare ids. Nothing matched,
      so 100% of that path's output was discarded, silently.
    • An edge from a built-in artifact to a pack artifact was dropped with
      neither a warning nor an error. It now resolves, or reports a typed error
      naming the endpoint it could not resolve.
    • A bare name that referred to a styleguide was turned into a made-up
      directive:<name> node
      rather than the styleguide:<name> the author
      meant.
    • Unrecognised keys in agent profiles and in graph nodes and edges were
      silently ignored
      , so a typo or a retired key left an artifact that read
      one way and behaved another (see Breaking Changes).
    • Activating the doctrine-daphne agent profile pulled in 76 related
      artifacts but not the one procedure the profile itself says it runs.
      The
      edge to that procedure used a relation type (applies) that nothing
      traverses, and it was the procedure's only inbound edge, so the profile's own
      operating procedure was unreachable. The edge is retyped to requires, and
      authoring an applies edge into the shipped tree is now rejected.
    • Error messages sent operators to files that do not exist - an inline
      reference rejection pointed at src/doctrine/graph.yaml, split into per-kind
      fragments long ago, and two operator-facing skill documents told authors to
      read a src/doctrine/<kind>/shipped/ layer that has never existed on disk.
      Both are corrected and both are now guarded.
    • The generated schema for the docs structural lint had widened to "any
      object"
      , so a malformed lint configuration validated cleanly and the lint
      ran on settings nobody had checked. The generator now emits the real
      ten-key contract.
    • The documented example for declaring agent-profile lineage used a syntax
      that exists nowhere in the vocabulary
      (urn:profile:...). Anyone who copied
      it got a declaration that merged without complaint and produced no edge. The
      example is corrected in AGENTS.md / CLAUDE.md and in
      the org-pack authoring guide, and
      the accepted forms are now enforced at merge time.
  • spec-kitty agent tasks mark-status could not find subtask ids written the
    way the shipped template tells you to write them (#2962).
    The command
    resolved a subtask id by matching a row shape in tasks.md - a checkbox, a
    pipe-table row, or an inline Subtasks: T001, T002 list. The shipped
    software-dev template instructs authors that subtask rows are reference
    rows and explicitly not checkboxes, so a tasks.md that follows the
    template matched none of those shapes and every id came back NOT_FOUND.
    That blocked the review workflow on every work package of every mission using
    that template. The command now falls back to the authored subtasks: roster
    in work-package frontmatter - the same list the lane-transition guard already
    treats as canonical, so the two surfaces agree on what a work package's
    subtasks are. The fallback runs last, so every row shape that worked before
    resolves exactly as before.

  • Placement-port residuals: partition routing is now enforced by the port, not
    by caller discipline (#2923, #2924, #2926, #2932; epic #2931).
    Closes the
    residuals deferred from the coord-write-placement-closure merge (PR #2920):

    • The sole status_phase writer (_flip_phase) resolves its write target
      through the placement port and fails closed with a typed
      PlacementMismatchError (writing nothing) if the resolved PRIMARY home
      disagrees with the caller's directory - coincidental correctness becomes an
      enforced invariant.
    • The two-target legacy cutover reads tasks/ frontmatter from the PRIMARY leg
      while the status event write stays on COORD.
    • Three hand-rolled "resolve write target, else degrade" copies collapse into
      one resolve_write_target_or_degrade helper (each caller keeps its own
      fail-open / fail-closed policy), and the retrospective trace loader degrades
      to [] on a deleted coordination branch. A latent bug is fixed along the way:
      a caller-supplied coordination branch that was silently discarded in the
      pre-meta.json bootstrap window is now honored.
    • The write-side placement scan no longer blanket-exempts the migration/
      subtree, restoring whole-tree precision. The remaining deferred gate/contract
      reds were already green on the current base and are verified in-mission.
  • The merge review-readiness check no longer writes a stray status.json
    during a merge (#2934).
    The check that looks for a rejected review before
    merge was reducing mission status through the writing materializer, so it
    left a status.json snapshot on disk as a side effect. On a mission with no
    status events yet, that snapshot had no matching status.events.jsonl (the
    append-only event log that is the real source of truth), and the merge could
    commit the orphaned snapshot on its own - the invalid "snapshot without its
    event log" state the status doctor flags. The check now reads status without
    writing anything; a gate reads, it does not persist. Includes the regression
    test and a de-mocked planning-only merge test that exposed the shape.

  • Running Spec Kitty non-interactively (agents, CI, piped input) no longer
    hangs waiting for a prompt that will never be answered (#2876; extended in
    #2912; landed by #2910).
    Under SPEC_KITTY_NON_INTERACTIVE=1, spec-kitty plan and
    specify reached their discovery interviews and called a blocking prompt with
    no non-interactive gate anywhere in the call graph, so an open-but-silent
    stdin pipe made them wait forever. The interviews now honor the contract: they
    still open every Decision Moment and write the decision index, but take
    defaults and record each question as deferred instead of prompting - no prompt
    is ever emitted non-interactively. A single authority, core/env.is_interactive()
    (precedence SPEC_KITTY_FORCE_INTERACTIVE > SPEC_KITTY_NON_INTERACTIVE > a
    real TTY), now backs that decision, and #2912 routed the four other divergent
    prompt-gates - init, the merge hollow-review confirm, the intake
    candidate picker, and doctor - through it too, so every prompt obeys the
    same rule (with doctor keeping its CI-environment veto layered on top). Also
    fixes the garbled interview hint menu where Rich was eating [enter]/[text]/[d]
    as style tags.

  • Merging an accepted mission no longer discards the acceptance and
    issue-matrix evidence recorded when it was accepted (#2804; scorer hardening
    #2912; landed by #2910).
    acceptance-matrix.json and issue-matrix.md are filled on the
    target at accept time while the mission branch keeps empty scaffolds; the
    squash integration ran git merge --squash -X theirs, so the empty scaffold
    won the add/add conflict and the merged history kept blank gate artifacts -
    losing the audit trail exactly when a reviewer needs it. Two new custom merge
    drivers (merge-driver-acceptance-matrix / merge-driver-issue-matrix) now
    keep whichever side actually carries evidence - scored, not fixed-side, so it
    is correct whether the fill happened on the target or in a lane, with ties
    going to the target where accept ran. The scorers were then hardened (#2912) to
    read the scaffold marker from the field the scaffolder actually writes, and to
    resolve verdict/title columns by canonical header rather than fixed position,
    so a reordered or minimal matrix scores correctly. Seeded into .gitattributes,
    the init seed, and an upgrade migration for already-initialized repos -
    following the same pattern as the #2709 meta/traces drivers.

  • Lifecycle gates no longer emit a confident-but-wrong verdict when run from
    the wrong worktree, and a misclassified lock write no longer blocks a claim or
    merge (#1834, #2885, #2882, #2795).
    Every lifecycle gate now receives an
    explicit execution context - which tree, which ref, which phase - and refuses,
    recording a clear cannot-evaluate diagnostic instead of a pass/fail, when that
    context is missing or inconsistent, rather than silently inheriting the
    caller's working directory and judging a fact about one worktree against
    another. Separately, spec-kitty implement/merge no longer wrongly report a
    work package as blocked because Spec Kitty mistook its own planning-partition
    lock write for a dirty tree (the #2795 claim-blocker, now topology-agnostic).
    Internally, eleven scattered gate-exemption predicates are retired onto one
    owner guarded by an exemption-registry ratchet, and the accept gate now
    asserts ref-agreement before it judges the acceptance matrix.

  • The issue-matrix.md and acceptance-matrix.json missing-file errors now name the regenerate command.
    Both files are already scaffolded automatically during spec-kitty tasks (finalize-tasks); the failure messages an operator actually sees when one is missing (at move-task --to approved and at spec-kitty accept) previously said nothing about that, so a missing file read as "no tooling exists for this" rather than "re-run finalize-tasks." Both messages now name spec-kitty agent mission finalize-tasks --mission <slug>, and the issue-matrix message also points at its schema/worked-example doc (src/specify_cli/cli/commands/review/ERROR_CODES.md). The spec-kitty-mission-review skill's Gate 4 section gained the same pointer.

  • Windows backslash in git tree-path misreported committed specs as uncommitted (#2836).
    _git_commit_check_context built the git tree path with str(Path(...)), which
    renders using the OS-native separator - a backslash (\) on Windows. Git's
    HEAD:<path> object syntax and ls-files pathspec require forward slashes, so
    both subprocess checks failed and is_committed() reported genuinely-committed
    spec files as uncommitted, blocking the setup-plan workflow (e.g.
    /spec-kitty.plan refusing to proceed). Both return sites now resolve through a
    single worktree-aware kernel seam (kernel.paths.repo_tree_path) that renders
    forward slashes on every host via PurePosixPath. Invisible on POSIX
    (macOS/Linux/CI) where os.sep is already /.

  • Posix path-separator normalization consolidated into one kernel seam.
    The str(x).replace("\\", "/") idiom behind #2836 was scattered across ~17
    sites in charter, mission_runtime, and specify_cli (review, upgrade +
    migrations, merge, git, skills, status, paths, bulk_edit) - each an independent
    chance to reintroduce the Windows backslash defect. All now route through the
    behaviour-agnostic kernel.paths.to_posix(path: Path | str) seam (kernel being
    the zero-dependency root every layer can import downward), leaving the seam
    definition as the only replace("\\", "/") in src/.

  • merge's hollow-review warning no longer flags a WP with a genuinely independent reviewer.
    force_count >= 2 alone can't distinguish "the reviewer used --force to bypass an unrelated gate false-positive" from "no independent review happened" - both increment the same counter. When the event log positively confirms a different actor logged the approving transition than the one that most recently implemented the WP, the warning no longer fires; absence of that evidence still warns as before (fail-safe default, never suppressed on uncertain data).

  • Honest force-provenance on evidence-gated backward edges (#2684, #2736, #2810).
    Persisted StatusEvent.force is now truthful - falsy on the evidence-gated
    review-rejection edges (build_transition_plan asks the FSM instead of
    auto-promoting force) and truthful on genuine guard-bypasses (leaving a
    terminal done). Fixes the false-force stamp found during #2736 / PR #2810.

  • Off-axis emit sites resolve their write target from stored topology, never Path.cwd() (#2647).
    Runtime-state events can no longer be written against the wrong feature
    directory when the CLI runs from a foreign working directory.

  • Rollback to planned releases the prior claim (#2512).
    A rolled-back work package no longer retains a stale shell_pid/agent in the
    reduced snapshot, so the next resume/re-claim is not blocked by a dead claim.

  • Runtime-state backfill verify is now genuinely fail-closed on value tampering (#2684).
    The parity check could silently pass a tampered value when a correcting seed annotation
    happened to sort last in a same-timestamp reduce fold (a content-hash-ULID coin-flip); it
    now flags any two annotations assigning different values to one slot as corruption,
    regardless of fold order. (The backfill CLI wiring itself remains deferred to #2816.)
    The runtime-state eviction also hardens the dual-write end-state: bypass frontmatter reads
    routed onto the gated snapshot seam, the phase-1 authority predicate split from the legacy
    lane-mirror, and the #2093 authority invariant asserted by imported-symbol identity.

  • Fresh-project charter synthesize no longer crashes after the charter.yaml inversion (#2800, #2773).
    Post-#2773, charter generate stopped writing charter.md, but the fresh-project
    synthesize intercept still gated on charter.md being present - so on a real fresh
    project the intercept never fired and charter synthesize fell through to the production
    adapter, exiting 1 with 'str' object has no attribute 'get'. The intercept now gates on
    the authoritative charter.yaml (what generate writes), restoring the fresh-project seed
    path and aligning with the inversion invariant that charter.md is display-only, never a
    resolving signal.

  • Pre-review regression gate no longer silently skipped by a leaked sync toggle in the parallel suite (#2800, #2794).
    The gate reuses the sync layer's process-wide SPEC_KITTY_SYNC_MINIMAL_IMPORT /
    SPEC_KITTY_SYNC_DISABLE opt-outs; in a whole-tree -n auto run one could leak into an
    xdist worker and skip the gate, reding the gate-observability tests. An autouse fixture now
    unsets both toggles before every agent test, making them worker- and order-independent. No
    production behaviour change; the deliberate gate↔sync coupling is tracked for a dedicated
    opt-out in #2801.

  • CI test-suite remediation: stale fixtures from two recent merges brought back to green (#2800).
    The charter.yaml consolidation (#2773) and the coord-rollback transactionality mission
    (#2786) merged with un-flagged test-fixture debt a CI path-filter hid until #2800 surfaced
    it: charter selection/bundle/synthesize/freshness fixtures still seeding the retired
    governance.yaml/metadata.yaml triad; arch-gate remainders (golden-count, tmp-literal,
    marker drift); a #2709 regression fixture missing the canonical force event field; and 89
    mechanical mypy --strict errors in the agent test package. Fixtures re-pointed onto
    charter.yaml, gates re-based, and tests whose premise encoded a retired flow removed
    outright per the standing-order §4 remediation framework (now amended to codify
    superseded-design -> remove). The legacy-topology bootstrap CWD gap a strict-xfail guards
    is now tracked in #2802.

  • Sync batch-400 poison isolation - one invalid event no longer strands its whole batch (#2736; closes #2755).
    A whole-batch HTTP 400 with no per-event details made the CLI fan the batch-level error onto every
    event as rejected (non-terminal), so the culprit re-poisoned every subsequent drain and the innocents
    never delivered - the upstream cause of the wp_status_event_without_create projection anomaly. The
    receiver now recursively bisects a poison batch (delivery/receivers.py): split -> re-POST both halves
    -> recurse to singletons, isolating the culprit (kept rejected/retryable, never force-parked) and
    delivering every innocent, with create-before-status ordering preserved by sequential left-before-right
    recursion (and a split-point clamp so a same-wp_id create+status pair still terminates). The live
    offline-queue path (sync/batch.py _parse_error_response no-details branch) now dispositions a
    batch-level 400 as failed_transient (no retry_count bump) instead of poisoning innocents. A single
    shared core/batch_partition.py primitive (split_in_half + create_aware_midpoint) de-duplicates the
    batch-splitting math and closes #2755 by retrofitting the 413 byte-shrink onto it, guarded by a
    behavioral + AST single-authority test. CLI-side only, no server-contract change; the SaaS
    transition-matrix and reducer alignment are tracked cross-repo (spec-kitty-saas#509 / #510).

  • Mission squash merge no longer clobbers target-newer acceptance provenance or traces (#2709).
    The supported squash merge ran git merge --squash -X theirs on every conflicting file,
    silently reverting target-newer meta.json acceptance/VCS fields (and traces/*.md
    sections) to the older mission-branch copies, and the coord->target projection
    blind-overwrote the durable event log and status.json. Merges now reconcile per
    artifact class
    : planning artifacts stay mission-authoritative (-X theirs intent from
    #1732 preserved), while meta.json acceptance/VCS keys field-merge target-authoritative
    with acceptance_history unioned and traces/*.md do a line-level union - via new
    spec-kitty merge-driver-meta / merge-driver-traces git drivers whose activation is
    scoped to the ephemeral squash merge (they no longer leak into a later auto_rebase). The
    projection unions the event log via merge_event_payloads and rematerializes status.json
    from the reduced log instead of a blind copy. Seeded by migration m_3_2_6. Ships with a
    red-first reproduction and two class-closing architectural lints (no-blind-copy over the
    projection path + driver-registry completeness sourced from the canonical artifact-kind
    registry).

  • Merge rollback and --resume stay coherent after a failed target advance (#2711).
    When target advancement failed after approved -> done events were committed to the
    coordination branch, rollback reverted only working-tree bytes - leaving a committed done
    opposed to a reverted working approved - and spec-kitty merge --resume re-emitted a
    fresh done (non-idempotent). Rollback now reverts the coordination done commit
    (coord-worktree git revert, sourced from the canonical write-target ref; never a raw
    update-ref), and --resume derives progress from the durable committed event log with
    MergeState.completed_wps demoted to an advisory hint - so committed and working status
    stay coherent and resume is event_id-stable. INV-5 (#1827) merge-phase ordering is
    preserved. Ships with a red-first reproduction and a resume non-reemission property guard.
    Tracked follow-ups: #2786 (write a durable reconcile marker when the rollback revert
    itself fails) and #2794 (a SPEC_KITTY_SYNC_MINIMAL_IMPORT test-isolation leak that
    skips the pre-review gate under the parallel CI suite).

  • A failed coordination-done revert during rollback no longer silently re-opens the
    #2711 split-brain (#2786), and the coordination write-set rolls back transactionally
    (#2367 Mechanism B).
    When the rollback's coord-done git revert itself failed (#2786),
    or when a merge aborted mid-way through _record_merged_wps_done_for_merge before any revert
    ran (#2367-B), rollback restored only working-tree bytes and left a committed done opposed to
    a reverted working approved - a silently-stranded split-brain. Rollback now marks-not-raises:
    a durable MergeState.pending_coord_reconcile marker records the stranded WP(s) - derived from
    the committed coordination ref (the reliable authority; a working-tree diff is empty at the
    revert-failure point) over this merge's own pre-target done write-set, so a legitimately
    pre-existing-done WP is never re-stranded. spec-kitty merge --resume heals via a strand-gated,
    idempotent git revert (byte-stable on re-run), and spec-kitty doctor coordination detects the
    strand (re-verifying incoherence from the committed ref, not marker-presence) with a --fix. A
    behavioral class-closing guard reds if any of the seven _restore_final_bookkeeping_snapshots
    rollback sites (incl. the previously-unenumerated coord-reachable site) strands without marking.
    INV-5 (#1827) ordering preserved; happy-path merge byte-identical. Ships red-first repros for both
    mechanisms. Deferred with follow-ups: #2797 (unify the two git revert transport legs into
    one shared helper). (#2795, #2367 Mechanism A - claim-time VCS-lock resync, was deferred here
    and has since landed in #2906, above.)

  • --json output is now plain regardless of terminal colour; CLI tests are colour-deterministic (#2632).
    Under a colour-forcing harness (e.g. FORCE_COLOR=3) Rich syntax-highlighted --json
    output - splicing ANSI escapes into the payload so json.loads and | jq choked - and
    split literal substrings in styled human output, red-ing ~81 pre-existing tests. All CLI
    output now routes through a single canonical CliConsole seam
    (specify_cli.cli.console) whose machine-output methods (emit_json/print_json) are
    plain by construction, so --json is safe under any colour environment. Test determinism
    comes from toggling colour on that one shared object (set_plain), never from mutating
    os.environ. The whole CLI layer (~77 ad-hoc Console() constructions) was moved onto the
    seam - no shim - and an architectural guard forbids a raw Console() under
    src/specify_cli/cli/. Deferred non-CLI consoles (including an active retrospective/cli.py
    --json corruption vector) are tracked in #2634. ADR: 2026-07-14-1-canonical-cli-console-seam.

  • Implement-loop friction quick-wins II (#2570, #2493, #2555, #2566, #2589, #2533, #2580).
    Eight fixes that make lifecycle guards no-op-stable against their own runtime writes and the
    pre-review gate return real verdicts, each preserving the guard's true-positive:
    the lane allocator ignores the shell_pid/base_* WP frontmatter it just wrote (batch lane
    allocation needs no inter-commit); the analysis-report freshness check ignores pipe-table
    [D]/[P] status-marker churn from mark-status; the pre-review gate resolves pytest via
    uv run (a real verdict instead of a spurious --force on a green tree) and serializes
    concurrent runs behind a machine-wide lock whose acquire-timeout is decoupled from the run
    timeout; the agent_profiles manifest stores output_path repo-relative so spec-kitty upgrade is cross-machine deterministic; the issue-matrix approval error names the offending
    schema-drift column instead of a misleading "Missing rows"; bulk-edit inference no longer
    trips on ordinary refactor verbs while genuine bulk edits still trip; setup-plan's first
    scaffold write returns success + scaffold_only (mirroring the specify twin) instead of
    reading as blocked; move-task routes coord-lane planning-artifact staging through the
    commit-router authority path (no lane kitty-specs/ commit, STATUS_STATE placement
    byte-unchanged) and closes the 4th divergent shell_pid writer; and a solo PR-bound coord
    mission routes its empty-coord status surface cleanly to primary instead of tripping a
    split-brain warning. Successor to the loop-friction fast-follow; follow-ups filed for the
    topology-derivation revisit (#2602), de-god debt (#2603, #2604), the implement --json
    output contract (#2605), and worktree-aware doctor tool-surfaces --fix (#2613).

  • Implement-loop friction quick-wins (#2581, #2573, #2549, #2577). Four independent
    loop-friction fixes: mission create / spec-kitty specify now derive the create-time
    topology from context - a non-primary feature branch without --pr-bound defaults to
    single_branch instead of minting a coordination branch the operator must manually flatten
    (primary branch, --pr-bound, and explicit --topology still default to coord);
    move-task --to for_review gains a --skip-pre-review-gate flag, honors the
    SPEC_KITTY_SYNC_DISABLE / SPEC_KITTY_SYNC_MINIMAL_IMPORT disable env, and surfaces a
    progress notice so the scoped-test gate no longer reads as a hang (the full async redesign
    stays deferred on #2573); finalize-tasks --json reports per-branch commit hashes
    (commit_hashes) for the two-branch coord-topology commit set instead of a single
    commit_hash that omitted the coordination-branch commit (#2549 facet B; facet A deferred);
    and charter synthesize no longer fails closed on an empty/first-run config demanding a
    companion tactic for every built-in directive - first-run parity restored (#2526 regression).

  • Coord-shadows follow-ups: canonical-source consolidation + gate/liveness robustness
    (#2574, #2575, #2576, #2567, #2568).
    The triplicated subtask-gate-dir resolver is unified
    onto one resolve_subtasks_gate_dir seam with the strong git-ancestry fallback (the weak
    status_transition site no longer reads a stale coordination husk); core/process_liveness
    guards against PID reuse via a persisted creation-time baseline co-written at every claim site
    (a recycled PID no longer reads as a live claim); the rollback-uncheck write routes through the
    house path-guard and surfaces failures instead of silently leaving checked rows; the acceptance
    gate's stray checkbox parser is migrated onto the canonical core/subtask_rows; and the review
    lock folds onto the canonical is_process_alive (last stray os.kill liveness probe removed).

  • Coord-shadows-primary read/gate arm closed (#2502, #2504, #2510, #2512, #2513, #1231).
    The for_review subtask gate no longer fails open: _infer_subtasks_complete reads the
    PRIMARY planning surface at every emit-layer caller and blocks on unchecked rows on both the
    native agent status and orchestrator-api paths (the redundant #2511 per-door pre-derivation
    is retired). A single canonical core/subtask_rows._walk_wp_section now defines a WP subtask
    row for the guard, the dashboard count, and rollback-uncheck (the writer no longer re-enters a
    re-appearing ## WPnn heading). A recovered coordination-topology lane re-registers its
    sparse-checkout and leaks no status.events.jsonl/status.json. Process liveness is promoted
    to core/process_liveness.is_process_alive (with a sync/daemon re-export alias) so the
    stale-WP indicator suppresses false-stale for a live claim. Freshness-hash checkbox-insensitivity
    (#1764) is pinned by a regression guard; #1862 is verified-already-fixed.

  • Arch-suite warnings remediated at root (#2553, #2554). Report-only diagnostics
    (migration patch-skips, duplicate-gate selection, ratchet shrinkage, legacy-contract
    backfill) are routed off the warnings channel via record_property (signal
    preserved, no blanket filterwarnings=ignore); a Toolguide model-vs-schema drift is
    fixed so terminology-guard.toolguide.yaml validates (clearing the base.py skip
    warning); and three agent profiles now cite bdd-scenario-lifecycle as a procedure
    rather than a tactic (clearing CharterCatalogMissWarning).

  • Coord-authority safe_commit misfired on a coordination-husk identity read
    (#2508).
    Identity-meta was read off the coordination husk rather than the primary
    checkout, so safe_commit could target the wrong tree; the read now anchors on the
    primary checkout (_load_coord_branch_meta / _commit_workflow_change). Pinned
    red-first by a dedicated regression that fails against the pre-fix code.

  • orchestrator-api transition no longer lets WPs into for_review with
    unchecked subtasks (#2510).
    The command trusted a caller-asserted
    --subtasks-complete; left unset, emit-time inference read tasks.md off
    the STATUS feature dir - the coordination worktree husk mid-mission, where
    the PRIMARY-partition tasks.md never exists - and failed open,
    silently bypassing the subtask guard native move-task enforces (field
    evidence: four WPs reached done with 0/16 rows ticked, force=false).
    Mirroring the command's own commit-gate precedent, the API now derives the
    value server-side from the PRIMARY planning surface when the caller doesn't
    assert it; explicit assertions keep working and --force keeps its bypass.
    Fifth member of the coord-shadows-primary class
    (#2331/#2430/#2502/#2508).

  • Dashboard artifact viewers no longer render empty for in-flight
    coordination missions (#2502).
    #2431 re-anchored the mission list and
    kanban to read planning artifacts primary-first, but the viewer endpoint
    family (/api/research, contracts/checklists listings, the spec/plan file
    server) still resolved the feature dir through the coord-first resolver -
    landing on the status-only coord husk, so clicking spec/plan/research on a
    running mission showed nothing while the board showed the work. A new
    resolve_feature_planning_dir() composes the coord-first resolver with the
    primary-first planning re-anchor; all viewer endpoints use it, and
    handle_kanban's split reads are made explicit (legacy-format check =
    planning surface; weighted-progress read stays on the coord-first status
    surface). Finished missions were unaffected (their coord worktrees are
    gone), which is why the gap only showed mid-run - the same "broken precisely
    while running" shape as #2430.

  • Agent context payloads no longer lose their Profile-Cited Directives
    and Tactics anchors when the token budget trims a section (mission
    charter-sole-door-bypass-closure).
    When the token-budget algorithm
    substituted a section for a shorter fetch-command stanza, it deleted the
    section's own header line along with its body - so a governance payload
    that ran over budget could silently lose the
    Profile-Cited Directives (<profile>): and Tactics: anchors an agent
    reading the payload needs to find the section it wants. The budget
    algorithm now keeps each populated kind's header intact and swaps out only
    its body.

  • spec-kitty agent tasks status no longer silently degrades the board on
    a malformed .kittify/config.yaml (mission
    charter-sole-door-bypass-closure).
    Previously, a broken activation
    config (for example, activated_agent_profiles set to something other
    than a list) was swallowed by a blanket exception handler, and the
    command rendered a status board with no human-in-charge marker and no
    indication anything was wrong. It now surfaces the underlying
    CharterPackConfigError as a structured error instead of degrading
    silently - you'll notice this as a new, explicit failure where the
    command previously ran quietly with missing markers.

  • Bump cryptography floor to >=50.0.0, fixing CVE-2026-69247 (#3181).
    cryptography is the dependency behind AES-256-GCM + scrypt KDF encrypted
    auth session storage; the previously locked 49.0.0 was affected. There is
    no user-facing behavior change - encrypted session storage keeps working
    exactly as before, just on a patched dependency version. Run
    spec-kitty auth status after upgrading if you want to confirm your stored
    session is still recognized; no re-login is required.

♻️ Changed

  • The test suite is leaner and its CI ownership is explicit (mission
    assertive-test-suite-sanitation).
    Removed permanently inert tests,
    dominated duplicates, historical shape/count pins, stale scaffolds, and
    non-causal structural scanners while preserving reviewed live-path and
    planted-fault guards. The shared pytest environment now publishes a validated
    staged build with lease heartbeats, eliminating the #3283 sibling timeout
    cascade. Regression remains a generic blocking marker route with honest
    empty-set handling; quarantine remains an empty Tier-3 visibility route whose
    owner manifest is checked against repository-wide marker discovery. The
    original doctor restart-daemon NFR-002 ≤10-second shared-runner wall-clock
    claim is explicitly retired; its controlled Linux lane now enforces
    distinct-PID restart and healthy-control-plane behavior under bounded
    operation timeouts. Hosted-macOS performance evidence is deferred to a
    controlled developer/canary harness rather than shared-runner wall clock.
    The integrated census records 36,013 nodes / 2,667 Python test files versus
    the frozen 37,444 / 2,731 baseline, with every frozen candidate terminally
    owned.

  • The doctrine module now has a curated, enforced public API surface, so
    runtime code imports it through stable doors instead of reaching into its
    internals (mission doctrine-public-api-surface; #3179, closes the
    runtime→doctrine half of #2986).
    A new doctrine.api manifest names the
    symbols the future spec-kitty-doctrine wheel will export, the charter.*
    facades re-export them by object identity, and ~34 specify_cli modules move
    off direct doctrine.* imports onto those facades. A lazy-import ratchet plus a
    source-side laundering guard keep new reach-through from creeping back in. This
    is an internal architecture boundary only — no CLI command, output, or runtime
    behavior changes — and it unblocks the #3101 doctrine wheel cutover.

  • The contributor planning surface (docs/plans/) gains durable, version-spanning
    domain plans and a curated index (#3324).
    Two domain throughlines — SaaS & hosted sync,
    and doctrine & charter — now hold the standing strategy and invariants for their
    surface across releases, distinct from the release-scoped 3.2.x plans that retire
    once distilled. The plans index is reorganized into domain throughlines, portfolio &
    milestone planning, and a single working-collections list; loose top-level notes are
    filed into their subject subdirectories; and subdirectory landing files are
    standardized on index.md. This is an internal contributor-facing change — the CLI
    and templates are unaffected.

  • The beginner guides now open with illustrated Mission Kitty splashes, and the
    Spec-Driven Development page carries a real diagram instead of ASCII art
    (#3276).
    Getting Started, Understanding Missions, Your First Mission, and
    When to use Spec Kitty modes each gain a hero illustration; When to use modes
    adds a four-path comparison board; and the SDD workflow — previously a plain
    ASCII box diagram — is now a styled SVG flowchart that renders cleanly in light
    and dark mode. The guide prose also switches to native DocFX alerts
    (> [!NOTE] / > [!TIP]) so callouts stand out on the published site. Where a
    stylized splash shows a workflow-like board, a one-line caption points to the
    authoritative steps, modes, or mission types on the same page, so the artwork
    reads as illustration and the page text stays the source of truth. The written
    content and structure are unchanged; the pages are just easier to scan.

  • Spec Kitty's documentation now lives under one predictable docs/ root with
    canonical sections, so you can find a page by where it logically belongs
    (mission common-docs-convergence; #2215, #2887, #3273).
    The scattered
    root folders (research/, examples/, glossary/, media/) and the duplicate
    reference/ vs api/ split are gone. How-tos are now organised by audience —
    user how-tos under guides/, contributor how-tos under development/ — each
    subdivided by concern behind a single landing page, so you reach what you need
    in fewer clicks. Old documentation URLs redirect to their new homes, so existing
    links and bookmarks keep working. Every page now declares its intended reader
    and Divio type, and the architecture docs present one living design with prior
    versions filed as ADRs.

  • Dead queue-backed event-drain code removed from sync/batch.py, closing a
    latent consent-bypass path (mission chain-b-consent-bypass-3167; #3167,
    #3190, #3187, #3220).
    batch_sync and sync_all_queued_events had no
    production caller - the journal dispatcher remains the only event drain Spec
    Kitty actually uses - but if either were ever reconnected, they could send a
    project's events without checking that project's consent first. Both are
    deleted, along with 31 supporting symbols and 91 test nodes that existed only
    to serve them, and the now-inert E15 egress-allowlist entry is removed. A
    tests/sync autouse fixture that had been silently granting "consent" to
    almost every test in the folder - which is why this gate went uncovered for
    as long as it did - no longer patches the removed seam. No behaviour changes
    for existing projects.

  • Doctrine-pack resolution and charter activation now run through a single
    authority (mission resolution-activation-foundation; #2657, #3210).

    One kernel primitive resolves the packs root and honours
    SPEC_KITTY_PACKS_ROOT for every reader - the duplicate runtime home.py
    resolver is collapsed onto it - so resolution can no longer diverge between
    call sites. Fresh spec-kitty init provisions the default charter by
    deterministic copy instead of re-scanning, so a freshly initialised project
    and a migrated one start from an identical charter. The provisioned charter
    is the sole authority for what is activated, and an unresolved mission-type
    now fails closed at the create/use boundary instead of resolving implicitly.
    Existing projects need no action.

  • The event log is now the single authority for a work package's review
    verdict; the review-cycle .md no longer carries one (mission
    verdict-seam-write-unification-01KZ9Q35).
    Every verdict reader and merge/
    review gate resolves the event-sourced review_result slot in
    status.events.jsonl; the review-cycle-N.md frontmatter verdict field is
    retired, and durability moved to the event log (the .md write is now
    best-effort). To keep existing projects safe across this reader collapse, a
    new auto-discovered upgrade migration (verdict_provenance_backfill) runs on
    spec-kitty upgrade and backfills each mission's stranded terminal .md
    verdict into the event log (FR-012/SC-008); it is idempotent (keyed on a
    deterministic ULID) so re-running upgrade is safe. spec-kitty accept
    carries a non-blocking diagnostic that names any still-stranded WP and points
    at upgrade. Runbook:
    docs/migrations/verdict-provenance-backfill.md.
    No pyproject.toml/__init__.py version bump accompanies this entry: the
    mission changed only the status/__init__.py facade, not the CLI entry
    point, so a CHANGELOG entry without a version bump is correct here.

  • Built-in mission data now ships under packs/built-in/missions/, alongside
    every other built-in doctrine kind (mission
    doctrine-consumer-surface-missions-extraction; #3091).
    The mission-type
    profiles, step prompts, step contracts, and per-type content that previously
    lived embedded in the doctrine Python package at src/doctrine/missions/
    now resolve from the shippable packs/built-in/missions/ location; the 11
    .py logic modules stay in place as an ordinary package. Every reader -
    across kernel, doctrine, charter, specify_cli, and the upgrade migrations -
    was repointed in one atomic change, and mission-asset resolution now targets
    the pack location explicitly so it can no longer silently fall back to the
    (now data-less) package directory in a built/wheel layout. No user action is
    required: spec-kitty init and mission resolution behave exactly as before,
    and the regenerated DRG graph fragments are byte-identical.

  • Mission-asset resolution is now a single doctrine-agnostic kernel primitive
    (mission doctrine-consumer-surface-missions-extraction; #3091).
    The
    kernel's built-in-content lookup no longer hard-codes the doctrine package
    name or mission-type vocabulary; doctrine/pack_paths.py and
    MissionTemplateRepository.default_missions_root() converge onto that one
    primitive, guarded by a kernel-scoped architectural test that fails on any
    reintroduced kernel -> doctrine edge.

  • Retrospective policy set in charter.yaml now takes precedence over
    charter.md frontmatter (mission doctrine-charter-split-unification).

    If both files configure retrospective behaviour and they disagree, the
    compiled charter.yaml value now wins - matching how every other
    charter-governed decision already resolves. Projects that configure
    retrospective policy only via charter.md frontmatter (no charter.yaml
    retrospective block) are unaffected and keep resolving from the markdown as
    before.

  • Internal: doctrine/charter changes now get a dedicated, path-filtered CI
    workflow (mission charter-pack-usage-journey; #3102).
    A PR that touches
    only src/doctrine/** / src/charter/** (or touches neither) now runs the
    DRG-freshness, charter-context-resolution, and architectural/adversarial
    checks for that layer in an isolated workflow instead of paying for the full
    suite - faster, more targeted feedback for doctrine/charter work, no change
    for unrelated PRs. The charter pack apply -> charter generate two-step and
    the empty-charter dispatch-safety-net behaviour are also now documented in
    the charter journey guides (#3107).

  • The charter/context.py god-module was decomposed 3243 -> 570 lines (mission
    charter-delivery-finish-context-degod; #2532).
    The charter-context
    engine - previously a single 3243-line module - is now a thin orchestration
    surface delegating to 16 focused sibling modules under src/charter/
    (catalog diagnosis, token budget, artifact bodies, selection rendering,
    activation, bootstrap text, compact governance, JSON builder, profile
    resolution, doctrine-service builder, ...), and a long-standing internal import
    cycle is dissolved. This is a pure maintainability change: a byte-for-byte
    parity fixture over the three public entry points (build_charter_context /
    _include / _json) is green before and after, and a completion gate asserts
    each seam is really used. No behaviour or output changes.

  • Glossary-term casing gate is prose-only; the baseline ratchet is retired (#2830, #2823).
    All pre-existing non-canonical glossary-term casing across docs/ prose was paid down and the
    frozen baseline (tests/architectural/glossary_canonical_terms_baseline.txt) deleted, so
    test_glossary_canonical_terms.py now enforces zero non-canonical occurrences in scanned
    prose instead of freezing an escape hatch. The gate skips fenced code blocks and inline-code
    spans (captured CLI --help output and emitted-string literals keep their real casing) and
    exempts the historical docs/adr/ and docs/changelog/ trees. Also refreshes the AGENTS.md
    Canonical Kind Vocabulary table: charter-activatable tokens now list procedure (not
    template) and add glossary-pack; template, asset, and anti_pattern remain
    ArtifactKind members but are not charter-activatable (excluded via
    _NON_AUGMENTATION_ELIGIBLE_KINDS - this is a governance clarification, not a kind
    rename). Retires the Contextive generated-file staleness test.

  • The docs anti-sprawl structure ratchet (R1) is retired (#2851 follow-up). The per-PR
    blocking anti_sprawl_ratchet.py --strict step is removed from docs-freshness.yml, and
    the script plus its orphaned doctrine.directives.common_docs constant (its only consumer)
    and self-test are deleted. DIRECTIVE_042's single-root / 13-section / no-shadow-tree policy
    is unchanged - it remains a red-line integrity rule - but is now maintained by periodic bulk
    curation rather than a per-PR CI gate, matching the operator's bulk-remediation ops intent and
    trimming a blocking CI step. The other docs rulers (R2 related-edge validator, R3 lockfile
    freshness, description-length and body-link gates) are unaffected and stay blocking.

  • Runtime-state corpus cutover completed; the phase-1 dual-write flag is deleted (#2816, #2848).
    Completes the #2684 / #2093 eviction. WP runtime state - lane, claim, agent/assignee,
    shell_pid, subtask completion, tracker_refs, review-cycle fields, and resolved
    role/profile/model bindings - is now unconditionally reconstructed from the append-only
    event log through a single reader (reconstruct_wp_view); the _phase1_dual_write_enabled
    flag and the legacy frontmatter-runtime fallback are removed, so tasks/WP##.md frontmatter
    is no longer a runtime authority. The existing corpus was migrated (per-mission backfill -> verify -> status_phase flip). Adds spec-kitty migrate backfill-runtime-state, which
    seeds a mission's legacy runtime as events, verifies the reduced snapshot against the old
    reader by count + value (fail-closed - never flips a mission that fails verify; per-mission
    best-effort), and flips meta.json status_phase to snapshot-authority only for verified
    missions; the same seed->verify->flip path ships as an auto-discovered upgrade migration for
    consumer repos.

  • Skill projection delivers copies, never absolute symlinks (#2412, ADR 2026-07-19-1).
    Projected skill files under .claude/skills/, .agents/skills/, etc. used to be
    absolute symlinks into the user-global canonical root - which dangle when the repo
    is mounted into a dev-container or synced to a remote box, are unreadable to agent
    harnesses sandboxed to the repo root, and all break at once if the global root
    moves. _project_skill_file now always delivers a real copy (the pre-existing
    Windows fallback path, promoted): hash-equal destinations are left untouched
    (idempotent re-runs), legacy symlinks are replaced with copies organically on each
    project's next init/upgrade/repair run (no migration needed), and repair_skills
    always repairs to a copy, healing pre-existing delivery_mode: symlink manifest
    entries as it goes. Freshness is preserved because every install run re-projects
    the full skill set; copies inherit the canonical root's read-only mode. Supersedes
    the per-project-symlink half of ADR 2026-04-08-3 (the global canonical root is
    unchanged).

  • charter generate seeds a starter charter.md companion when absent (#2800).
    After the #2773 inversion charter generate produced no charter.md at all, leaving a
    fresh project without the display-only rationale companion and no signal one should exist.
    generate now writes a minimal starter charter.md only when the file is absent
    (create-if-absent); an existing curated companion is left byte-for-byte untouched,
    preserving the #2772 never-clobber invariant. charter.md remains display-only, never a
    resolving input. ADR 2026-07-18-1 amended.

  • Internal: the specify_cli aware-UTC clock contract is now enforced structurally
    (#2496).
    Follow-up to #2494's clock-consolidation sweep. Routes the remaining
    byte-identical datetime.now(UTC).isoformat() "now"-stamp call sites in specify_cli
    onto the single canonical now_utc_iso() helper, the canonical producer of that form.
    Behaviour-preserving: the UTC / timezone.utc spellings serialize byte-identically
    under requires-python >=3.11. Adds an AST negative gate over the whole
    src/specify_cli tree (tests/specify_cli/test_clock_consolidation.py), alongside
    the pre-existing owned-file inventory, so a module added later is covered the moment it
    lands rather than silently regressing while the suite stays green; the gate ships a
    self-mutant non-vacuity test and a stale-exemption check. It targets the fluent
    single-expression <x>.now(<aware-UTC>).isoformat() idiom (import aliases resolved);
    distinct contracts are deliberately out of scope and not flagged: the second-precision
    %Y-%m-%dT%H:%M:%SZ stamp family, isoformat(timespec=...), naive now(), the
    datetime-returning family, and non-fluent forms (a variable-split or a space-separated
    str() of an aware instant).

  • Internal: the coord-authority trio is decomposed into ports + pure cores
    (#2464, #2465).
    The three coord-authority god-modules are restructured
    behaviour-preservingly into the shipped Typer-shell + request-dataclass + pure-cores
    (ports injected) + executor pattern - following MissionResolver (#2494) and
    tasks.py (#2308): workflow.py -> workflow_cores.py + workflow_executor.py,
    implement.py -> implement_cores.py (a GitPort injected and two # noqa: C901
    suppressions removed), and acceptance/ -> summary_core.py + gates_core.py. The
    trio's leaf resolvers now consume the kind-aware placement seam, preserving all three
    (lenient) read contracts (#2465). No user-facing behaviour change; pinned by
    seam-only + cores-no-I/O architectural tests and a characterization safety net over
    implement / review / accept / next.

  • Read-side placement seam closure: the last unpoliced primitive is gone
    (#2886, #3014; closes the read-side placement-seam migration opened by
    #2922/#2921).
    The #2922 migration above left one gap on purpose - its own
    gate coverage bounds named primary_feature_dir_for_mission as unpoliced,
    because the wrapper itself, and the ~34 call sites still composing a
    mission's primary directory by calling it directly, hadn't been routed
    through the seam yet. They are now: every one of those call sites asks
    placement_seam(root, slug).read_dir(<kind>) for the artifact kind it
    actually wants, instead of asking a topology-blind wrapper to guess a path
    from a slug. The terminal KITTY_SPECS_DIR assembler survives as the
    seam's own module-private leaf - the one place still allowed to compose a
    path, because something has to. One behavioural nuance was accepted rather
    than papered over: on a backfilled mission where the old blind composition
    returned a directory that does not exist, the seam now recovers the real
    bare-<slug> directory instead of repeating the miss (identity-confirmed,
    not just path-shaped). A new architecture reference,
    docs/architecture/artifact-placement-seam.md,
    documents the L0-L4 read-placement layering end to end; branch-target-routing.md
    is narrowed to the branch-target sense now that placement has its own home.
    Governed by ADR
    2026-06-24-1
    and ADR
    2026-07-23-1;
    no new architectural decision was needed.

  • Activating a subset of directives, tactics, styleguides, toolguides,
    mission-step contracts, or glossary packs now actually narrows what your
    project sees - it didn't before (mission charter-sole-door-bypass-closure).

    If your charter.yaml/pack config selects only some of a pack's
    directives (or tactics, styleguides, toolguides, mission-step contracts,
    glossary packs), the runtime, CLI, and rendered context now consistently
    show only the selected set for those 6 kinds. Previously this filtering
    only worked for paradigm, procedure, and agent_profile - the other 6
    kinds fell through unfiltered, so a deactivated pack could still show up.
    Mission-type selection was already honoured before this mission; it is
    now covered by regression tests confirming that. If you haven't
    configured any activation selection, nothing changes
    - a
    bare/unconfigured project's result set is unaffected. One deliberate
    exception: .kittify/profiles (a local-override directory outside the
    doctrine activation model) is explicitly still unfiltered by this
    change - a scope call, not a missed site; that area is slated for
    separate future rework.

    Under the hood, this closes every enumerated direct-construction and
    ._inner bypass
    around the charter's DoctrineService factory and
    extends activation gating from 3 kinds to all 9 charter-activatable kinds
    plus the mission-type token. It does not close the full ~20-door
    list: doctrine.template_catalog.resolve_template_by_id (5 importers),
    specify_cli/runtime/resolver.py's tier-1-4 reimplementation,
    runtime/home.py's importlib.resources root lookup, three
    root-relative missions-root duplicates, and one escalated
    AgentProfileRepository site (tool_surface/profiles/projection.py)
    remain - each named in the mission spec and tracked as its own follow-on
    (#3176 for the last). This mission also promotes
    MissionTemplateRepository.default_missions_root() as the single shared
    missions-root authority and retargets two duplicate hardcoded path
    constructions onto it, but that consolidation does not claim
    convergence with doctrine.pack_paths.built_in_dir - full convergence
    remains #3091's to deliver.

    One more consumer-visible narrowing, for projects that declare
    languages.
    If your project's compiled `charter.yaml