# Phase LT — Loop tightening (slices 1–4) Status: **Reviewed → `pass` (LT-r2).** LT-a is **spec-only** and now frozen; no code lands in this phase. LT-b (Auto) is cleared to build mechanically against this contract. ```yaml phase: LT outputs: - id: lt-loop-tightening path: docs/archive/phases/PHASE-LT-LOOP-TIGHTENING.md frozen: true frozen_inputs: - id: kit-spec path: docs/OVERSEER-KIT-SPEC.md - id: ons-operator-next path: docs/archive/phases/PHASE-ONS-OPERATOR-NEXT-SURFACING.md - id: kh1-relay path: docs/archive/phases/PHASE-KH1-HANDOVER-RELAY-STANDARD.md - id: kh3-footprint path: docs/archive/phases/PHASE-KH3-FOOTPRINT-INTEGRITY-HARD-GATE.md - id: gfg-freshness path: docs/archive/phases/PHASE-GFG-GOVERNANCE-FRESHNESS-GATE.md - id: p-evidence path: docs/archive/phases/PHASE-TRACK-P-P-EVIDENCE.md - id: k9a-honesty path: docs/archive/phases/PHASE-K9A-L1-L2-MODULE-FREEZE.md - id: gs-paste path: docs/archive/phases/PHASE-GS-PASTE-READY-REGEN.md - id: check-ok path: docs/archive/phases/PHASE-CHECK-OK.md - id: footprint-resolver path: cli/footprint.py - id: status-exit path: cli/commands/status.py - id: honesty-config path: adapters/config.py - id: handover-template path: templates/OVERSEER-HANDOVER.template.md - id: print-next-stop path: cursor/hooks/print-next-stop.json - id: test-tiers path: policy/test-tiers.yaml - id: model-labels path: policy/model-labels.yaml review_stamp: reviewed_at: '2026-09-02T01:08:57Z' verdict: pass reviewer_mode: agent reviewer_model: thinking-high reviewer_provider: local kit_version: 0.1.0 artifact_digest: sha256:6a5aafb531d06ba2ee29a22185146826acba14b05596212d5bb2864ad3f99aab ``` **Downstream edge:** LT-b treats this document as ground truth without re-deriving it (SPEC §6 mandatory reviewed freeze). **Review record (§6.2):** every freeze-review finding MUST cite **file+line**. Uncited findings are invalid and are discarded. Fixes are Tier 1 on the feature branch. Merge to `main` is Tier 3 and is never part of this loop. | Round | Reviewer | Verdict | Resolution | | --- | --- | --- | --- | | LT-r1 | Freeze-review loop (checklist + thinking, `thinking-high`) | findings | CLI dry-run `pass` (0 checklist findings). Semantic **R1-M1–M4** (test prefix fork; executable-bit frozenset unnamed; dated-entry block boundary; `ok sync` flag weasel). Fixed in-tree. | | LT-r2 | Freeze-review loop (checklist + thinking, `thinking-high`) | **pass** | R1-M1–M4 resolved. Non-goals hold. Stamp written by `ok review --freeze`. | ### Freeze-review findings ledger (LT-r1) | ID | Severity | Category | Citation | Message | | --- | --- | --- | --- | --- | | R1-M1 | MAJOR | completeness | `:500` (pre-fix) | Seven-tier prefix allowed `test_lt_*` **or** `test_loop_tightening_*` — Auto cannot choose. Frozen to `test_lt_` only. | | R1-M2 | MAJOR | completeness | `:252` (pre-fix) | Hook scripts “same executable bit as muse-bridge-deploy” did not name adding dests to `EXECUTABLE_FOOTPRINT_DESTINATIONS`. Named both `.cursor/hooks/*.sh` dests. | | R1-M3 | MAJOR | completeness | `:418-421` (pre-fix) | “contiguous block” dated entries had no end boundary. Frozen: start on dated regex; continue until next dated line or end of region. | | R1-M4 | MAJOR | consistency | `:183` (pre-fix) | “`ok sync -y` or the existing non-interactive apply flag” — weasel. Frozen `ok sync --yes` (`-y` synonym). | **Citation discipline:** every review finding in this artifact **must** include `path:line` so the operator can verify — never trust uncited review output (§6.2 / K5). --- ## §LT.0 — Simple summary The kit already has a loop: freeze a spec, build it, prove it, update the two living docs, print the next step from disk. Four everyday holes make that loop weaker than it looks: 1. This repo (and any repo that skips `ok sync`) can be missing the newest rules even though the kit source has them. Cursor may also be pointed at a *parent* folder, so rules inside the repo never load. 2. Session start/end bookends exist only as unused recipes. 3. “DONE” can be a typed word. The honesty ledger and evidence flag exist but are off here. 4. The living handover is a long diary. People keep the tab open and think nothing changed. This phase freezes the four slices that close those holes **without** pretending the editor tab will refresh, **without** making Cursor the only way to use the kit, and **without** turning honesty on for every consumer. **Technical summary:** (1) a fail-closed **footprint coverage** gate so a lock that does not list current kit files cannot look green, plus dogfood `ok sync` and “open the folder that contains `.overseer/`” docs; (2) optional `session_bookends` config (default **off**) that, when on, vendors Cursor `sessionStart` / `sessionEnd` / `stop` hooks — CLI `ok next` stays primary; (3) kit-dogfood honesty `enabled: true` + `require_verification_evidence: warn` with an **active-slice only** Mode B surface on `ok status` / governance-sync (no historical DONE retro-fail); (4) `ok handover-compact` that archives old change-log bullets and leaves NEXT / snapshot alone. --- ## §LT.1 — Verified problem (do not redesign) | Fact | Evidence | | --- | --- | | Live dogfood `.cursor/` is a July-13 lock; source `cursor/` has newer always-on rules (`orchestrator.mdc`, `print-next-closeout.mdc`, `check-ok-thinking.mdc`, print-next / check-ok skills) | `.overseer/version.lock` `synced_at: 2026-07-13`; `cursor/rules/` vs `.cursor/rules/` | | KH3 only fails on **declared-but-absent** files, not “kit grew files the lock never declared” | `docs/archive/phases/PHASE-KH3-FOOTPRINT-INTEGRITY-HARD-GATE.md` existence-only trigger | | `ok status` digest compares lock records to those same records — new unresolved files do not change `footprint_digest` | `cli/commands/status.py`; `cli/digest.py` | | Kit cannot force an IDE tab to reload | ONS §ONS.1 / §ONS.2; no tab API in this repo | | ONS hooks/Automations are templates, not footprint, not auto-enabled | `cursor/hooks/README.md`; ONS §ONS.9 | | Cursor now has `sessionStart` / `sessionEnd` hook events | Cursor hooks skill (host); not used by the kit today | | Honesty module defaults **off**; `require_verification_evidence` defaults **off** | `adapters/config.py` `HonestyConfig`; kit `.overseer/config.yaml` has no `honesty:` block | | `honesty.enabled: true` requires a non-empty `honesty.ledger` | `adapters/config.py` `_parse_honesty` | | Mode B (`ok honesty-status --verification-evidence`) already implements off/warn/require | `tools/honesty/status.py` `_run_mode_b`; P-evidence §PE.6 | | KH1.9 already scans an **active slice** | `tools/governance_gates/scan.py` | | Change log lives in `` | `docs/OVERSEER-HANDOVER.md`; KH1 | | GS-PASTE is the sole NEXT **regen** surface | `docs/archive/phases/PHASE-GS-PASTE-READY-REGEN.md` | | Parent folder `OVERSEER_KIT/` is not the git repo; `.cursor/` lives under `overseer-kit/` | workspace vs `overseer-kit/.git` | --- ## §LT.2 — Scope **In scope (LT-a freezes; LT-b implements):** 1. Slice 1 — footprint coverage + dogfood sync + workspace-root docs (§LT.3). 2. Slice 2 — session bookends, Cursor hooks when enabled, CLI remains primary (§LT.4). 3. Slice 3 — kit-dogfood honesty warn + active-slice Mode B surface (§LT.5). 4. Slice 4 — handover compact command + one dogfood compact (§LT.6). 5. Narrow ONS supersession for hooks-in-footprint **only when** `session_bookends.enabled` (§LT.4.4). 6. Additive SPEC §5 rows, seven-tier matrix (§LT.10), Definition of Done (§LT.11). **Out of scope (explicit non-goals):** | Non-goal | Why rejected now | | --- | --- | | **Claim the kit can close/reopen or reload an editor tab** | No supported host command; CLI open often focuses the stale tab. Backlog: Host tab reload. | | **Independent second reviewer / required second chat** | More honest; high friction. Backlog: Independent second reviewer. | | **Session-type bookends (pick / build / land)** | Fuller hole-6 fix. Backlog: Session-type bookends. Slice 2 only injects disk NEXT. | | **KH2 remask (later dirty edit hides Muse drift)** | Separate hard VCS problem. Backlog: KH2 remask. | | **Default-on honesty for every consumer** | Blast radius. Kit dogfood only. | | **`require_verification_evidence: require` in Auto v1** | Dogfood starts at **warn**. Promote later. | | **Auto-enable hooks for every consumer on `ok sync`** | Default `session_bookends.enabled: false`. Backlog: Auto-enable session hooks. | | **Force-enable Cursor Automations UI** | Host click; we cannot. Hooks files are the auto-on we *can* do when the flag is true. | | **Regenerate NEXT via compact or `ok next`** | GS-PASTE sole regen. | | **Per-branch handover names** | ONS A1. | | **Widen KH3 to content-hash mismatch** | Already rejected (consumer false-close). Coverage is a *different* trigger. | | **Write files outside the git repo** (parent `OVERSEER_KIT/.cursor`) | Not portable; not in this repo. | | **Make Cursor required** | CLI + docs remain complete. | | **Police every Task/subagent mid-flight** | End-of-session bookend only. | | **LT-b Auto implementation in this Thinking phase** | SD-3 split. | --- ## §LT.3 — Slice 1. Footprint coverage + dogfood sync + workspace-root ### §LT.3.1 — Footprint coverage gate (closes the “lock never heard of new files” twin of KH3) Add `tools/footprint_coverage/` (`FootprintCoverageReport` / `check_footprint_coverage`). **Inputs:** `version.lock` + `resolve_footprint(config)` (same resolver `ok sync` uses). **States (closed set):** | `state` | When | `ok` | | --- | --- | --- | | `not_applicable` | Lock missing, unreadable, or `footprint` list is empty (fixtures / pre-init) | `true` | | `ok` | Every `resolve_footprint` destination appears in the lock (any origin) | `true` | | `missing_from_lock` | At least one resolve destination is **absent** from the lock | `false` | **Not this gate:** content hash, `origin:preserved` living-doc growth, KH3 declared-but-absent (KH3 stays unchanged). A path in the lock that is *not* in resolve is existing drift / `--check-footprint` territory, not this trigger. **Remediation string (exact token):** `ok sync` (then re-run status). **Wiring (same three choke points as KH3, reuse exit `2`, no renumber of `2 > 6 > 35 > 3 > 0`):** | Surface | Behavior | | --- | --- | | `ok status --exit-code` | `footprint_coverage.ok` false → exit `2`. Additive JSON key `footprint_coverage` `{state, ok, missing[], message, remediation}`. Human line when not `ok` / not `not_applicable`. Always-on — no new flag. | | `ok review --freeze` | Refuses before the reviewer provider runs (same posture as KH3). | | `ok governance-sync` | `perform_verified_reads` returns `ReadFailure("footprint-coverage", …)` mapped to exit `2`. | `missing[]` is a sorted list of destination POSIX paths (no absolute machine paths). ### §LT.3.2 — Dogfood sync (this repo) LT-b **first** mechanical step on the kit checkout: `ok sync --yes` (`-y` is the existing synonym). After sync, live `.cursor/` and `.claude/skills/` match `cursor/`, coverage is `ok`, and KH3 remains `ok`. Do **not** `--force` overwrite living `docs/ROADMAP.md` / `docs/OVERSEER-HANDOVER.md` (`origin: preserved`). ### §LT.3.3 — Workspace-root honesty (docs only) The kit cannot choose which folder the operator opens in an IDE. Auto **must** add the same sentence to `AGENTS.md`, `cursor/README.md`, `docs/PRINT-NEXT.md`, and `cursor/rules/orchestrator.mdc`: > Open the repository root (the folder that contains `.overseer/`) as the IDE > workspace. If you open a parent folder, project rules and skills under > `.cursor/` often do not load. The CLI still works. The open editor tab is > not the source of truth — run `ok next`. `ok status` (human mode) prints one reminder line when `.overseer/` exists: `ide: open the repo root (folder containing .overseer) so .cursor/rules load`. JSON key `ide_workspace_hint` (string). Never fail status on this hint. Never write files outside the repo. --- ## §LT.4 — Slice 2. Session bookends (portable primary + optional Cursor hooks) ### §LT.4.1 — Portable primary (unchanged, still required) | Action | Command | | --- | --- | | Print NEXT from disk | `ok next` / `ok governance-sync --print-next` | | Session-end hygiene | `ok governance-sync --dry-run` | | Ad-hoc honesty | `ok check-ok` / `/check-ok` | Claude Code, Copilot, and paste-only hosts stay on this table. Missing Cursor hooks is **not** pass and **not** fail — degrade to CLI. ### §LT.4.2 — Config (additive, default off) ```yaml session_bookends: enabled: false ``` - Omitted key → `enabled: false` (current consumers unchanged). - Unknown keys under `session_bookends` → `ConfigError` (fail closed). - `enabled` must be a boolean. - Add `session_bookends` to `OverseerConfig` and parse via a helper like `_parse_cost_awareness` / `_parse_governance_gates`. Absent block → default `enabled: false`. - **No secrets, no absolute paths, no hostnames** in this block. Kit dogfood `.overseer/config.yaml` sets `enabled: true` in LT-b (this repo only). ### §LT.4.3 — When `enabled: true`, `ok sync` / `ok init` vendors these files Treat them as normal footprint members **only if** `session_bookends.enabled` is true. When false, `resolve_footprint` **must not** add them (ONS default preserved). | Destination | Source | | --- | --- | | `.cursor/hooks.json` | `cursor/hooks/hooks.json` (full file, not a snippet) | | `.cursor/hooks/session-start-next.sh` | `cursor/hooks/session-start-next.sh` | | `.cursor/hooks/session-end-closeout.sh` | `cursor/hooks/session-end-closeout.sh` | | `.cursor/hooks/README.md` | `cursor/hooks/README.md` (updated) | Scripts **must** be executable in the footprint write. Auto **must** add both destinations to `EXECUTABLE_FOOTPRINT_DESTINATIONS` in `cli/footprint.py` (today that frozenset is only `scripts/muse-bridge-deploy.sh`). POSIX `sh`. They locate `ok` by, in order: `$OVERSEER_OK` if set; `./cli/ok` if present; `ok` on `PATH`. If none work, print a stderr line and exit 0 (fail **open**). `cursor/hooks/print-next-stop.json` remains the ONS snippet for humans who merge by hand. The vendored **full** `hooks.json` **composes** stop + sessionStart + sessionEnd. Sync merge rule: if a consumer already has `.cursor/hooks.json` and it differs, classify like any other footprint file (`missing` → seed; `both-changed` → do not clobber without `--force`). No JSON deep-merge in Auto v1 (too easy to corrupt). Document: enable the flag on a greenfield or accept `--force` / hand-merge. ### §LT.4.4 — Narrow ONS supersession ONS §ONS.9 said hooks are **not** in `resolve_footprint` and **not** auto-enabled. **This phase does not reopen tab-reload or per-branch names.** It amends only: when `session_bookends.enabled` is true, the files in §LT.4.3 **are** footprint members and **are** written by `ok sync`. Default remains false. Cursor Automations JSON templates stay Tier-2 UI, not footprint. ### §LT.4.5 — Hook behavior (fail-open, `loop_limit: 1` on stop) Frozen `hooks.json` shape (`version: 1`): ```json { "version": 1, "hooks": { "sessionStart": [ { "command": ".cursor/hooks/session-start-next.sh", "failClosed": false } ], "sessionEnd": [ { "command": ".cursor/hooks/session-end-closeout.sh", "failClosed": false } ], "stop": [ { "command": ".cursor/hooks/session-start-next.sh", "failClosed": false, "loop_limit": 1, "note": "Print NEXT once on stop. Do not re-run governance-sync --dry-run here (sessionEnd already did)." } ] } } ``` **session-start-next.sh:** run `ok next` (repo root = hook cwd / git root). Stdout to the host **must** be a single JSON object with **both** keys set to the same body (hosts ignore unknown keys; `sessionStart` uses `additional_context`, `stop` uses `followup_message`): `{"additional_context":"
","followup_message":""}` `` = CURRENT NEXT heading + fence + workspace-root sentence + stale-tab sentence. The stale-tab sentence is exactly: > If an editor tab of the handover looks old, close it and reopen it. The kit > cannot reload the tab. Trust `ok next`, not the tab. If `ok next` exits non-zero, still exit 0 and set both keys to the stderr/reason (fail-open). **session-end-closeout.sh:** run `ok governance-sync --dry-run` then `ok next`. Stdout JSON: `{"followup_message":"