✨ Added
- ToolSurfaceContract unified registry (mission
tool-surface-contract-01KV2K2P, PR #1948):
src/specify_cli/tool_surface/is now the bounded context for configured tool surface policy.
spec-kitty doctor tool-surfaces --jsonreports stable findings and repair commands across command
skills, doctrine skills, session/context surfaces, native agent profile projections, and plugin
bundle surfaces.doctor skills --jsonremains backward-compatible, legacyagent configflows
still work through the new contract, and fresh clones now get actionable generated-surface repair
plans instead of silent missing.agents/skills/drift. - Branch-strategy recommendation in
/specify(issue #765):spec-kitty agent mission branch-context
now resolves the repository's primary branch and emits a recommendation payload (primary_branch,
current_is_primary,recommended_strategy,reason). The software-dev specify prompt consumes it to
proactively recommend starting on a dedicated feature branch;mission create --start-branchnow
creates/switches before any mission artifacts are written when the operator is on the primary branch and
expects a later PR; staying on the current branch remains an explicit, supported choice (wiring--pr-bound
branch-strategy gate into the operator flow). The recommendation fields are additive and opt-in: callers
that do not resolve a primary branch receive the byte-identical legacy branch contract.
🐛 Fixed
- Docs: corrected the retired
spec-kitty agent workflow implementcommand (issue #1874): the
agent workflowcommand group no longer exists (the canonical form isspec-kitty agent action implement/… review). Updated the user-facingdocs/how-to/implement-work-package.mdand the
AGENTS.mdtesting note. (The same stale command also appears in the PowerShell toolguide and the
documentation/research per-WP task-prompt templates; those are rendered into the twelve-agent command
snapshots, whose baselines are already drifted onmain, so that replacement is left to the
cli-reference-audit sweep which can regenerate the baselines in one pass.) spec-kitty upgradeno longer churnsmetadata.yamlon a no-op (issue #1871): the "stamp
last_upgraded_atonly on material change" rule lived in three divergent idioms, and the migrations-applied
root path plus_stamp_schema_versionrewrotemetadata.yaml(and advanced the timestamp / mtime) even when
every migration was already recorded.ProjectMetadata.save()now does a masked compare-before-write
(skipping the write when only the volatilelast_upgraded_at/schema_versionwould change) and
_stamp_schema_versionskips its re-dump when the rendered bytes already match disk. A genuine
version/migration/environment change still writes with a fresh timestamp; a no-op upgrade — including across a
fully-recorded version range, on both the root and worktree paths — is now zero writes. This closes the class
at the write boundary for upgrade/doctor/regeneration instead of adding a fourth per-path guard.agent tasks map-requirements --jsonno longer crashes on auto-commit (issue #1891, Finding 1): the
command stored theCommitResultreturned bysafe_commit()directly in the--jsonpayload, so on the
auto-commit success pathjson.dumpsfailed with "Object of type CommitResult is not JSON serializable" —
the mapping succeeded but agents got an unparseable error instead of the result.committedis now a bool
and the resultingcommit_sha(ornull) is exposed alongside it. (Findings 2 and 3 —agent action implement --jsonandsetup-plan/finalize-tasksJSON preamble — are tracked separately.)accept --lenientnow relaxes mission path conventions (issue #1892):spec-kitty accept/agent mission acceptvalidated a mission's declaredpaths(src/,tests/,contracts/for software-dev)
unconditionally, so repos with a non-default layout (e.g. a Go service usinginternal/with no top-level
tests/) failed acceptance even with--lenient— the only workaround was creating throwaway empty
directories. Path conventions now block only in strict mode; under--lenientan unmet convention is
surfaced as a non-blocking warning. (A per-projectpathsoverride remains a possible follow-up.)- Name-vs-authority remediation (mission #133; closes #1889, #1860, #1865, #1866, #1867, #1863, #1896, #1898, #1904, #1684, #1906): (#1884/#1883/#1885 were independently fixed by PR #1910 and are verified-already-fixed here, not re-closed)
binds the two remaining "a name/string shape is trusted as authority without cross-checking the declared authority"
seams and ratchets them closed, and clears the live 3.2.0 release-blocker P0s rooted in that class. Topology
authority seam (WorktreeTopology+classify_worktree_topology+is_registered_coord_worktreein
coordination/surface_resolver.py, wrapping thegit worktree list --porcelainregistry) and branch-identity
authority seam (mission_branch_name_required+ structuredBranchIdentityUnresolvedinlanes/branch_naming.py,
dual-era: legacy\d{3}-AND mid8 names both resolve) replace the convention predicates at their consumer sites;
the(slug.replace('-','')+"00000000")[:8]mid8-fabrication idiom is eradicated (routed through
resolve_transaction_mid8, fail-closed). P0s fixed:setup-plan's committed-spec gate verifies against the
placement authority's ref not primary HEAD (#1884); the accept gate is idempotent across all modes via
accept-owned-path exclusion (#1883); unresolvable mission handles raise a structuredMissionNotFoundError
(code + next_step, #1911) instead of a silentmission=unknownstub (#1885 residual). #1889's coordination-branch-deleted
case becomes a distinct loudCoordinationBranchDeleted(decision-table row R3). An architectural ratchet
(test_topology_resolution_boundary.py) keeps coord predicates, unbackstoppedkitty/mission-{slug}composes, and
the fabrication idiom from regrowing outside the blessed seam modules. Doctrine refinements (#1865/#1866/#1867) and
the DRG extractor styleguide/toolguidereferenceswalk (#1863) ride along; the authority-path default flips
architecture/2.x/adr→3.x/adr. Cross-lane dependency code propagation (#1684):allocate_lane_worktree
now merges approved dependency-lane tips (fresh creation + lane re-entry) so a dependent WP in a sibling lane sees
its approved dependency's code, instead of branching from the bare mission branch. _branch_exists/ref_existsconsolidation (#1904): the duplicatedgit rev-parse --verifybranch/ref
existence idiom acrosscoordination/status_transition.py,missions/_create.py,lanes/worktree_allocator.py,
andlanes/merge.pyis unified intolanes/_git.py(env-parameterized so the merge path's environment composes).
🧹 Maintenance
-
SonarCloud hygiene on mission #133 surfaces: raised new-code coverage on the authored seam/allocator/query
files; reduced cognitive-complexity (extract-method) and duplicate-literal smells acrossdoctrine.py,
sync/daemon.py,sync/owner.py,drg/validator.py,org_charter.py,_read_path_resolver.py,core/worktree.py,
agent/workflow.py, andupgrade.py(all behavior-preserving); regenerated stale codex/vibe command-skill
snapshots to match the advanced templates (PR #1897 finding). -
Upgrade no longer re-records not-applicable migrations (issue #1872): a migration whose
detect()
isFalsewas re-appended as askipped/ "Not applicable"MigrationRecordon everyspec-kitty upgrade
run over the same version range, growingapplied_migrationswithout bound and — for worktrees, after
#1857 — bumpinglast_upgraded_aton no-op runs.ProjectMetadata.record_migration()is now idempotent
(an identical(id, result)record is not re-appended) and the worktree upgrade path only marks metadata
dirty when a new record was actually written, restoring stablelast_upgraded_atfor no-op re-runs. A
genuinefailed → successtransition still records the new result. -
Coordination & Merge stabilization (mission 131; closes #1826, #1861 Part 1, residuals of #1833/#1814/#1736/#1735):
merge-pipeline ref advances now resync any worktree checked out on the advanced branch (shared
git/ref_advance.pyhelper with a no-raw-update-refarchitectural ratchet), refusing loudly — never
resetting — when the worktree holds uncommitted state; the safe-commit backstop message names the diverged
worktree/ref/state;finalize-tasks --validate-onlyno longer switches the git checkout; task finalization
cleans its own primary-checkout residue (operator files untouched); workspace resolution treats non-worktree
"husk" directories under.worktrees/as structured failures instead of silently running git against the
primary repo, with a newspec-kitty doctor workspaces [--fix]check for self-serve recovery — note:
pre-existing husks that previously failed silently now produce explicit errors; run
spec-kitty doctor workspaces --fixonce to clean them; retrospective gating reads route through the
canonical status surface (AC10 ratchet);upgrade --dry-runno longer prints a success line implying
changes were applied; merge-driver hardening (single_make_merge_env()authority, narrowed exception
mask, deterministic mixed-timestamp event-log sort). -
Protected-branch guard capability honesty (PR #1850 review): the bool→capability conversion had
re-opened protected-ref commits from production flows — three sites assertedGuardCapability.TEST_MODE
(legacy workflow commit, baseline-artifact commit, finalize-tasks bootstrap) and six non-merge flows
borrowedMERGE_BOOKKEEPING(move-task, mark-status, map-requirements, decision-log, op-record). All
now assertSTANDARD; protected destinations refuse, and refusals degrade gracefully (decision events
and Op records are preserved on disk, nothing lands on the protected ref).SPEC_KITTY_TEST_MODEno
longer waives the command-level protected-branch prechecks — only the documented operator hatch
SPEC_KITTY_ALLOW_PROTECTED_BRANCH_COMMITSdoes — and the coordination gate now computes the same
hatch-awareProtectionStateassafe_commit, so the two can no longer disagree. Ratcheted by
tests/architectural/test_guard_capability_call_sites.py(capability→flow allowlist;TEST_MODEhas
zerosrc/callers) andtests/git/test_guard_capability_regression.py. -
Mission handle canonicalization completes at every CLI write boundary (PR #1850 review): bare mid8,
numeric-prefix, and full-ULID handles now resolve to the identical canonicalmission_slug,
mission_id, status surfaces, and placement (ref and kind) as the full slug — across
resolve_status_surface_with_anchor,resolve_placement_only,MissionStatus.load,
_find_mission_slug(agent tasks/status/workflow),agent decision open,merge --mission,
spec-kitty next --mission,plan --mission,mission run/close --mission,
research --mission, andcontext resolve(persistedauthoritative_ref). No more
wrong-but-plausiblekitty-specs/<mid8>/paths,legacy-<mid8>identities, split-brain runtime
runs or SaaS sync namespaces keyed by the raw handle, orclose --discardsilently leaving lane
branches/worktrees behind. Pinned by
tests/specify_cli/missions/test_handle_equivalence_matrix.py(78 parity tests). -
Sync daemon reaper is scoped to its daemon root, not just the interpreter (PR #1850 review): the
spawner embeds the resolved daemon state root and spawn-time interpreter identity as inert argv markers;
the reaper kills only on marker + spawn-signature + interpreter-identity match and conservatively skips
unmarked or unidentifiable processes. Fixes both the cross-$HOMEover-kill and the macOS
framework-Python inertness (where the re-exec rewritesexe()andargv[0]to thePython.appstub). -
CI
nextfilter covers the canonical runtime:src/runtime/next/**andsrc/mission_runtime/**
now trigger the next suites and count toward diff-coverage critical paths (previously only the
deprecatedsrc/specify_cli/next/shim was mapped, sointegration-tests-nextskipped on
canonical-runtime changes). -
StatusReadPathNotFoundno longer escapesmission_runtime's single-error contract: the fail-closed
refusal is translated toActionContextError(error code and message preserved) at all three resolution
boundaries and handled in the transactional status path;MissionStatus.loadkeeps its established
CoordAuthorityUnavailableshape for every handle form in the fail-closed coordination window. -
Repo hygiene: per-machine
.kittify/legacy-warning-shown-*marker files untracked and gitignored.