Contracts
lane-work-tip.md
Contract: lane work tip
Ref
- Name:
refs/spec-kitty/lane-tip/<full-lane-branch-name> - Scope: local only; never pushed.
Recorder hook
The hook is post-commit, written in POSIX sh, and installed at git rev-parse --git-path hooks by policy/hook_installer.py.
#!/bin/sh
# Generated by spec-kitty (lane-tip recorder). Do not edit by hand.
b=$(git symbolic-ref -q HEAD) || exit 0
case "$b" in refs/heads/kitty/mission-*-lane-*) ;; *) exit 0 ;; esac
id=${b##*-lane-}
case "$id" in ''|*[!a-z]*) exit 0 ;; esac
git update-ref "refs/spec-kitty/lane-tip/${b#refs/heads/}" HEAD >/dev/null 2>&1
exit 0
Rules:
- The hook is installed only if the slot is empty or already carries the spec-kitty signature. Otherwise the installer warns and leaves the slot alone (C-010).
- It always exits 0 and never starts Python (NFR-001).
API (specify_cli.lanes.lane_tip)
| Function | Returns / effect |
|---|---|
tip_ref(branch) -> str | the ref name for the lane branch |
| `record_tip(repo_root, branch, sha=None) -> str \ | None` |
| `read_tip(repo_root, branch) -> str \ | None` |
clear_tip(repo_root, branch) -> None | deletes the ref |
is_absorbed(repo_root, tip, target, base) -> bool | raises AbsorptionUnsupported when git is too old to evaluate |
is_absorbed returns True in any of three cases:
tip == basetipis an ancestor oftargetmerge-tree --write-tree target tipequalstarget^{tree}
Errors
| Error | Code | When | Carries |
|---|---|---|---|
DestroyedLaneError | DESTROYED_LANE (existing) | a destroyed lane whose tip is not absorbed | tip_sha and a remedy git branch <b> refs/spec-kitty/lane-tip/<b> (restore), or git update-ref -d refs/spec-kitty/lane-tip/<b> (deliberately abandon) |
LaneWorkTipUnknownError | LANE_WORK_TIP_UNKNOWN | the context is present but no tip ref exists | — |
single-branch-execution.md
Contract: single_branch execution
Owner: the lane manifest (lanes.json), read through is_repo_root_lane, and the workspace resolver.
Finalize
compute_lanes(..., topology=single_branch) returns a manifest that holds exactly one lane:
lane_id:lane-planningwp_ids: every work package in the mission
The manifest carries the target branch as target_branch. It also carries mission_branch, but only when meta.json records one.
If assert_topology_matches_manifest(topology, manifest) fails, the manifest write raises SINGLE_BRANCH_CODE_LANES_UNMIGRATED.
Resolve
For any work package in a single_branch mission, resolve_workspace_for_wp returns:
| Field | Value |
|---|---|
resolution_kind | repo_root |
worktree_path | effective_root if set, otherwise the repository root checkout |
branch_name | meta.mission_branch if set, otherwise target_branch |
lane_id | lane-planning |
status_execution_mode | direct_repo |
Implement: refusals
Refusals are checked in this order. Each names the blocking object and a remedy (NFR-004).
1. Unmigrated mission. Code lanes are present, so the command raises SINGLE_BRANCH_CODE_LANES_UNMIGRATED. 2. Wrong branch. The write checkout's HEAD is not the expected write branch. The error names both branches. The command never switches branches itself. 3. Another WP in progress. Another work package, from any single_branch mission, is in_progress in the same write checkout. The error names that mission and WP.
4. Dirty checkout. The write checkout has tracked changes, or untracked files outside spec-kitty-owned paths. The error lists the paths.
- Exception: resuming the WP that is already
in_progressis a no-op. - Exception: resume.
Claim base and for_review (post-plan fold B2)
A work package in a repo-root lane has no lane branch, so the for_review gate needs another starting point. This covers every repo-root lane WP, planning_artifact WPs included.
- Claim:
implementrecordsrefs/spec-kitty/wp-base/<mission_slug>/<WP>as the write checkout's current HEAD. On resume, it records the ref only if it is missing. - for_review: the gate requires at least one commit in
wp-base..HEADin the write checkout. If there are none, it refuses the move, as it does today. - Cleanup: the ref is deleted when the WP reaches a terminal state.
The manifest's mission_branch for a single_branch mission is meta.mission_branch when set, otherwise target_branch.
Implement: no dependency merge
Implement skips _merge_dependency_lane_tips for the repo-root lane. Dependency readiness (approved / done) is still enforced.
Consolidate
| Case | Behaviour |
|---|---|
Unprotected target, or commit_to_target | Bookkeeping only. No git landing and no branch deletion. |
| Protected target | Lands mission_branch onto target_branch through the mission→target phase. Authored blobs come from first-parent base..mission_branch. Checks out target_branch in the write checkout, then deletes mission_branch. Never removes the repository root checkout or target_branch. |
topology-restamp.md
Contract: topology re-stamp migration and doctor finding
Migration m_4_0_0rc5_single_branch_code_lanes_restamp
| Property | Value |
|---|---|
MIGRATION_ID | 4_0_0rc5_single_branch_code_lanes_restamp |
TARGET_VERSION | 4.0.0rc5 |
runs_on_worktrees | False |
Behaviour:
- Selection. Walks the
kitty-specs/*/directories on the checked-out branch. A mission is selected whenread_topology(meta) == single_branchandhas_code_lanes(lanes.json). - Write. For each selected mission it writes
topology: lanesthrough the canonical meta writer, and changes no other field. - No commit. The migration does not commit. The upgrade auto-commit owns that.
- Idempotent. A second run changes nothing (NFR-003).
- Operator CLI.
spec-kitty migrate backfill-topology --restamp-single-branch [--dry-run]calls the samerestamp_single_branch_with_code_lanes().
Doctor
spec-kitty doctor topology adds per-mission finding rows:
| Finding | Carries |
|---|---|
SINGLE_BRANCH_CODE_LANES_UNMIGRATED | the mission slug and the remedy |
| (none) | clean single_branch missions produce no row |