# Validation & Commit Phase (Steps 6-7)

## Step 6 — Quality Audit

**Precondition:** Backlog cards have been generated by `backlog-phase.md` and the user has not explicitly asked to abort. (There is no separate approval gate in the `/prd` flow; execution proceeds automatically from Step 5.)

Mark task 5 as `in_progress`.

0. **Agent Specialization Audit (runs FIRST, blocks if any failure)** — for each generated child card (epics are skipped):

   a. **`owner_agent` enum check (BLOCKING)** — read the `owner_agent` field. It MUST equal one of:
      ```
      coder | ui-expert | plan | visual-designer | motion-expert
      ```
      Empty string, missing field, `claude`, or any other value = **BLOCKER**.
      Message to the user: `"Card <ID> has owner_agent: '<value>' — must be one of {coder, ui-expert, plan, visual-designer, motion-expert}. Fix the card YAML before commit."`
      Halt the audit. No subsequent checks run on a card that fails this gate.

   b. **UI-only scope heuristic (WARN — soft gate, not blocking)** — for each card with `owner_agent: ui-expert`:
      - Concatenate `scope.summary`, `requirements[]`, and `acceptance_criteria[]` into one lowercase string.
      - Grep for logic-marker substrings: `api call`, `endpoint`, `route handler`, `state machine`, `validation logic`, `business rule`, `database`, `migration`, and datastore-access patterns per `stack.database` (e.g. `prisma`, `.from(` / SQL client, `firestore.collection`, `db.collection`), `auth flow`, `token storage`, `webhook`, `cron`.
      - For each marker hit, log a WARN line in the audit output:
        `"[UI-SCOPE-WARN] Card <ID> (ui-expert) mentions '<marker>' — review whether logic belongs in a sibling coder card. See backlog-phase.md § Rule B."`
      - The check is intentionally a soft gate: substrings like "API contract per il mock" or "consumes the auth state from `useAuth()`" are legitimate UI references. The WARN exists to prompt human review, not to block.
      - Exception (no WARN): the marker appears INSIDE a quoted string that explicitly says "consume", "mock", "contract reference", or "exposed by sibling card" — those are UI cards reading an upstream contract, which is expected. **[SEMANTIC — not purely deterministic]** Determining whether a marker substring is (a) inside a YAML quoted string and (b) that string contains one of the escape phrases requires contextual interpretation across multiple YAML quoting styles (single-quote, double-quote, block scalar). FP posture: when in doubt, log the WARN and let the human decide — do not suppress a WARN based on uncertain quoting analysis.

   c. **`review_profile` enum check (BLOCKING)** — read the `review_profile` field on each child card. It MUST equal one of:
      ```
      skip | light | balanced | deep
      ```
      - **Missing or empty** = **BLOCKER** — the card-writer must compute it via `prd-card-writer.md § Rule C` (the SSOT mapping table). Message: `"Card <ID> has no review_profile — compute it from Rule C (skip|light|balanced|deep) before commit."`
      - **Out-of-enum value** = **BLOCKER**. Message: `"Card <ID> has review_profile: '<value>' — must be one of {skip, light, balanced, deep}."`
      - **Consistency WARN (soft)**: a `feature`/`enhancement` card with `review_profile: light` may violate Rule C. Log `"[REVIEW-PROFILE-WARN] Card <ID> is feature/enhancement but review_profile: light — verify against Rule C in prd-card-writer.md."` **Do NOT auto-bump the field** — `review_profile` is computed by `prd-card-writer` (Rule C); see `prd-card-writer.md` for the criteria. This is a soft gate: log the warning and surface it for human review.
      - **Epics**: `review_profile` MUST be `skip` (trackers have no code work). Any other value = WARN, normalize to `skip`.

   **Gate**: every card has `owner_agent` AND `review_profile` in their enums. WARN entries from checks (b)/(c) are noted in the audit log; user may accept them after review.

1. **Field Name Audit (runs after item 0 — only when item 0 passes for a given card; skipped for cards blocked in item 0)** — for each generated card:

   a. Verify every `data_fields` entry has `ts_verified: true` OR `status: new` with `schema_ref`.
   b. For cards with `estimated_complexity: HIGH` or `MEDIUM`:
      - For each `status: existing` field: run `grep -n "fieldName" src/types/<domain>.ts`
        and verify ≥1 match. 0 matches = **BLOCKER** — correct or mark as `🆕 new`.
      - For each `status: new` field: PRD `## Schema Verification` must have complete
        definition (type, nullable, default, indexing notes).
   c. For cards with `estimated_complexity: LOW`:
      - Verify only that `data_fields` has `ts_verified: true` or `status: new`. No grep needed.
   d. If card has `db_indexes` (or legacy `firestore_indexes`) but NO `data_fields`: log warning —
      "card touches the persistence layer (db_indexes present) but missing data_fields block."

   **Gate**: no field with `status: existing | modified` and `ts_verified: false` can proceed.

2. Read the audit phase reference: [audit-phase.md](audit-phase.md)
   Then execute the audit protocol described within, using the card IDs from Step 5.
   The protocol auto-detects which agents to include (security and performance
   are signal-triggered, no manual profile selection needed).
   **I/O contract:** `audit-phase.md` may edit card YAML files (e.g. its Step 6.9 normalisation pass). The list of all card paths modified by the audit (including those edited in Step 6.9) is the complete set that Step 7 item 6 must stage. The glob `FEAT-XXXX-*.yml (all cards from Step 5)` in item 6 covers this; note it explicitly so that context compaction does not cause a model to stage only the pre-audit originals.
3. Wait for audit report and card edits.

**Gate:** all HIGH/MEDIUM findings resolved, no open `[MANUAL]` items that require external human input beyond this orchestrator. (Items that the orchestrator can resolve autonomously are resolved before this gate; items that need a human decision were already routed to `AskUserQuestion` inside `audit-phase.md`. Items that survive to Step 7 item 1 are those that received a human answer but required card re-edits — these are explicitly handled there.)

## Step 7 — Resolution, Commit & Merge

**Precondition:** Step 6 complete; the session is running inside a docs worktree
created by Step 1 (HARD RULE 17). `$WORKTREE_PATH` is set in the state file.

**Variable guard (R6).** Before executing any item in Step 7, resolve and verify:
- `$WORKTREE_PATH` — read from the state file `## Worktree / path:`. HALT with `"ABORT: WORKTREE_PATH not set in state file — cannot commit or merge."` if absent or empty.
- `$MAIN` — the absolute path of the main (non-worktree) git repository root. **READ the persisted value from the state file `## Worktree / main_path:`** (written at SKILL.md Step 1, the R6 persist-then-read pattern `/new` uses). HALT with `"ABORT: MAIN absent from state — re-run from a session whose Step 1 persisted main_path."` if the field is absent or empty. Only when the field is genuinely missing (legacy state file) FALL BACK to deriving it once via `git -C "$WORKTREE_PATH" rev-parse --git-common-dir` (strips `/.git`). Every subsequent `git` command that runs outside the worktree MUST use `git -C "$MAIN" …`; never use a bare `git` command whose cwd is undefined. (This is the same variable name and resolution contract `/new` uses — the two skills must agree.)

### Resolve remaining items

1. If `[MANUAL]` items remain: resolve each one. (These are items that the orchestrator could not auto-resolve in Step 6 and whose human decision was provided via `AskUserQuestion` — they survived the Step 6 gate legally. See gate text above.)
2. If resolution required card re-edits: re-run the audit protocol (audit-phase.md) on affected cards.
   **Re-audit iteration cap:** maximum 2 re-audit iterations. If HIGH/MEDIUM findings or open `[MANUAL]` items persist after iteration 2, raise ONE `AskUserQuestion`:
   - Question: `"After 2 re-audit rounds, <N> finding(s) remain unresolved. Options: [Accept remaining findings and proceed to commit] / [Abort this PRD session] / [Show me the findings and decide]"`
   Act on the answer; do not continue looping silently.

### Update knowledge base (inside the worktree)

3. Add/update entry in `$WORKTREE_PATH/${paths.references_dir}/ssot-registry.md`
   pointing to the new PRD as canonical source for this macro feature. **Skip
   when the file does not exist in the project** (some consumers don't maintain
   one — check the path before writing).
4. Update `$WORKTREE_PATH/${paths.references_dir}/project-status.md` Active Code
   Context with PRD completion. **Skip when the file does not exist.**

   **Note on cross-PRD collisions.** Both files (`ssot-registry.md` and
   `project-status.md`) live on `git.trunk_branch` and are touched by other PRD
   sessions in parallel. The rebase in step 8 below brings the worktree up to the
   latest `origin/git.trunk_branch` BEFORE the merge, so two parallel PRDs ending
   at the same time produce a clean append-only diff in most cases. If the rebase
   reports conflicts on these two files, `worktree-manager mw-docs` does **not**
   ask immediately: it strips the markers ("keep both sides", which on append-only
   registries is the correct additive result) and then runs a structural
   validation. The merge proceeds automatically when the validation passes. It
   escalates to the user **only** in the rare case where both PRDs modified the
   same row and the validation fails — i.e. a conflict that genuinely cannot be
   auto-resolved. See `/mw` step 4b "Structured registries" row.

### Commit (inside the worktree)

5. **Run the Metrics Log step from SKILL.md** ("Metrics Log — MONITORING SIGNAL, non-blocking") FIRST
   — it appends the run summary to `$WORKTREE_PATH/${paths.metrics}/skill-runs.jsonl`
   (`paths.metrics` default: `docs/metrics`), which must be part of the same commit.
   Non-blocking: if it fails, log "Metrics Log: SKIPPED" in the progress bar and continue.

6. Stage ONLY PRD session artefacts by explicit file name (NEVER `git add .`
   or `git add -A`):
   - `$WORKTREE_PATH/${paths.prd_dir}/sessions/YYYY-MM-DD-<slug>-state.md`
   - `$WORKTREE_PATH/${paths.prd_dir}/<slug>/PRD.md`
   - `$WORKTREE_PATH/${paths.prd_dir}/<slug>/design.html` (if exists)
   - `$WORKTREE_PATH/${paths.prd_dir}/<slug>/mockups/**` (if any)
   - `$WORKTREE_PATH/${paths.backlog_dir}/FEAT-XXXX-*.yml` (all cards from Step 5)
   - `$WORKTREE_PATH/${paths.references_dir}/ssot-registry.md` (only if item 3 wrote the file)
   - `$WORKTREE_PATH/${paths.references_dir}/project-status.md` (only if item 4 wrote the file)
   - `$WORKTREE_PATH/${paths.metrics}/skill-runs.jsonl` (only if the Metrics Log step in 5 wrote a line)
   - `$WORKTREE_PATH/${paths.metrics}/sessions/prd-<slug>.md` (only if `-stats` was active and the sessions file was written)

   Verify staged set with `git -C "$WORKTREE_PATH" diff --staged --name-only`.

7. Update state file: status -> `committing`, add `worktree.branch`.
   (Status is set to `committing` — not `completed` — so that if the commit in
   item 8 fails, any subsequent `/prd` recovery session sees an incomplete run
   rather than a false `completed` with no SHA.)

8. Commit with format: `[PRD] <slug>: <brief description>`
   ```bash
   git -C "$WORKTREE_PATH" commit -m "[PRD] <slug>: <brief description>"
   ```
   After the commit succeeds: capture the SHA via `git -C "$WORKTREE_PATH" rev-parse HEAD`,
   then update the state file atomically: status -> `completed`, write the SHA
   under `## Validation`. If the commit fails, leave status as `committing` and
   HALT with the git error verbatim.

### Merge into `git.trunk_branch` (worktree-manager mw-docs)

9. **Invoke `worktree-manager` programmatic mw-docs** — no confirmation prompt.
   The user opted into the full PRD pipeline when they ran `/prd`; asking for
   confirmation at the end is unnecessary friction and breaks the seamless
   flow (v3.22.0 design intent). Merge and worktree cleanup run automatically.

   **Why:** the ONLY thing that stops the automatic merge is a conflict
   `mw-docs` genuinely cannot auto-resolve (a code/test conflict, or a
   structured registry whose strip+validate failed). Everything `mw-docs` can
   auto-resolve (additive docs/YAML/JSONL, append-only registries that pass
   validation) merges with zero questions. Do NOT pre-empt the merge with a
   confirmation gate "just in case there might be conflicts" — let `mw-docs`
   run and only surface what it actually reports as unresolvable.

   The merge is recoverable: if something goes wrong post-merge the user can
   `git revert` on `git.trunk_branch` just like any other commit. The hard-to-reverse
   operations (`git push`, branch deletion) happen inside `mw-docs` which
   has its own failure handling (rebase abort, push rejection, gh CLI error)
   — none of which silently corrupts state.

   **Escape hatch.** If the user explicitly says "do not merge" or "leave the
   worktree" at any point during Step 7, respect that and STOP after the commit
   — the worktree stays on disk, ready for a future manual `/mw` or
   `worktree-manager mw-docs`. This is a user-initiated override, not a
   default gate.

   **Invocation (one call only):**
   ```
   Skill: worktree-manager
   Mode:  programmatic, mw-docs
   Input: { "worktreePath": "$WORKTREE_PATH" }
   ```

   The skill executes:
   - Safety commit (idempotent — likely no-op after step 8; if any file was
     missed by item 6's explicit list, the safety commit picks it up, but the
     tip commit message will be the safety message rather than `[PRD] …`. To
     avoid this, ensure item 6 stages every file written in Steps 5-7).
   - `git fetch origin git.trunk_branch` + `git rebase origin/git.trunk_branch` inside the worktree.
   - Conflict auto-resolution table (see `/mw` step 4b): additive docs/yaml and
     append-only `.jsonl` files auto-resolve; **structured registries**
     (`project-status.md`, `ssot-registry.md`, `field-registry.*`,
     `traceability-matrix.md`, `REGISTRY.md`) are auto-resolved by strip +
     structural validation, and only abort (escalating to the user) when the
     validation fails — the rare same-row collision. Code / test / fixture
     conflicts always abort and escalate.
   - Merge via the configured `git.merge_strategy` (`pr` or `local-push`).
   - Cleanup: `git worktree remove` + `git branch -d` + registry entry removal.
   - Local `git.trunk_branch` ref sync (only if main repo HEAD is already on `git.trunk_branch`).

   On success it returns `{ merged: true, mergeCommit, prNumber, strategy,
   worktreeRemoved: true }`.

   **Failure handling.** If `mw-docs` reports failure (rebase conflicts, push
   rejected, gh CLI error, post-cleanup error): STOP and surface the error
   verbatim. The worktree stays on disk so the user can investigate. Do NOT
   retry automatically — the next attempt should be user-driven.

**Gate:** `mw-docs` returned `merged: true` AND `worktreeRemoved: true` AND
`strategy` is one of `pr` or `local-push` (well-formedness check — ensures
the return object is complete before the final summary reads it) AND the
state file (now on `git.trunk_branch` via the merge) shows `completed`.

Mark task 5 as `completed`.

### Step 7.5 — Workspace hygiene & finalization (BLOCKING — the run MUST conclude clean)

**Why this exists (v3.39.0).** A PRD run used to end by handing the user a list of
"non-blocking manual actions" — an uncommitted file blocking the local `git.trunk_branch`
fast-forward, a remote branch left undeleted. That is a half-finished process.
`/prd` now closes the loop exactly like `/new` Phase 6c: **no residue is ever
surfaced as a passive manual TODO.** Every leftover is either auto-resolved or put
behind ONE explicit `AskUserQuestion` gate.

**Requires `$MAIN`** (resolved in the Step 7 variable guard above).

Scan the captured `mw-docs` stdout (the merge step above) for the structured
markers it can emit, then act:

1. **`[SYNC-NEEDS-DECISION] …`** — the local `git.trunk_branch` could not
   fast-forward and the blocker is a **foreign** uncommitted file (one this run
   does not own), or a genuine divergence/rebase conflict. Do NOT auto-commit
   foreign work. Raise ONE `AskUserQuestion`:
   - Question: `"Local <git.trunk_branch> did not sync: <detail from marker>. How should we close?"`
   - Options: `[Commit the pending file now (describe it)]` / `[I will handle it — close without local sync]` / `[Show me the diff and we decide]`.
   Act on the answer in this turn. Never leave it as a printed note.

2. **`[SYNC-DEFERRED] …`** (main repo HEAD ≠ `git.trunk_branch`) — fetch already
   updated `refs/remotes/origin/git.trunk_branch`. If the user's HEAD is now
   `git.trunk_branch`, run `git -C "$MAIN" pull --ff-only origin git.trunk_branch`.
   Otherwise raise ONE `AskUserQuestion`:
   `[Pull now (fast-forward only)]` / `[Leave deferred, I will reconcile]`.

3. **No marker** — `mw-docs` already fast-forwarded (or auto-reconciled the
   framework telemetry log). If `mw-docs` stdout contains the auto-reconcile
   message (`✅ Reconciled stale metrics log…`), check whether local
   `git.trunk_branch` is 1 commit ahead of `origin/git.trunk_branch`
   (`git -C "$MAIN" status --porcelain -b`). If it is, run
   `git -C "$MAIN" push origin git.trunk_branch`. On push failure, treat
   as a `[SYNC-NEEDS-DECISION]` case.
   If neither condition applies: nothing to do; the tree is clean.

4. **Remote feature branch** — `mw-docs` deletes the merged remote branch as
   routine cleanup (see AGENTS.md § branch deletion: deleting an **already-merged**
   feature branch is NOT the gated "branch deletion" that needs owner approval).
   If for any reason the deletion was blocked, do NOT print "you can delete it
   manually": run `git -C "$MAIN" push origin --delete <branch>` now,
   and only if THAT is denied raise ONE `AskUserQuestion`.

**Gate:** `git -C "$MAIN" status --porcelain` for framework-owned paths is
empty, no `[SYNC-NEEDS-DECISION]` marker is left unhandled, and the merged remote
branch is gone (or its deletion is explicitly user-deferred).

### Step 7.6 — Obsidian back-reference (NON-BLOCKING — runs only when a spec note was given)

**Why this exists.** When the user kicked off the PRD from an Obsidian note (state file
`## Obsidian Spec Note / status` is not `none`, see HARD RULE 19), they want that same note
to become the durable index of *how the feature was developed and where it landed* — so
they can later ask "come è stata sviluppata la feature X?" and get the PRD, cards, and
merge from the note itself.

This is the **FINAL** write of the two-phase back-reference (the provisional write already
happened at discovery-phase Step 1 point 2, status `provisional-written`). It **upgrades
the provisional block in place** — same slug-keyed markers — replacing the lightweight
"PRD in stesura" block with the full, queryable payload.

**Runs AFTER the merge (Step 7 item 9) succeeds** — only then are the canonical paths,
card IDs, branch, PR/SHA final.

**Procedure:** follow [obsidian-backref.md](obsidian-backref.md) **§ Mode: FINAL** — it is
the SSOT for the marker contract, the idempotent read-replace-or-append write procedure,
the full snippet template, the skip conditions (no note / `path: none` / unwritable → log
and continue, never fatal), and the final state update to
`## Obsidian Spec Note / status: back-reference-written`. Fill the `yaml` from the final
state: the real epic + child card IDs (Step 5), the merge SHA under `## Validation` (Step 7
item 8), the PR number from the `mw-docs` return (or `n/a`), and the Canonical Sources the
PRD assembled (prd-writing-phase.md Step 4 point 3) for `resources:`.

**This step is NON-BLOCKING** — any failure (note moved, vault unmounted, permission
denied) logs "Obsidian back-reference: SKIPPED (<reason>)" and the run completes. The PRD
merge has already happened; the back-reference is a convenience index, never a gate.

### Final output

Print final summary:

- PRD path (relative to repo root, e.g. `docs/prd/<slug>/PRD.md`)
- Design path (if applicable)
- Card IDs created
- Branch & merge strategy used
- PR number (if `strategy = pr`) or merge SHA (if `local-push`)
- Confirmation that the worktree was cleaned up AND the remote branch was deleted
- Obsidian back-reference: the note path the snippet was written to (only when Step 7.6 ran; omit otherwise)

**HARD RULE — no passive deferral.** The final summary MUST NOT contain a
"your manual actions / non-blocking notes / you can do it yourself" section.
If something genuinely needed the user, it was already raised as an
`AskUserQuestion` in Step 7.5 and acted on — it does not reappear here as a TODO.
A run that prints "local `git.trunk_branch` did not fast-forward, handle the file
yourself and then pull" has FAILED Step 7.5 — go back and resolve it before
printing the summary.

Display completed Progress Bar.
