Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 25 additions & 15 deletions docs/src/content/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ no-mistakes init --worktree-root ~/work/my-repo-runs

| Flag | Type | Default | Description |
| ----------------- | -------- | ------- | ------------------------------------------------------------------------------------------------ |
| `--fork-url` | `string` | (none) | GitHub fork remote URL to push branches to while opening PRs against `origin` |
| `--fork-url` | `string` | (none) | GitHub fork remote URL to push branches to while opening PRs against `origin` |
| `--worktree-root` | `string` | (none) | Directory to create this repository's run worktrees in; prints the `worktree_roots` entry to add |

Creates or refreshes a local bare repo, installs the managed pre-receive admission and post-receive notification hooks, best-effort isolates the gate repo's hook path from shared git config changes when Git supports `config --worktree`, adds or repairs the `no-mistakes` git remote, detects the default branch, records or updates the repo in SQLite, installs the `/no-mistakes` agent skill at user level into `~/.claude/skills/no-mistakes/SKILL.md` and `~/.agents/skills/no-mistakes/SKILL.md`, and ensures the daemon is running, installing the managed service when available and falling back to a detached daemon otherwise.
Expand Down Expand Up @@ -201,11 +201,11 @@ no-mistakes axi sync --recover
no-mistakes axi sync --recover --keep-local
```

| Flag | Type | Default | Description |
| -------------- | ------ | ------- | ---------------------------------------------------------------------------- |
| `--check` | `bool` | `false` | Verify the live target and exact plan without changing `HEAD` |
| Flag | Type | Default | Description |
| -------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `--check` | `bool` | `false` | Verify the live target and exact plan without changing `HEAD` |
| `--recover` | `bool` | `false` | Return custody of a branch stranded by a terminal run with unpublished pipeline commits (a no-op when cancellation already released the branch) |
| `--keep-local` | `bool` | `false` | With `--recover`: keep the current local head; never touches the worktree |
| `--keep-local` | `bool` | `false` | With `--recover`: keep the current local head; never touches the worktree |

The default command is an explicit non-interactive apply request and never prompts.
All modes return the complete `branch_sync` object as TOON.
Expand All @@ -221,7 +221,7 @@ Run `axi sync` only when structured output offers `next_action.code: sync`; proc

### Custody recovery

A run that goes terminal (cancelled, failed, or completed without a push stage) after moving the pipeline head leaves the branch `pipeline_owned`. Status offers `next_action.code: recover_custody` only when recovery can establish the same eligibility it will enforce: an equal or ahead local head proves the source locally and can create the local anchor when the gate is unavailable, but any existing gate recovery ref must still match the recorded head; importing a missing preserved head requires an exact run-specific gate anchor (or legacy commit evidence that can be anchored), a clean worktree, and either local ancestry or the content-preservation proof described below. The eligible state reports `safety: blocked_pipeline_owned_recoverable`, the run's terminal `pipeline.status`, and the exact `submitted_head`/`current_head`/`relation` ownership facts.
A run that goes terminal (cancelled, failed, or completed without a push stage) after moving the pipeline head leaves the branch `pipeline_owned`. Status offers `next_action.code: recover_custody` only with verified source evidence: an equal or ahead local head proves the source locally and can create the local anchor when the gate is unavailable, but any existing gate recovery ref must still match the recorded head; importing a missing preserved head requires an exact run-specific gate anchor (or legacy commit evidence that can be anchored), a clean worktree, and local ancestry. A clean non-ancestral local head whose comparison requires the gate reports `safety: blocked_recover_explicit_verification_required`: cached status deliberately defers the write-capable content-preservation proof to explicit `--recover`. The ordinary eligible state reports `safety: blocked_pipeline_owned_recoverable`, the run's terminal `pipeline.status`, and the exact `submitted_head`/`current_head`/`relation` ownership facts.
A run whose terminalization verifies that the managed worktree head never changed from the submitted head releases the branch instead: the terminal outcome, including cancellation, ends ownership; status reports `state: user_owned` with the same exact ownership facts and no `next_action`; the branch and head are immediately usable for any separately authorized delivery; and nothing blocks a direct push or PR.
Without positive evidence that the submitted head stayed unchanged, custody is not guessed away. Missing or conflicting evidence, and import cases with a dirty worktree or genuinely divergent history, require manual reconciliation instead of advertising a recovery that will refuse.
While a run is still active, it reports `state: pipeline_owned`, the exact submitted/current heads and their relation, and `next_action.code: continue_active_run` with `no-mistakes axi status`, even when its head has not moved yet.
Expand Down Expand Up @@ -342,9 +342,9 @@ cancels it before starting over. Treat rerun as a between-runs action after a
failed or cancelled outcome, or after you have committed a separate fix outside
an active run; do not use it to bypass a gate.

| Flag | Type | Default | Description |
| ---- | ---- | ------- | ----------- |
| `--intent` | `string` | (none) | Explicit intent overriding inherited intent or fresh inference |
| Flag | Type | Default | Description |
| ---------- | -------- | ------- | -------------------------------------------------------------- |
| `--intent` | `string` | (none) | Explicit intent overriding inherited intent or fresh inference |

## no-mistakes sync

Expand All @@ -358,20 +358,20 @@ no-mistakes sync --recover
no-mistakes sync --recover --keep-local
```

| Flag | Type | Default | Description |
| -------------- | ------ | ------- | --------------------------------------------------------------- |
| `--check` | `bool` | `false` | Verify and print the fresh plan without changing `HEAD` |
| `-y`, `--yes` | `bool` | `false` | Apply an eligible guarded synchronization without an interactive prompt |
| Flag | Type | Default | Description |
| -------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `--check` | `bool` | `false` | Verify and print the fresh plan without changing `HEAD` |
| `-y`, `--yes` | `bool` | `false` | Apply an eligible guarded synchronization without an interactive prompt |
| `--recover` | `bool` | `false` | Return custody of a branch stranded by a terminal run with unpublished pipeline commits (a no-op when cancellation already released the branch) |
| `--keep-local` | `bool` | `false` | With `--recover`: keep the current local head; never touches the worktree |
| `--keep-local` | `bool` | `false` | With `--recover`: keep the current local head; never touches the worktree |

Without `--yes`, apply prints the exact full-SHA plan and requires TTY confirmation; `--recover` prompts the same way before returning custody.
A non-TTY apply or recovery refuses with a direct `--yes` hint.
The command uses the same service and safety contract as `no-mistakes axi sync`, including the guarded equivalent advance and custody recovery documented there; it never stashes, rebases, creates a merge commit, switches branches, deletes a branch, or updates an external remote.

## no-mistakes status

Show repo, daemon, active run, and relevant cached local-branch synchronization status.
Show repo, daemon, active run, and cached local-branch synchronization status.

```sh
no-mistakes status
Expand All @@ -383,6 +383,16 @@ Displays:
- Gate path
- Daemon status (running/stopped, PID)
- Active run details: ID, branch, status, head SHA, start time
- Cached local repository state: branch, short `HEAD`, cleanliness, and the
locally recorded synchronization guidance

The cached local-state line is always present once a repository is registered.
It is local evidence only: its Git inspection does not fetch or query a Git
remote, and it does not claim that the remote branch is currently fresh. That
inspection does not mutate either the invoking repository or local gate Git
object database, refs, index, or worktree; a Git-status failure is labelled as unavailable rather than as
confirmed dirtiness. `status` may still record its normal local command
telemetry separately.

## no-mistakes runs

Expand Down
194 changes: 194 additions & 0 deletions docs/superpowers/plans/2026-08-26-cached-repository-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# Cached Repository State Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Make `no-mistakes status` always render cached, local-only repository branch evidence without adding a pipeline or network operation.

**Architecture:** `internal/cli/status.go` already obtains `branchsync.State` through `InspectCached`. Add one small presenter for that state and render it unconditionally after repository discovery. Include the rendered cached summary in the existing status telemetry fingerprint so sampled status events correspond to visible state.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

**Tech Stack:** Go, Cobra, existing `branchsync.Service`, existing CLI test helpers.

---

## File structure

- Modify: `internal/cli/status.go` - render cached evidence and fingerprint it.
- Create: `internal/cli/status_test.go` - table tests for the pure cached-state presenter and fingerprint coverage.

### Task 1: Write the failing presenter test

**Files:**

- Create: `internal/cli/status_test.go`

- [ ] **Step 1: Define clean, dirty, and unavailable cases**

```go
func TestCachedBranchSummary(t *testing.T) {
tests := []struct { name string; state branchsync.State; want string }{
{"clean branch", branchsync.State{State: branchsync.StateSynchronized, Local: branchsync.LocalState{Branch: "feature/state", Head: "0123456789abcdef", Clean: true}}, "cached: feature/state 01234567 (clean; already synchronized with the pipeline-pushed head)"},
{"dirty branch", branchsync.State{State: branchsync.StateDirty, Local: branchsync.LocalState{Branch: "feature/state", Head: "fedcba9876543210", Reason: "uncommitted changes"}}, "cached: feature/state fedcba98 (dirty: uncommitted changes; dirty)"},
{"unavailable", branchsync.State{State: branchsync.StateAmbiguousContext}, "cached: unavailable (ambiguous context)"},
}
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {
if got := cachedBranchSummary(tt.state); got != tt.want { t.Fatalf("cachedBranchSummary() = %q, want %q", got, tt.want) }
}) }
}
```

- [ ] **Step 2: Verify red**

Run: `go test ./internal/cli -run '^TestCachedBranchSummary$'`

Expected: compile failure because `cachedBranchSummary` does not exist.

### Task 2: Add the local-only presenter and render it

**Files:**

- Modify: `internal/cli/status.go`
- Test: `internal/cli/status_test.go`

- [ ] **Step 1: Implement the presenter**

```go
func cachedBranchSummary(state branchsync.State) string {
summary := humanSyncSummary(state)
if state.Local.Branch == "" || state.Local.Head == "" { return "cached: unavailable (" + summary + ")" }
head := state.Local.Head[:minLen(len(state.Local.Head), 8)]
cleanliness := "clean"
if !state.Local.Clean { cleanliness = "dirty"; if state.Local.Reason != "" { cleanliness += ": " + state.Local.Reason } }
return fmt.Sprintf("cached: %s %s (%s; %s)", state.Local.Branch, head, cleanliness, summary)
}
```

- [ ] **Step 2: Replace the conditional cached-state rendering**

```go
syncState := (&branchsync.Service{DB: d, Repo: repo, WorkDir: "."}).InspectCached(cmd.Context())
cachedSummary := cachedBranchSummary(syncState)
fmt.Fprintf(w, "\n %s %s\n", sDim.Render("local state:"), cachedSummary)
```

- [ ] **Step 3: Verify green**

Run: `go test ./internal/cli -run '^TestCachedBranchSummary$'`

Expected: PASS.

### Task 3: Keep telemetry aligned with rendered evidence

**Files:**

- Modify: `internal/cli/status.go`
- Modify: `internal/cli/status_test.go`

- [ ] **Step 1: Add a failing fingerprint regression test**

```go
func TestStatusFingerprintIncludesCachedSummary(t *testing.T) {
run := &db.Run{ID: "run-1", Branch: "feature/test", Status: "running", HeadSHA: "head-one"}
before := statusFingerprint("repo", "running", run, "cached: main 01234567 (clean; synchronized)")
after := statusFingerprint("repo", "running", run, "cached: main 89abcdef (dirty; dirty)")
if before == after { t.Fatal("changing displayed cached evidence must change the status fingerprint") }
}
```

- [ ] **Step 2: Verify red**

Run: `go test ./internal/cli -run '^TestStatusFingerprintIncludesCachedSummary$'`

Expected: compile failure until `statusFingerprint` accepts the cached summary.

- [ ] **Step 3: Update signature and call site**

```go
fingerprint := statusFingerprint(repo.ID, daemonState, activeRun, cachedSummary)
```

Build the fingerprint from repository id, daemon state, cached summary, and
the existing active-run fields. Update the existing active-run-head test with
the fourth argument.

- [ ] **Step 4: Verify focused tests**

Run: `go test ./internal/cli -run 'Test(CachedBranchSummary|StatusFingerprint)'`

Expected: PASS.

### Task 4: Validate and prepare review

**Files:**

- Modify: `internal/cli/status.go`
- Create: `internal/cli/status_test.go`

- [ ] **Step 1: Add command-level status coverage**

Use `setupTestRepo`, `executeCmd("init")`, and `executeCmd("status")` to
assert the clean and a newly dirty worktree both retain `repo`, `daemon`, and
`no active run` output and always include `local state: cached:`. The clean
case must include `(clean;`; the dirty case must include `(dirty:`.

Run: `go test ./internal/cli -run '^TestStatusAlwaysRendersCachedLocalState$' -count=1`

Expected: PASS.

- [ ] **Step 2: Format and run full gates**

Run: `gofmt -w internal/cli/status.go internal/cli/status_test.go && make lint && go test -race ./... && go build -o ./bin/no-mistakes ./cmd/no-mistakes`

Expected: each command exits 0.

- [ ] **Step 3: Inspect the review diff**

Run: `git diff --check && git diff -- internal/cli/status.go internal/cli/status_test.go`

Expected: no whitespace errors and no source file outside the planned scope.

- [ ] **Step 4: Commit after fresh evidence**

```bash
git add internal/cli/status.go internal/cli/status_test.go
git commit -m "feat(status): show cached repository state"
```

### Task 5: Review-remediation safety proof

**Files:**

- Modify: `internal/cli/status_test.go`
- Modify: `docs/src/content/docs/reference/cli.md`

- [x] **Step 1: Add a command-level cached-only safety regression**

Run `status` after `init` through a test-local Git wrapper that records any
`fetch` or `ls-remote` invocation. Snapshot `.git/FETCH_HEAD`, `.git/index`,
refs, worktree state, and the gate database before and after the command.
Assert that the command renders cached state, makes no remote Git call, and
does not change any snapshot. Also exercise a locally available diverged head
and snapshot `.git/objects`: cached inspection must conservatively report
`blocked_diverged` without constructing a merge tree. Cover the terminal
recovery shape where both divergent heads exist only in the local gate and
snapshot that bare repository's `objects` directory: cached inspection must
defer semantic verification to explicit recovery.

- [x] **Step 2: State the user-facing freshness boundary**

Document that the always-rendered cached local-state line's Git inspection is
local evidence, does not fetch or query a Git remote, does not mutate local
Git state, and does not assert remote freshness. Existing command telemetry
is a separate concern. A Git-status failure must render cleanliness as
unavailable, never as a confirmed dirty worktree.

- [x] **Step 3: Run focused and full verification**

Run:

```bash
gofmt -w internal/cli/status_test.go
go test ./internal/cli -run 'TestStatus' -count=1
make lint && go test -race ./... && go build -o ./bin/no-mistakes ./cmd/no-mistakes && git diff --check
```

Expected: each command exits 0 before updating the PR.
Loading
Loading