<!-- Modulo on-demand di /new — NON un file installato a sé. Caricato via Read dalla § "Routing" del SKILL.md core. -->

> **Modulo `/new`** — eseguilo, poi torna al core § "Routing" per la fase successiva. I `§ "..."` (Context economy, Context Tracking, Trivial-card fast-lane, Risk-signal detector, Fix Application Log) vivono nel **core SKILL.md**.

## Phase 6 — Post-batch merge & cleanup (delegated to worktree-manager skill)

**→ Visibility (batch transition)**: TaskUpdate `Merge & cleanup` → `in_progress` and emit a Progress Bar per § "Progress Visibility". Mark it → `completed` at the end of Phase 6c (merge done, worktree removed, workspace reconciled).

After the final review passes AND all cards are committed in the worktree, delegate the entire merge and cleanup to the **worktree-manager** skill (`/mw` in programmatic mode):

1. **BEFORE invoking /mw** — verify no uncommitted files remain in the worktree:
   ```bash
   cd <worktree-path>
   git status --porcelain
   ```
   If ANY uncommitted files exist (staged, unstaged, or untracked), commit them NOW with `[safety] Auto-commit remaining files before merge`. Do NOT proceed to `/mw` with a dirty worktree — files WILL be lost during rebase.
2. Invoke `/mw` with:
   - The worktree path and branch from the tracker
   - `checksAlreadyPassed: true` (final review + QA already validated the build)
   - All card IDs for the commit message
3. The skill handles: safety commit of any remaining uncommitted files (step 3), rebasing onto the latest trunk (`$TRUNK` = `git.trunk_branch`) (step 4b — auto-resolves doc conflicts, stops on code conflicts), merging into the trunk via the configured `git.merge_strategy` (step 4c — `pr` uses `gh pr merge`, `local-push` does a direct FF push to `origin/$TRUNK`; NEITHER runs `git checkout` of the trunk on the main repo, respecting the absolute terminal-isolation rule), post-merge verification, worktree removal, registry cleanup, and remote branch deletion.
4. **If code merge conflicts** → the skill STOPs and reports. Doc-only conflicts (ssot-registry.md, project-status.md, etc.) are auto-resolved by keeping both sides.
5. **If post-merge build fails** → the skill STOPs and keeps the worktree intact for investigation.
6. Record the merge commit hash and result in the tracker. **Also record the merge timestamp** under `## Worktree Merges` (`merge_ts: <ISO-8601>`): for `local-push` use the local merge-commit time (`git log --format=%ci <hash>`); for `pr` (`gh pr merge`) use the PR merge completion time, or note `merge_ts: pending` if GitHub has not yet finalized the merge. Phase 8's `cycle_time_mins` reads THIS field as the strategy-independent end anchor (never a raw hash that may be the PR HEAD).

### Phase 6b — Backlog Card Status Reconciliation (MANDATORY — ZERO TOLERANCE)

**This gate runs IMMEDIATELY after Phase 6 merge completes, BEFORE presenting any summary to the user. It is NON-SKIPPABLE.**

The most common failure mode is leaving cards IN_PROGRESS after merge. This creates SSOT drift and confuses downstream agents (codebase-architect, doc-reviewer). This gate prevents that.

**Steps:**

1. **Read the tracker file** to get the full list of card IDs in the batch.
2. **For EACH card in the batch**, read its backlog YAML (`${paths.backlog_dir}/<CARD-ID>.yml`) and check the `status` field:
   - If `status: DONE` → OK, skip.
   - If `status` is anything else (`IN_PROGRESS`, `READY`, `TODO`, etc.) → **FORCE UPDATE to DONE immediately**.
3. **Early exit — all cards already DONE**: if step 2 found ALL cards are already `status: DONE`, skip steps 4-5 entirely. Log in the tracker:
   ```
   ## Phase 6b — Status Reconciliation
   Cards checked: N
   Already DONE: N (all cards marked DONE by coder agents)
   Force-updated to DONE: 0
   Reconciliation commit: not needed
   ```
   Proceed directly to step 6.
4. **Force update procedure** for non-DONE cards:
   ```bash
   # In the MAIN repo (not worktree — worktree is already cleaned up)
   cd <main-repo-path>
   ```
   Edit the backlog YAML: set `status: DONE`, add `completed_date: <today>`, add implementation note: `"Marked DONE by post-merge reconciliation gate (Phase 6b)"`.
5. **If ANY card was force-updated**: commit in the main repo with these precautions:
   a. **Clear stale COMMIT_LOCK** (common when coder agents crash or timeout):
      ```bash
      # Remove any stale COMMIT_LOCK from main repo or worktrees
      rm -f <main-repo-path>/.git/COMMIT_LOCK 2>/dev/null
      find <main-repo-path>/.git/worktrees -name COMMIT_LOCK -delete 2>/dev/null || true
      ```
   b. **Stage backlog YAMLs AND related docs** (pre-commit hook requires doc updates):
      ```bash
      # Stage only the force-updated backlog YAMLs
      git add ${paths.backlog_dir}/CARD-001.yml ${paths.backlog_dir}/CARD-002.yml
      # If ssot-registry.md was modified during the batch, stage it too
      # (doc-freshness hook blocks commits that touch ${paths.backlog_dir}/ without ssot-registry.md)
      git diff --name-only ${paths.references_dir}/ssot-registry.md && git add ${paths.references_dir}/ssot-registry.md || true
      ```
   c. **Commit with explicit file list**:
      ```bash
      git commit -m "[BATCH] Mark cards DONE — post-merge reconciliation"
      ```
   d. **If commit fails due to pre-commit hook** (lint-staged, doc-freshness): check the error.
      - If "no staged files" → all changes were already committed. Log as "not needed".
      - If doc-freshness requires ssot-registry.md → update its `last_verified_from_code` date, stage it, and retry.
      - If COMMIT_LOCK → re-run step 5a and retry.
      - Do NOT retry more than 2 times. Log the failure and move on.
5e. **Epic-parent closure (MANDATORY — the epic card is NOT in the batch)**: the batch
   implements children; the epic/parent card (`group.is_epic: true`, id usually ending
   `-00` / filename `*-epic.yml` / `review_profile: skip`) is never implemented and is
   excluded from the per-card loop in step 2 — so it stays at its old status (almost
   always `TODO`) even when its **last** child just went DONE. Close it here, deterministically:

   For each **distinct parent epic** referenced by the batch — the set of `group.parent`
   values across the batch cards, PLUS any card in the batch that is itself an epic
   (`group.is_epic: true`) — do:
   a. Read the epic parent card `${paths.backlog_dir}/<EPIC-ID>.yml`. If `status: DONE`
      already → skip (idempotent).
   b. Check that **every** child is DONE (not just the batch's subset — children
      implemented in earlier sessions count too):
      ```bash
      # Lists children of <EPIC-ID> that are NOT yet DONE. Empty output ⇒ all DONE.
      grep -l "parent: <EPIC-ID>" ${paths.backlog_dir}/*.yml | xargs grep -L "status: DONE"
      ```
   c. **Only if** that command prints nothing (all children DONE) → force-update the epic
      parent: set `status: DONE`, add `completed_date: <today>`, add note
      `"Marked DONE by epic-closure gate (Phase 6b) — all children DONE"`. Stage it and
      **fold into the SAME reconciliation commit** as step 5 (or, if step 5 didn't run
      because all children were already DONE, make a dedicated
      `[BATCH] Close epic <EPIC-ID> — all children DONE` commit, honoring the same
      COMMIT_LOCK / doc-freshness precautions as step 5a–5d).
   d. **If any child is still open** → leave the epic untouched: it is genuinely still in
      progress, and step-4b's "Card rimanenti nell'epic" table already reports the gap.
6. **Update tracker** with reconciliation results:
   ```
   ## Phase 6b — Status Reconciliation
   Cards checked: N
   Already DONE: M
   Force-updated to DONE: K [list card IDs]
   Epics closed: J [list epic IDs] (or "none — children still open" / "n/a — no epic in batch")
   Reconciliation commit: <hash> (or "not needed")
   ```
7. **HALT condition**: if a card cannot be updated (e.g., file read error, YAML parse error), log it in `## Issues & Flags` and continue with the remaining cards. Report the failure in the final summary.

**Why this exists**: Agents frequently skip the DONE marking in Phase 4 (step 27) due to context compaction, commit failures that interrupt the flow, or team mode where Step D.6 gets lost. This gate is the safety net that catches ALL of these cases.

### Phase 6c — Workspace Hygiene Post-merge (BLOCKING — non-skippable)

**Why this exists**: FEAT-0006 incident closed the batch leaving the main repo with an unpushed orphan commit (`c9d41f9`) and local `develop` diverged from `origin/develop` for a manual fix. This phase closes the loop opened by Phase 0: it verifies the main repo is clean and synchronized, parses the `[SYNC-DEFERRED]` marker emitted by `/mw` when HEAD ≠ `$TRUNK`, and restores any stash saved in Phase 0.

**Auto Mode does NOT override this phase.** Every `AskUserQuestion` below is non-bypassable.

**Steps:**

1. **Fetch remote state**:
   ```bash
   git -C "$MAIN" fetch origin --quiet
   ```

2. **Parse `mw-docs` sync markers** — scan the captured stdout from every `/mw` invocation in this batch (kept in the tracker under `## Worktree Merges`):
   - **`[SYNC-DEFERRED] main repo HEAD=<branch>`** (main repo HEAD ≠ `$TRUNK`) — surface via `AskUserQuestion`:
     - Question: `"Una o più merge worktree hanno deferito la sincronizzazione di local $TRUNK (main repo HEAD non era $TRUNK). Come riconcilio adesso?"`
     - Options: `[Ora HEAD è $TRUNK → ff-pull adesso]` / `[Lascia deferred (riconcilio io manualmente)]` / `[Mostra dettagli e fammi decidere]`.
   - **`[SYNC-NEEDS-DECISION] …`** (HEAD was `$TRUNK` but the ff was blocked by a **foreign** uncommitted file, or by a divergence/rebase conflict `/mw` would not auto-commit) — this is NEVER a passive note. Surface via `AskUserQuestion`:
     - Question: `"Il $TRUNK locale non si è sincronizzato: <dettaglio dal marker>. Come chiudo?"`
     - Options: `[Committa tu adesso (descrivi cosa)]` / `[Lo gestisco io — chiudi senza sync locale]` / `[Mostrami il diff e decidiamo]`.
   - **No marker** — `/mw` fast-forwarded (or auto-reconciled the framework telemetry log); nothing to parse.

3. **Clean-tree assertion (BLOCKING — same framework-managed partition as Phase 0 step 3)**:
   ```bash
   POST_DIRTY="$(git -C "$MAIN" status --porcelain \
     | grep -vE "^.{3}\"?${METRICS}/" \
     | grep -vE "^.{3}\"?${METRICS}\"?$" \
     | grep -vE "^.{3}\"?\.baldart/(generated/|state\.json|skill-conflicts\.json)" \
     | grep -v '^[[:space:]]*$')"
   ```
   (Apply the **same** partition as Phase 0 step 3: telemetry under `$METRICS` and BALDART-managed `.baldart/` artifacts — `generated/`, `state.json`, `skill-conflicts.json`, but NOT `overlays/` — are auto-ignored, never surfaced. Otherwise a reinstall mid-batch would block the close on its own output.)
   If `$POST_DIRTY` is non-empty AND no Phase 0 stash is pending → invoke `AskUserQuestion`:
   - Question: `"Main repo ha modifiche non committate dopo il batch (non c'era stash di Phase 0). Cosa faccio?"`
   - Options: `[Mostrami il diff e fammi decidere]` / `[Stash adesso con label baldart-post-batch-<timestamp>]` / `[Lascia così (lo gestisco io)]` / `[Halt]`.

4. **Divergence assertion (BLOCKING — this catches the FEAT-0006 orphan-commit pattern)**. `$MAIN` and `$TRUNK` are read from the tracker (`## Worktree` section); HALT if either is absent/empty.
   ```bash
   read BEHIND AHEAD <<< "$(git -C "$MAIN" rev-list --left-right --count "origin/$TRUNK...$TRUNK")"
   ```
   Compute `$GENUINE_AHEAD` / `$GENUINE_LIST` / `$FW_SKIPPED` with the **exact same reclassification loop as Phase 0 step 4** (framework-management commits — subtree merges into `.framework/`, `baldart update`/`add` chores, `.framework/`-only commits — are auto-ignored, NOT orphan commits). Then branch on `$BEHIND` and `$GENUINE_AHEAD`:
   - **`BEHIND=0` and `GENUINE_AHEAD=0`** — synchronized. If `$FW_SKIPPED > 0`, log `Divergence: $FW_SKIPPED framework-management commits ahead (auto-ignored)` and proceed. No `AskUserQuestion`.
   - **Ahead only (`GENUINE_AHEAD > 0`, genuine unpushed commits)** — invoke `AskUserQuestion`:
     - Question: `"Local $TRUNK ha $GENUINE_AHEAD commit genuini non pushati su origin/$TRUNK al termine del batch (esclusi $FW_SKIPPED di gestione framework). Lista: <git show -s --oneline $GENUINE_LIST>. Cosa faccio?"`
     - Options: `[Push adesso (git push origin $TRUNK)]` / `[Cherry-pick selettivo su origin/$TRUNK]` / `[Reset --hard origin/$TRUNK (richiede conferma esplicita, DISTRUTTIVO)]` / `[Halt con stato preservato]`.
     - On `Reset --hard origin/$TRUNK` re-ask `AskUserQuestion` to confirm: `"Confermi reset --hard? I commit <list> verranno persi se non pushati altrove."` Solo dopo conferma esplicita esegui il reset.
   - **Behind only (`BEHIND > 0`, `GENUINE_AHEAD=0`)** — invoke `AskUserQuestion`:
     - Question: `"Local $TRUNK è behind origin/$TRUNK di $BEHIND commit (qualcuno ha pushato durante il batch). Faccio ff-pull adesso?"`
     - Options: `[Fast-forward pull]` / `[Lascia behind (lo gestisco io)]`.
   - **Diverged both ways (`BEHIND > 0` and `GENUINE_AHEAD > 0`)** — invoke `AskUserQuestion`:
     - Question: `"Local $TRUNK è diverged ($BEHIND behind, $GENUINE_AHEAD genuine ahead). Servono entrambe le riconciliazioni. Come procedo?"`
     - Options: `[Rebase local $TRUNK su origin/$TRUNK]` / `[Mostrami i commit locali e decidiamo]` / `[Halt]`.

5. **Restore Phase 0 stash (if any)** — read the tracker for `## Workspace Snapshot: <message-label>`:
   - If the snapshot is `dirty-tree override` → skip restore, log "user retained dirty-tree responsibility".
   - If it is a stash MESSAGE label (e.g. `baldart-new-phase0-<FIRST-CARD-ID>-<timestamp>`) — resolve the stash by its message, NEVER by positional `stash@{N}` (other stashes may have shifted the index since Phase 0). This runs on `$MAIN` (the main checkout, not a worktree), so a stash op here is safe:
     ```bash
     REF="$(git -C "$MAIN" stash list | grep -F "<message-label>" | head -1 | cut -d: -f1)"
     [ -n "$REF" ] && git -C "$MAIN" stash pop "$REF" \
       || echo "stash '<message-label>' not found — was it already restored?"
     ```
     On conflict, do NOT silent-drop — invoke `AskUserQuestion`:
     - Question: `"Restore dello stash di Phase 0 ha generato conflitti. Lo stash è ancora presente (NON eliminato). Come procedo?"`
     - Options: `[Lascia lo stash + apri istruzioni per merge manuale]` / `[Mostrami il conflitto inline]` / `[Halt]`.

6. **Log and exit**:
   ```
   ## Phase 6c — Workspace Hygiene Post-merge
   Status: PASS
   Clean-tree: <yes | resolved: stashed | user-retained>
   Divergence (local…origin/$TRUNK): <0\t0 | resolved: pushed/cherry-picked/ff-pulled/rebased>
   Sync-deferred markers: <none | reconciled | user-retained>
   Phase 0 snapshot restore: <n/a | popped clean | conflict-deferred-to-user>
   Completed: <timestamp>
   ```
   If any step ended in HALT, set `Status: HALT` and report — Phase 7 must NOT start with an unclean main repo unless the user explicitly chose `[Lascia così]`.

7. **Anti-bypass guard** — like Phase 0, before exiting Phase 6c re-read the tracker section you just wrote. Missing `## Phase 6c` → refuse to proceed to Phase 7. There is no `--skip-phase-6c` flag; treat any urge to skip as a self-correction trigger.

### Fail-safe rules (enforced by worktree-manager skill)
- **Never `git checkout`, `git switch`, `git checkout -b`, or `git branch` on the main repo** from inside this orchestrator. The main repo is shared across parallel terminals. Use the configured `git.merge_strategy` for trunk merges and `git -C <main> pull --ff-only` (only when `HEAD = $TRUNK` already) for sync. See `/mw` step 4c.
- Never merge into a release/production branch — only into the integration trunk (`$TRUNK` = `git.trunk_branch`), via the configured `git.merge_strategy` (NOT local checkout).
- Never force push to the trunk. `--force-with-lease` on feature branches after rebase is allowed.
- Never delete a branch before successful merge verification.
- Never remove a worktree before confirming the trunk is stable post-merge.
- Stop execution immediately if any command fails.

---
