# Phase 05 — Global Verification

**Mode:** INLINE (orchestrator performs every verification step directly — never trust cluster-worker reports).
**Workflow CLI param:** `--workflow=migration`.
**Inputs (must exist on entry):** `tmp/${MIGRATION_ID}/{intent,classification,transform-rules,clusters}.{json,md}`; `tmp/${MIGRATION_ID}/phase-04-cluster-reports.md` (consolidated cluster reports from Phase 04) — OR if `dry_run == true`, only the rules + design-sensitive log.
**Outputs (must exist on exit):** `tmp/${MIGRATION_ID}/verification-report.md` — the migration's verifiable proof, including the residual-grep table, FLAGGED resolution log, dual-path diff sweep result, and optional test-run summary.

---

## Entry Gate [MANDATORY]

```bash
devforgeai-validate phase-check ${SESSION_ID} --workflow=migration --from=04 --to=05
```
IF exit != 0: HALT — Phase 04 incomplete. (Note: under SKILL.md's "Dry-Run Pass-Through" pattern, Phase 04 runs sequentially and completes via the empty-clusters branch — the phase-state advances 04→05 normally, and this `phase-check` returns "Transition allowed: 04 -> 05" as in a real-run. There is no skip.)

---

## Reference Loading [MANDATORY]

```
Read(file_path=".claude/skills/spec-driven-migration/references/verification-protocol.md")
Read(file_path=".claude/skills/spec-driven-migration/assets/templates/migration-report-template.md")
```

IF any Read fails: HALT -- "Phase 05 reference files not loaded."

---

## Step 5.1: Load Verification Protocol + Cluster Reports

Phase 05's contract is straightforward but easy to get wrong: classify every remaining occurrence of `FROM_PATTERN` in the working tree, and prove (not assume) that zero of them are still transformable. The protocol document is the SSOT for that classification.

**EXECUTE:**
```
Read(file_path="references/verification-protocol.md")
Read(file_path="tmp/${MIGRATION_ID}/phase-04-cluster-reports.md")
Read(file_path="tmp/${MIGRATION_ID}/transform-rules.md")
Read(file_path="tmp/${MIGRATION_ID}/intent.json")
```
(`verification-protocol.md` is forward-declared; if absent, use the inline classification matrix below.)

Inline residual-classification matrix:
```
Each remaining FROM_PATTERN hit must classify into exactly ONE bucket:
  ALREADY-MIGRATED   — the line already contains the to_pattern alongside an inert from_pattern reference
  DOC-historical     — the hit is in a changelog, ADR archive, or "deprecated" block; never edit
  EXCLUDED           — the path matches an exclusion glob from intent.json
  FLAGGED-type-sens. — the hit was flagged by Phase 04 worker as type-sensitive; resolve in Step 5.3

  TRANSFORMABLE      — DISALLOWED. Any hit that classifies here means Phase 04 was incomplete; HALT.
```

**VERIFY:**
- All 4 Reads succeeded.
- The consolidated cluster reports document lists every cluster from `clusters.json` (Grep for each `cluster_id`).

**RECORD:**
```bash
devforgeai-validate phase-record ${SESSION_ID} --workflow=migration --phase=05 --step=protocol-loaded
```

---

## Step 5.2: Global Residual Grep + Classification

The cluster workers self-reported residuals scoped to their own SCOPE_DIRS. The orchestrator MUST independently grep the entire `scope_dirs` from `intent.json` — this is the audit-grade verification. Per [[feedback_verify_truncated_subagent_reports]]: a truncated or optimistic cluster-worker report can hide a residual; the only defense is the orchestrator's own independent grep.

**EXECUTE:**

For every directory in `intent.json.scope_dirs`:
```
Grep(pattern=FROM_PATTERN, path=<scope_dir>, output_mode="files_with_matches")
```
Aggregate all returned paths. Then for each path, Grep again with `output_mode="content"` to get the actual line contents. Apply the migration's `exclusions` to drop paths matching any exclusion glob.

For every remaining hit, classify it into exactly ONE of the 4 allowed buckets (ALREADY-MIGRATED, DOC-historical, EXCLUDED, FLAGGED-type-sensitive). Build a table:

```
| File | Line | Excerpt | Bucket | Rationale |
|------|------|---------|--------|-----------|
| ...  | ...  | ...     | ...    | ...       |
```

**Dry-Run Branch:** If `intent.json.dry_run == true`, this step instead produces a "WOULD-EDIT" table — rather than classifying live residuals, it lists every site the cluster workers WOULD have edited per the transform-rules. No `--from=04` precondition applies; the dry-run grep + projection is the sole content of the report.

**VERIFY:**
- Every aggregated hit is classified.
- ZERO hits classify as TRANSFORMABLE. If any do, HALT — Phase 04 was incomplete; the orchestrator must re-dispatch the affected cluster (return to Phase 04 with a narrowed cluster scope).
- The number of FLAGGED-type-sensitive hits in this table matches (within tolerance) the count in `phase-04-cluster-reports.md` section (d). A divergence means a worker missed a flag — HALT and re-investigate.

**RECORD:**
```bash
devforgeai-validate phase-record ${SESSION_ID} --workflow=migration --phase=05 --step=residual-classified
```

---

## Step 5.3: Hand-Resolve FLAGGED Type-Sensitive Sites

Workers flag type-sensitive sites (e.g., `Read(file_path="X")` when X is changing from file to folder) instead of transforming them — exactly because a uniform noun-swap would silently break runtime semantics. Phase 05 is where the orchestrator hand-resolves each flagged site with the correct semantics.

**EXECUTE:**

For each FLAGGED entry from the consolidated cluster reports (Phase 04 section (d)):
1. Read the file (both dual-path copies).
2. Determine the correct semantic transformation, consulting `references/type-change-bug-flagging.md` (forward-declared; if absent, derive from the transform-rules.md "Migration Intent" block — specifically the `to_type` and `type_change_bug_declared` fields).

Common semantic rewrites (from the source-tree migration playbook):
| Type-change | Wrong (noun-swap) | Right (semantic) |
|-------------|-------------------|------------------|
| file → folder | `Read("X.md")` becomes `Read("X/")` (errors at runtime) | Replace with the canonical entry-point read (e.g., `Read("X/_manifest.json")`) or a CLI query (e.g., `query-source-tree --subtree <path>`) |
| file → CLI | `Read("X.md")` becomes `Read("query-X")` (still treats as file) | Replace with `Bash("query-X --flag")` invocation |
| section-of-file → standalone file | `Grep(pattern="^## SectionName", path="X.md")` | Replace with `Read("X/section-name.md")` directly |

3. Apply the rewrite with Edit on BOTH dual-path copies (IDENTICAL old_string/new_string).
4. Self-diff:
   ```
   Bash("diff <.claude/path> <src/claude/path>")
   ```
   Output MUST be empty.

5. If a flagged site cannot be cleanly resolved (e.g., the semantic rewrite requires architectural changes outside the migration's scope): document the DEFERRAL in the verification report with explicit justification — do NOT silently transform. Per CLAUDE.md HALT trigger #10: "Insufficient evidence for a finding — flag as INCONCLUSIVE rather than guessing."

**Dry-Run Branch:** If `intent.json.dry_run == true`, this step produces a "WOULD-RESOLVE" plan only — for each FLAGGED site, document the proposed semantic rewrite; do NOT Edit any file.

**VERIFY:**
- Every FLAGGED site from Phase 04 either has a resolution Edit + clean `diff`, OR a documented deferral with justification.
- No FLAGGED site was "noun-swapped" silently — the type-change-bug protection holds.

**RECORD:**
```bash
devforgeai-validate phase-record ${SESSION_ID} --workflow=migration --phase=05 --step=flagged-resolved
```

---

## Step 5.4: Global Dual-Path Diff Sweep

The cluster workers self-diff'd their pairs and the orchestrator spot-checked 5 pairs in Phase 04 Step 4.4. Phase 05's sweep is broader: every edited subtree gets a `diff -r` to prove the entire migration is dual-path identical.

**EXECUTE:**

For each cluster from `clusters.json` whose scope_dirs include both `.claude/X/` and `src/claude/X/`:
```
Bash("diff -r .claude/X/ src/claude/X/")
```
The output MUST be empty (no Only-in lines, no differing-files lines) within the cluster's scope, MINUS any files explicitly declared single-path in `intent.json`.

If a subtree is very large (>1000 files), sample 20 random pairs from the cluster's edited set per the cluster report's section (a) instead of full `diff -r`. Document the sampling approach in the verification report.

**Dry-Run Branch:** SKIP Step 5.4 entirely — no files were edited; there is nothing to diff. Record "dry-run: dual-path sweep skipped" in the verification report.

**VERIFY:**
- Every full `diff -r` returns empty (or every sampled `diff` returns empty for sampled subtrees).
- The set of files reported as "Only in" or "differ" is zero AFTER applying single-path declarations.

IF any drift detected: HALT and escalate via AskUserQuestion — "Dual-path drift detected after Phase 04 + Step 5.3. The migration must NOT proceed to commit with drifted mirrors. List the drifted files and request orchestrator reconciliation."

**RECORD:**
```bash
devforgeai-validate phase-record ${SESSION_ID} --workflow=migration --phase=05 --step=dual-path-swept
```

---

## Step 5.5: Optional Test Runs (Conditional)

If the migration touched tested code paths, the verification protocol's revert-test pattern says: re-run the affected test suite to confirm no regression. This is JUDGMENT-CONDITIONAL — the orchestrator decides based on whether any edited file lies under a tested subtree (e.g., `src/claude/scripts/devforgeai_cli/**` is tested; `.claude/agents/*.md` typically is not).

**EXECUTE:**

If any edited file from the consolidated Phase 04 reports (section (a)) lies under a tested subtree:
```
Bash("devforgeai-validate run-tests ${MIGRATION_ID} --path=tests/")
```
or scope to the affected test directory if known.

If no tested code was touched, document the rationale and skip the test run. ("Migration scope was `.claude/agents/` + `.claude/commands/` — no tested code paths affected; test re-run skipped.")

**Dry-Run Branch:** SKIP Step 5.5 — no edits were made. Record "dry-run: no test re-run needed."

**VERIFY:**
- IF tests were run: exit code 0; all tests pass.
- IF tests were skipped: the skip rationale is documented in the verification report.

IF tests fail: HALT — the migration broke runtime behavior. Investigate via [[feedback_verify_truncated_subagent_reports]] (a worker may have mis-classified a READ-OP site) and the type-change-bug catalog. Do NOT proceed to Phase 06.

**RECORD:**
```bash
devforgeai-validate phase-record ${SESSION_ID} --workflow=migration --phase=05 --step=tests-rerun
```

---

## Step 5.6: Write the Verification Report

The verification report is the final auditable proof of the migration's correctness. It becomes the body of the Phase 06 commit message.

**EXECUTE:**

Load the template:
```
Read(file_path="assets/templates/migration-report-template.md")
```
(Forward-declared; if absent, use the inline structure below.)

Inline structure:
```
# Migration Verification Report — ${MIGRATION_ID}

## Intent (carried from intent.json)
- from_pattern, to_pattern, to_type, scope_dirs, exclusions, dry_run

## Cluster Summary (from phase-04-cluster-reports.md)
- N clusters, total files edited, FLAGGED count, Uncertain count

## Residual Classification (from Step 5.2)
- Per-bucket counts: ALREADY-MIGRATED / DOC-historical / EXCLUDED / FLAGGED
- Confirmation: zero TRANSFORMABLE residuals

## FLAGGED Resolution Log (from Step 5.3)
- Per-site: file, line, original FLAG context, applied resolution OR deferral justification

## Dual-Path Sweep Result (from Step 5.4)
- Per-cluster: diff -r empty? sampled? files compared

## Test Re-Run (from Step 5.5)
- Run? skipped? rationale? exit code?

## Design-Sensitive Files (carried from Phase 03)
- Per-file rewrites already applied in Phase 03 (just reference design-sensitive-handled.md)

## Verdict
- READY-TO-COMMIT | DEFER-WITH-OPEN-FLAGS | DRY-RUN-COMPLETE
```

Populate every section from prior steps' artifacts. Then:
```
Write(file_path="tmp/${MIGRATION_ID}/verification-report.md", content=<populated report>)
```

**VERIFY:**
- The file exists:
  ```
  Glob(pattern="tmp/${MIGRATION_ID}/verification-report.md")  # MUST return 1 path
  ```
- The Verdict line is exactly one of `READY-TO-COMMIT`, `DEFER-WITH-OPEN-FLAGS`, or `DRY-RUN-COMPLETE`.
- Every section has content (no `<populated>` placeholders left).

IF Verdict is `DEFER-WITH-OPEN-FLAGS`: the migration MUST NOT proceed to Phase 06 commit. AskUserQuestion to confirm deferral, then HALT.

**RECORD:**
```bash
devforgeai-validate phase-record ${SESSION_ID} --workflow=migration --phase=05 --step=report-written
```

---

## Exit Gate [MANDATORY]

```bash
devforgeai-validate phase-complete ${SESSION_ID} --workflow=migration --phase=05 --checkpoint-passed
```
IF exit != 0: HALT.

---

## Phase 05 Success Criteria

- [ ] Global residual `Grep(pattern=FROM_PATTERN)` ran across every directory in `intent.json.scope_dirs`; every hit was classified.
- [ ] ZERO residual hits classified as TRANSFORMABLE (any such hit means Phase 04 must be re-run).
- [ ] Every FLAGGED type-sensitive site from Phase 04 was either hand-resolved (Edit dual-path identical, `diff` empty) or explicitly deferred with documented justification.
- [ ] Global dual-path `diff -r` (or sampled diff) returned empty for every edited subtree (skipped if dry-run).
- [ ] If tested code was touched: `run-tests` exit code 0. If not touched: skip rationale documented.
- [ ] `tmp/${MIGRATION_ID}/verification-report.md` written with Verdict ∈ {READY-TO-COMMIT, DEFER-WITH-OPEN-FLAGS, DRY-RUN-COMPLETE}.
- [ ] `phase-record` was called for each step (`protocol-loaded`, `residual-classified`, `flagged-resolved`, `dual-path-swept`, `tests-rerun`, `report-written`).
- [ ] The Exit Gate `phase-complete` returned exit 0.
