---
phase: quick-260527-s8l
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/owl/echo_commune.rs
  - src/common/tracked.rs
autonomous: true
requirements:
  - "S8L-01: p-* project branches must receive commits and propagate to origin"
  - "S8L-02: ensure_worktree must self-recover from pre-populated non-empty target dirs"

must_haves:
  truths:
    - "Sending a commune with <project-context>...</project-context> from an agent inside a project results in a NEW commit landing on p-{project_name} in the seed bare repo."
    - "After such a commit, psyches/tracked/projects/{project_name}/.git EXISTS (file, worktree marker) — the project worktree is materialized."
    - "ensure_project_worktree({name}) succeeds even when projects/{name}/ already contains a stray .md file (no .git), by salvaging contents aside, materializing the worktree, then restoring the salvaged files."
    - "All three project-write call sites in src/owl/echo_commune.rs call ensure_project_worktree BEFORE fs::write."
    - "cargo build --release succeeds; cargo test passes including two new regression tests."
  artifacts:
    - path: "src/owl/echo_commune.rs"
      provides: "Reordered project-slot writes (ensure → write → commit) at sites ~501, ~588, ~792"
      contains: "ensure_project_worktree"
    - path: "src/common/tracked.rs"
      provides: "ensure_worktree salvage path: on `already exists` + missing .git → mv aside → retry → restore"
      contains: "salvage"
  key_links:
    - from: "src/owl/echo_commune.rs::route_two_slice (line ~501)"
      to: "tracked::ensure_project_worktree"
      via: "direct call BEFORE fs::create_dir_all + fs::write"
      pattern: "ensure_project_worktree\\(.*\\)\\s*[\\s\\S]{0,200}?fs::write"
    - from: "src/owl/echo_commune.rs::route_two_slice_with_precedence (line ~588)"
      to: "tracked::ensure_project_worktree"
      via: "direct call BEFORE write_with_precedence"
      pattern: "ensure_project_worktree\\(.*\\)\\s*[\\s\\S]{0,300}?write_with_precedence"
    - from: "src/owl/echo_commune.rs::route_project_slot (line ~792)"
      to: "tracked::ensure_project_worktree"
      via: "direct call BEFORE fs::write (replaces project_worktree_path + create_dir_all)"
      pattern: "ensure_project_worktree\\(project_name\\)"
    - from: "src/common/tracked.rs::ensure_worktree fallback branch (~448-468)"
      to: "salvage helper"
      via: "second-failure recovery when target dir is non-empty + lacks .git"
      pattern: "salvage"
---

<objective>
Fix the silent-failure bug where `p-{project_name}` branches of `spt-agent-storage` never receive commits because `route_two_slice` (and its peers in `src/owl/echo_commune.rs`) write the project `.md` file BEFORE calling `ensure_project_worktree`. The pre-populated directory causes `git worktree add` to fail with `'...' already exists`, both primary and fallback attempts fail, and the error is swallowed with a `(payload on disk)` warning. Result: no `.git` marker is ever created in `psyches/tracked/projects/{name}/`, no commit fires, and `p-{project_name}` stays pinned at the seed-init SHA forever.

Purpose: Cross-machine project-context sync (the entire reason `spt-agent-storage` exists for projects) has never worked. This is the highest-severity sync bug in v1.8.

Output:
- `src/owl/echo_commune.rs` — three production project-write call sites reordered so `ensure_project_worktree` runs FIRST (materializing `.git`), then `fs::write` (now writing into a real worktree), then `commit_project_payload`.
- `src/common/tracked.rs::ensure_worktree` — defense-in-depth salvage path that recovers existing broken installs without manual operator action.
- Two new regression tests (one in `echo_commune.rs`, one in `tracked.rs`) that lock in the ordering invariant and the salvage behavior.
- Atomic commits per concern with the trailer `Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>`.

Note: This plan does NOT auto-repair the user's broken local install (`psyches/tracked/projects/claude_skill_owl/` is currently non-worktree). Per scope constraint, the user does that manually after this fix lands.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@CLAUDE.md
@.planning/STATE.md
@.planning/debug/p-branches-not-pushed-origin.md

<interfaces>
<!-- Key signatures the executor needs. Already verified by the planner — do not re-explore. -->

From src/common/tracked.rs:
```rust
pub fn ensure_project_worktree(project_name: &str) -> Result<PathBuf, TrackedError>
pub fn commit_project_payload(project_name: &str, files: &[&str], subject: &str) -> Result<(), TrackedError>
pub fn compose_commit_subject(kind: &str, self_id: &str, slot: &str, short: &str) -> String
fn ensure_worktree(scope: EnsureScope, name: &str) -> Result<PathBuf, TrackedError>
// EnsureScope is an enum with Project / Agent variants; `scope.worktree_path(name)` returns the target dir.

pub enum TrackedError {
    InvalidId(String),
    Io(std::io::Error),
    GitFailed(String),
    WorktreeFailed(String),
    // ... others
}
```

From src/common/owlery.rs:
```rust
pub fn project_worktree_path(name: &str) -> PathBuf  // returns $SPT_HOME/psyches/tracked/projects/{name}
pub fn resolve_self_project_name_via_info_cwd(self_id: &str) -> Option<String>
pub fn derive_current_repo_names() -> Vec<String>
```

From src/owl/echo_commune.rs (the three production sites to reorder):
- `route_two_slice` (around line 494-532): project slot uses `owlery::project_worktree_path` + `fs::create_dir_all` + `fs::write` + `tracked::commit_project_payload`. NO `ensure_project_worktree` call. BUG.
- `route_two_slice_with_precedence` (around line 581-616): same structure, but write goes through `write_with_precedence`. NO `ensure_project_worktree` call. BUG.
- `route_project_slot` (around line 773-822): same structure (called from the live-slot error branch). NO `ensure_project_worktree` call. BUG.

Verified NON-bugs (do NOT edit):
- `src/live/signoff.rs` grep hits at lines 789, 824, 863, 904, 970 — all inside `#[cfg(test)]` / test helpers. Production signoff project-writes delegate to `route_two_slice_with_precedence` in echo_commune.rs (line 85), so they inherit the fix automatically.
- `src/live/context.rs` grep hits at lines 470, 2338, 2401, 2517 — line 470 is a READ path (`download_payload` inject); 2338/2401/2517 are test helpers. NO production write site exists in context.rs.

Result: production fix surface is THREE sites in echo_commune.rs only. signoff.rs and context.rs production code inherits the fix transitively. (Plan #files_modified reflects this.)
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Reorder all three project-write call sites in echo_commune.rs (ensure_project_worktree FIRST, then write, then commit) + add regression test</name>
  <files>src/owl/echo_commune.rs</files>
  <behavior>
    Regression test (add to existing `#[cfg(test)] mod tests` in echo_commune.rs, near the existing route_two_slice_* tests around line 763+ in signoff.rs's pattern — but co-located in echo_commune.rs):

    - Test name: `route_two_slice_project_worktree_materializes_dotgit_marker`
    - Setup: tempdir under `SPT_HOME`, mint a unique agent id, call `seed_perch_info_with_cwd(&id)` (or equivalent helper — if absent in echo_commune.rs test scope, replicate the minimal pattern used in signoff.rs tests: seed perch info.json so `resolve_self_project_name_via_info_cwd` returns Some) so `resolve_self_project_name_via_info_cwd` returns the cwd's project name.
    - Action: call `route_two_slice(&id, "<project-context>PROJECT BODY</project-context>", "commune", "short")` (or `route_two_slice_with_precedence` for the second variant).
    - Assert 1 (THE BUG GATE): `owlery::project_worktree_path(&project_name).join(".git").exists()` — proves the worktree was materialized. Under the OLD code this assertion fails because the dir contains the .md but no .git.
    - Assert 2: `owlery::project_worktree_path(&project_name).join(format!("{}.md", id)).exists()` — proves the body landed.
    - Assert 3: a commit exists on `p-{project_name}` (call `git -C {seed} log --oneline p-{project_name}` via `crate::common::git::run_git_checked` or equivalent, parse stdout, assert it has >=2 lines — the seed init + the new commit). This is the user-visible behavior the bug breaks.
    - Cleanup: restore prior `SPT_HOME`.
    - Skip-guard: `if !git_available() { return; }` consistent with sibling tests.
    - Mirror the test for `route_two_slice_with_precedence` if doing so doesn't double the test bulk; if it does, keep ONE test against `route_two_slice` since all three sites share the same reorder pattern. Acceptable.
  </behavior>
  <action>
First write the regression test described in `<behavior>` and confirm it FAILS against the unmodified production code (RED). Then apply the reorder at all three production sites:

**Site 1 — `route_two_slice` project slot (around lines 494-532):**

Replace the body of the `Some(project_name) => { ... }` match arm. Current shape:
```
let proj_dir = owlery::project_worktree_path(&project_name);
if let Err(e) = std::fs::create_dir_all(&proj_dir) { ... IoError ... }
else {
    let proj_file_name = format!("{}.md", self_id);
    let proj_path = proj_dir.join(&proj_file_name);
    match std::fs::write(&proj_path, body) { Ok(_) => { ... commit_project_payload ... Written } Err(e) => IoError }
}
```

New shape (CALL ensure_project_worktree FIRST; the function returns the worktree PathBuf, which replaces `proj_dir`):
- Call `tracked::ensure_project_worktree(&project_name)`.
- On `Err(e)`: return `SliceWriteState::IoError(format!("ensure_project_worktree: {}", e))`.
- On `Ok(proj_dir)`: drop the now-redundant `std::fs::create_dir_all` (ensure_project_worktree creates the dir as part of `git worktree add`). Proceed with `fs::write` → `compose_commit_subject` → `commit_project_payload` → `SliceWriteState::Written`. Preserve existing `Err` branch for `fs::write` returning `SliceWriteState::IoError`.

**Site 2 — `route_two_slice_with_precedence` project slot (around lines 581-616):**

Same reorder. Current shape uses `write_with_precedence(...)` instead of `fs::write`. The reorder is identical: replace the `project_worktree_path` + `create_dir_all` lead-in with `tracked::ensure_project_worktree(&project_name)`. On `Err`, return `SliceWriteState::IoError(format!("ensure_project_worktree: {}", e))`. On `Ok(proj_dir)`, proceed with the existing `write_with_precedence` → commit path. The `WriteOutcome::Suppressed` and `WriteOutcome::IoError` branches stay unchanged.

**Site 3 — `route_project_slot` (around lines 773-822):**

This function returns `Result<(), String>`, not `SliceWriteState`. Replace lines that compute `proj_dir = owlery::project_worktree_path(project_name)` followed by `std::fs::create_dir_all(&proj_dir)`. New lead-in:
```
let proj_dir = match tracked::ensure_project_worktree(project_name) {
    Ok(p) => p,
    Err(e) => {
        eprintln!(
            "route_two_slice: ensure_project_worktree({}) failed: {} (skipping project slot)",
            project_name, e
        );
        return Ok(());
    }
};
```
Then continue with the existing `proj_file_name` + `fs::write` + `commit_project_payload` block unchanged. The eprintln preserves the existing soft-fail posture (PROJECT.md commit-posture: payload landing is contractual, git failures never block delivery).

**Across all three sites:** when ensure succeeds the call to `fs::create_dir_all` becomes redundant — REMOVE it (the worktree add already created the directory). Do not keep dead code.

Run `cargo build --release` after edits — must compile clean. Then re-run the regression test (GREEN). Commit atomically.

Verification commands:
- `cargo test --lib echo_commune` (broader: catches all existing route_two_slice tests too — must all still pass)
- `cargo build --release` (re-confirm release build is clean; the deploy path is gated on this)

Commit message:
```
fix(s8l): reorder ensure_project_worktree before fs::write in echo_commune

Fixes the silent-failure bug where p-{project_name} branches never received
commits. route_two_slice / route_two_slice_with_precedence / route_project_slot
all wrote the project .md file BEFORE calling ensure_project_worktree, which
pre-populated the target dir and caused `git worktree add` to fail with
`'...' already exists`. The error was swallowed; no .git marker was ever
created; no commit fired.

Reorder: ensure_project_worktree FIRST (materializes .git) → fs::write
(into a real worktree) → commit_project_payload.

Regression test asserts {wt}/.git exists AND a commit lands on p-{name}
after one route_two_slice call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
```
  </action>
  <verify>
    <automated>cargo test --lib echo_commune -- --nocapture 2>&amp;1 | grep -E "(route_two_slice_project_worktree_materializes_dotgit_marker|test result)" &amp;&amp; cargo build --release 2>&amp;1 | tail -5</automated>
  </verify>
  <done>
    - Regression test `route_two_slice_project_worktree_materializes_dotgit_marker` exists, passes, and demonstrably FAILS when reverted to old code (the executor SHOULD `git stash` the production changes once and re-run the test to confirm RED, then `git stash pop` and confirm GREEN).
    - All three production sites in echo_commune.rs call `tracked::ensure_project_worktree` before `fs::write` / `write_with_precedence`.
    - Redundant `fs::create_dir_all` calls at the three sites are removed.
    - `cargo build --release` exits 0.
    - `cargo test --lib echo_commune` shows all tests passing (including pre-existing route_two_slice_* tests).
    - One atomic commit landed with the message above.
  </done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: Add salvage path to tracked::ensure_worktree for non-empty target dir (defense-in-depth) + regression test</name>
  <files>src/common/tracked.rs</files>
  <behavior>
    Regression test (add to the existing `#[cfg(test)] mod tests` in tracked.rs, near `ensure_project_worktree_creates_p_prefix_branch` around line 2138):

    - Test name: `ensure_project_worktree_salvages_pre_populated_dir`
    - Setup: tempdir, `SPT_HOME` guard, call `ensure_seed()` (or rely on `ensure_project_worktree`'s internal bootstrap).
    - Pre-populate the target: `let target = owlery::project_worktree_path("salvagetest"); std::fs::create_dir_all(&target).unwrap(); std::fs::write(target.join("stray.md"), "STRAY CONTENT MARKER").unwrap();`
    - Action: `let wt = ensure_project_worktree("salvagetest").expect("must salvage")`.
    - Assert 1: `wt.join(".git").exists()` — worktree materialized.
    - Assert 2: `wt.join("stray.md").exists()` — salvaged file restored.
    - Assert 3: `std::fs::read_to_string(wt.join("stray.md")).unwrap().contains("STRAY CONTENT MARKER")` — content preserved.
    - Assert 4 (CRITICAL): NO `salvage-*` sibling directory left under `psyches/tracked/projects/` after restore — clean rollback. (Iterate `read_dir` on the projects root; the only entry should be `salvagetest`.)
    - Skip-guard: `if !git_available() { return; }`.
  </behavior>
  <action>
First write the regression test (RED — fails because `ensure_worktree` currently returns `WorktreeFailed`).

Then add the salvage path inside `ensure_worktree` in `src/common/tracked.rs` (function starts at line 335). The salvage logic activates ONLY when BOTH the primary and fallback `worktree add` fail with `already exists` AND the target dir lacks `.git`. Insert in the `branch_exists || path_in_use` fallback branch at lines ~448-468.

New control flow inside the existing `if branch_exists || path_in_use { ... }` block:

1. Re-prune (already present, keep).
2. Attempt second `worktree add ../{scope}/{name} {branch}` (already present, keep — this still tries the no-`-b` form first because that handles the existing-branch case without touching the dir).
3. If second succeeds → `Ok(wt)` (already present, keep).
4. **NEW:** If second fails with `Nonzero { stderr }` AND `stderr.to_lowercase().contains("already exists")` AND `wt.exists()` AND `!wt.join(".git").exists()` (target dir non-empty, NOT a worktree):
   a. Compute `salvage_path = wt.parent().unwrap().join(format!("{}.salvage-{}", name, chrono::Local::now().format("%Y%m%dT%H%M%S")))`. Use `chrono::Local` already imported in this module (verify with grep; if absent, import it at file scope).
   b. `std::fs::rename(&wt, &salvage_path).map_err(|e| TrackedError::Io(e))?` — move existing contents aside.
   c. Re-run `git worktree add ../{scope}/{name} -b {branch} main` (third attempt — now against an empty target). Use the same `git::run_git_checked` call shape as the first attempt. On failure, attempt the no-`-b` fallback (`worktree add ../{scope}/{name} {branch}`) since the branch may already exist from the seed-init.
   d. On either third-pass success: walk `salvage_path` with `std::fs::read_dir` and `std::fs::rename` each entry back into `wt` (do NOT use a recursive copy — entries are top-level files in this scenario; restoring directory trees is out of scope, so `read_dir` + `rename` is sufficient). After all entries restored, `std::fs::remove_dir(&salvage_path)` (must be empty after the rename loop).
   e. On any third-pass failure: attempt rollback — `std::fs::rename(&salvage_path, &wt)` to restore the original state; return `Err(TrackedError::WorktreeFailed(format!("salvage failed: {}", stderr)))`.
   f. Emit a single stderr warning: `eprintln!("tracked: salvaged pre-populated {} (moved aside, materialized worktree, restored {} entries)", wt.display(), n_restored);` — soft-fail posture per PROJECT.md.
5. Else (second fail did NOT match the salvage precondition) → return the existing `Err(TrackedError::WorktreeFailed(stderr))`.

The salvage path is SCOPED: it activates only for the exact failure mode the s8l bug created. It does NOT activate when:
- The target dir already has `.git` (fast-path returned earlier at line 364-407).
- The second attempt succeeds (legitimate branch-already-exists case, fallback handles it).
- The second attempt fails for any other reason (e.g., permission errors, disk full).

Run `cargo build --release` after edits — must compile. Re-run the regression test (GREEN). Also re-run the full `cargo test` to confirm no regression in the existing 30+ tracked.rs tests.

Commit message:
```
fix(s8l): add salvage path to ensure_worktree for pre-populated targets

Defense-in-depth: when both primary and fallback `git worktree add`
fail with `already exists` AND the target dir lacks .git, move the
existing contents aside (`.salvage-{ts}`), retry `worktree add` against
the now-empty target, then restore the salvaged top-level entries.

Recovers existing broken installs (created by the prior bug) without
operator action. The reorder in Task 1 prevents new occurrences; this
salvage path repairs the in-the-wild damage on next access.

Regression test asserts a pre-populated dir with stray.md survives:
.git materializes, stray.md is restored with content intact, no
salvage-* sibling is left behind.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
```
  </action>
  <verify>
    <automated>cargo test --lib tracked -- --nocapture 2>&amp;1 | grep -E "(ensure_project_worktree_salvages_pre_populated_dir|test result)" &amp;&amp; cargo build --release 2>&amp;1 | tail -5</automated>
  </verify>
  <done>
    - Regression test `ensure_project_worktree_salvages_pre_populated_dir` exists, passes, demonstrably FAILED against the unmodified code (executor confirms via one `git stash` cycle).
    - `ensure_worktree` in `src/common/tracked.rs` has a new salvage branch INSIDE the existing `branch_exists || path_in_use` arm, gated on the second-attempt failure + missing `.git` + non-empty `wt`.
    - On salvage success: `.git` exists in `wt`, salvaged files are restored, no `*.salvage-*` sibling remains.
    - On salvage failure: original state is rolled back via `rename` reverse.
    - All pre-existing `tracked.rs` tests still pass: `cargo test --lib tracked` exits 0.
    - `cargo build --release` exits 0.
    - One atomic commit landed with the message above.
  </done>
</task>

<task type="auto">
  <name>Task 3: Final integration check + STATE.md update</name>
  <files>.planning/STATE.md, .planning/quick/260527-s8l-fix-p-project-branches-never-receiving-c/260527-s8l-SUMMARY.md</files>
  <action>
Run the full test suite and release build one final time to confirm Tasks 1 and 2 compose cleanly:

1. `cargo build --release` — must exit 0.
2. `cargo test` — full suite, must exit 0. The two new regression tests from Tasks 1 and 2 plus all pre-existing tests pass.
3. `cargo test --lib echo_commune route_two_slice` — focused re-run to confirm no test was silently skipped.
4. `cargo test --lib tracked ensure_project_worktree` — focused re-run for the same reason.

If ANY of the above fails, do NOT proceed — return to the failing task and fix forward. Do not paper over with `#[ignore]`.

After all green, append to `.planning/STATE.md` under the `Recent quick tasks` area (or wherever recent activity is logged — search the file for `260527-6ah` precedent and add a sibling entry immediately after it):

```
Last activity: 2026-05-28 - Completed quick task 260527-s8l: fix p-* project branches never receiving commits — reordered ensure_project_worktree before fs::write at 3 echo_commune.rs sites + added ensure_worktree salvage path for pre-populated dirs in tracked.rs (2 new regression tests).
```

Update the `last_updated` and `last_activity` frontmatter fields at the top of STATE.md accordingly.

Write `.planning/quick/260527-s8l-fix-p-project-branches-never-receiving-c/260527-s8l-SUMMARY.md` using the template from `~/.claude/get-shit-done/templates/summary.md`. Capture:
- Root cause (one paragraph — quote from `.planning/debug/p-branches-not-pushed-origin.md` Resolution section).
- Fix surface: 3 sites in echo_commune.rs + 1 salvage path in tracked.rs + 2 regression tests.
- Commits: SHA + subject for Task 1's commit, Task 2's commit, and this final docs commit.
- Out-of-scope reminder: user repairs local broken install manually (do not auto-repair) per planner constraint.
- Deploy: NOT triggered. User runs `docs/DEPLOY.ps1` after verifying locally.

Commit the docs:
```
docs(s8l): summarize p-branch commit fix + update STATE

Quick task 260527-s8l complete: 3 echo_commune.rs sites reordered,
salvage path added to ensure_worktree, 2 regression tests landed.
User runs manual repair on local broken install + DEPLOY.ps1 next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
```
  </action>
  <verify>
    <automated>cargo build --release 2>&amp;1 | tail -3 &amp;&amp; cargo test 2>&amp;1 | tail -10 &amp;&amp; test -f .planning/quick/260527-s8l-fix-p-project-branches-never-receiving-c/260527-s8l-SUMMARY.md</automated>
  </verify>
  <done>
    - `cargo build --release` and `cargo test` both exit 0.
    - `.planning/STATE.md` reflects 260527-s8l as the most recent activity with a one-line summary describing both fixes.
    - `.planning/quick/260527-s8l-fix-p-project-branches-never-receiving-c/260527-s8l-SUMMARY.md` exists and documents: root cause, fix surface, commit SHAs, manual-repair reminder, deploy reminder.
    - Three atomic commits total on the branch (Task 1, Task 2, Task 3 docs), all carrying the Co-Authored-By trailer.
  </done>
</task>

</tasks>

<verification>
Phase-level sanity:

1. `git log --oneline -5` shows three new commits in order: Task 1 reorder, Task 2 salvage, Task 3 docs/STATE. Each carries the `Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>` trailer.
2. `cargo build --release` exits 0.
3. `cargo test` exits 0. The two new regression tests are visible in the output:
   - `echo_commune::tests::route_two_slice_project_worktree_materializes_dotgit_marker ... ok`
   - `tracked::tests::ensure_project_worktree_salvages_pre_populated_dir ... ok`
4. `grep -n "ensure_project_worktree" src/owl/echo_commune.rs` returns at least 3 production call sites (lines ~501 area, ~588 area, ~792 area) — NOT just test code.
5. `grep -n "salvage" src/common/tracked.rs` returns the new salvage logic inside `ensure_worktree`.
6. NO new files outside the planned set were created or modified. Specifically: `src/live/signoff.rs` and `src/live/context.rs` are UNCHANGED (their production code inherits the fix transitively via `route_two_slice_with_precedence`).
7. The user's local broken install is NOT auto-repaired. The SUMMARY explicitly notes the manual recovery step (the user runs it after verifying the fix locally).
</verification>

<success_criteria>
- p-* project branches CAN now receive commits: a fresh project commune triggered after the fix produces a new commit on `p-{project_name}` in `psyches/tracked/seed` and (on next `sync_after_commit`) pushes it to origin.
- The bug pattern is structurally prevented at all three production write sites in `echo_commune.rs`.
- Existing broken installs (where `psyches/tracked/projects/{name}/` exists without `.git`) self-heal on the next `ensure_project_worktree` call via the salvage path.
- Two regression tests lock in both invariants and would fail-loudly on regression.
- `cargo build --release` + `cargo test` both pass.
- Three atomic commits on `main` with the required trailer.
- STATE.md and SUMMARY.md document the change for milestone-close audit.
</success_criteria>

<output>
Create `.planning/quick/260527-s8l-fix-p-project-branches-never-receiving-c/260527-s8l-SUMMARY.md` when done (Task 3).
</output>
