# Phase 03 — Transform-Rules + Design-Sensitive Triage + Cluster Planning

**Mode:** INLINE (interactive — orchestrator + user co-author the rules; orchestrator performs the design-sensitive edits; user confirms the cluster plan).
**Workflow CLI param:** `--workflow=migration`.
**Inputs (must exist on entry):** `tmp/${MIGRATION_ID}/intent.json` + `tmp/${MIGRATION_ID}/classification.md` (from Phase 02); user approval recorded in the checkpoint.
**Outputs (must exist on exit):** `tmp/${MIGRATION_ID}/transform-rules.md` (per-bucket transformation directives); `tmp/${MIGRATION_ID}/design-sensitive-handled.md` (log of in-phase logic rewrites); `tmp/${MIGRATION_ID}/clusters.json` (partition for Phase 04 parallel workers); user confirmation of the cluster plan recorded in the checkpoint.

---

## Entry Gate [MANDATORY]

```bash
devforgeai-validate phase-check ${SESSION_ID} --workflow=migration --from=02 --to=03
```
IF exit != 0: HALT — Phase 02 incomplete or skipped.

---

## Reference Loading [MANDATORY]

```
Read(file_path=".claude/skills/spec-driven-migration/references/transform-rules-schema.md")
Read(file_path=".claude/skills/spec-driven-migration/references/classification-bucket-catalog.md")
Read(file_path=".claude/skills/spec-driven-migration/references/cluster-partitioning.md")
Read(file_path=".claude/skills/spec-driven-migration/assets/templates/transform-rules-template.md")
```

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

---

## Step 3.1: Author the Transform-Rules

The classifier bucketed every occurrence; the transform-rules document is the per-bucket transformation grammar that the cluster workers will execute uniformly. The orchestrator + user co-author this document. The user's role is to declare migration-specific intent (e.g., the exact READ-OP replacement, the LISTING noun-swap, the type-change-bug declaration). The orchestrator drafts the structure and prompts for each decision.

**EXECUTE:**

Load the three reference files:
```
Read(file_path="tmp/${MIGRATION_ID}/classification.md")
Read(file_path="references/transform-rules-schema.md")
Read(file_path="references/classification-bucket-catalog.md")
```
(`transform-rules-schema.md` and `classification-bucket-catalog.md` are forward-declared; if either does not exist yet, defer to the bucket names in `classification.md` and the inline schema below.)

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

Inline structure (one section per bucket the classifier emitted):
```
# Transform Rules — ${MIGRATION_ID}

## Migration Intent (carried from intent.json)
- from_pattern: <FROM_PATTERN>
- to_pattern:   <TO_PATTERN>
- to_type:      <file|folder|cli>
- type_change_bug_declared: <true|false>   # CRITICAL — see READ-OP below

## Bucket: READ-OP
Replacement: <exact old_string → new_string contract>
Example: <one before/after snippet>

## Bucket: LISTING
Replacement: <noun-swap rule — only LISTING-positioned mentions, not narrative prose>

## Bucket: PATH-VALIDATION
Replacement: <path-shape rule — file vs folder vs CLI-target validation>

## Bucket: DOC
Replacement: <typically NONE for DOC-historical; specify when DOC-current-state must update>

## Buckets the workers MUST NOT auto-transform
- DOC-historical: never edit
- EXCLUDED: never edit
- ALREADY-MIGRATED: never edit
- MIXED: surface for orchestrator triage (Step 3.2)
- FLAGGED type-sensitive: surface for Phase 05 hand-resolution
```

Issue THREE AskUserQuestion checkpoints in sequence, recording the answer into the document before moving to the next:

```
AskUserQuestion #1:
  Question: "READ-OP bucket: the from_pattern is read/loaded by code. What is the exact READ-OP target replacement? (Often a file → file rename, OR a file → folder/manifest entry-point change.)"
  Header: "READ-OP Target"
  Options: <task-specific; usually a free-form follow-up>

AskUserQuestion #2:
  Question: "LISTING bucket: the from_pattern appears in plain prose lists (e.g., 'see X, Y, Z'). Should LISTING references be noun-swapped (X → Y) or left as DOC-historical?"
  Header: "LISTING Noun-Swap"
  Options:
    - label: "Noun-swap LISTING entries"
    - label: "Treat LISTING as DOC-historical (no edit)"
    - label: "Mixed — orchestrator decides per file in Step 3.2"

AskUserQuestion #3 (CRITICAL):
  Question: "Type-change-bug check: does the migration change the FILE-SYSTEM TYPE of the referenced object? (file → folder, file → CLI invocation, etc.) A type-change requires every reference call site to be reviewed individually; workers will FLAG these instead of transforming."
  Header: "Type-Change-Bug Declaration"
  Options:
    - label: "Yes — to_type changes file-system type; workers must FLAG affected sites"
    - label: "No — same type (file → file, folder → folder); workers may transform uniformly"
```

The Q3 answer flips `type_change_bug_declared` in the transform-rules document. The workers (and Phase 05) rely on this flag — declaring it wrong is the failure mode the source-tree migration caught and the reason this skill exists.

Write the populated document:
```
Write(file_path="tmp/${MIGRATION_ID}/transform-rules.md", content=<populated rules>)
```

**VERIFY:**
- The file exists:
  ```
  Glob(pattern="tmp/${MIGRATION_ID}/transform-rules.md")  # MUST return 1 path
  ```
- Every bucket present in `classification.md` has a corresponding `## Bucket:` section in `transform-rules.md` (Grep for each bucket name from the classifier's summary table).
- The Migration Intent block lists all 4 fields including `type_change_bug_declared`.

**RECORD:**
```bash
devforgeai-validate phase-record ${SESSION_ID} --workflow=migration --phase=03 --step=rules-authored
```

---

## Step 3.2: Design-Sensitive Triage (Orchestrator Inline)

Some files cannot be uniformly transformed by a cluster worker. They require LOGIC REWRITE rather than noun-swap. Examples from the source-tree migration (PR #68):

- A `fix-workflow` script that hand-edited a now-generated artifact — the script's logic is no longer applicable; it must be rewritten or retired.
- A content-grep for a "section of X" in a file that has been decomposed into multiple files — the grep target is now structurally different.

These files MUST be handled INLINE by the orchestrator in this phase, BEFORE the cluster workers run. The cluster workers are uniform transformers; they cannot judge "rewrite the algorithm, not the literal."

**EXECUTE:**

Re-read `classification.md` and identify the MIXED bucket + any FILES-NEEDING-NO-EDIT entries that the classifier flagged with annotations like "logic rewrite needed" or "redirected Read." Also include any file the user explicitly named in Step 3.1's AskUserQuestion #2 ("Mixed — orchestrator decides per file").

For each design-sensitive file:
1. Read both dual-path copies (`.claude/...` and `src/claude/...`).
2. Apply the logic rewrite manually with Edit (or Write if a full rewrite). The rewrite must encode the migration's INTENT, not its noun-swap.
3. Per [[feedback_dual_path_mechanical_migration]]: edit BOTH copies with IDENTICAL old_string/new_string in the same Edit pair to preserve byte-identity.
4. Self-diff the pair:
   ```
   Bash("diff <.claude/path> <src/claude/path>")
   ```
   Output MUST be empty.

Log each design-sensitive file handled, with a one-paragraph "what was rewritten and why" rationale:
```
Write(file_path="tmp/${MIGRATION_ID}/design-sensitive-handled.md", content=<log>)
```
The log MUST be present (even if empty: write a header + "No design-sensitive files identified in this migration.").

**VERIFY:**
- `tmp/${MIGRATION_ID}/design-sensitive-handled.md` exists.
- Every file listed has been verified dual-path identical (`diff` output empty).
- The design-sensitive set has been REMOVED from the cluster-worker scope (these files MUST NOT be revisited by workers — record them in the exclusions Phase 04 will receive).

IF a design-sensitive `diff` is non-empty after Edit: HALT and reconcile inline before continuing. Per [[feedback_verify_truncated_subagent_reports]]: even in single-actor inline work, verify the post-condition rather than trusting the Edit return.

**RECORD:**
```bash
devforgeai-validate phase-record ${SESSION_ID} --workflow=migration --phase=03 --step=design-sensitive-handled
```

---

## Step 3.3: Cluster Planning (Partition the Remaining Files)

The non-design-sensitive files (the bulk of the migration) get partitioned into N clusters for Phase 04 parallel dispatch. Clustering by directory keeps each worker's mental model tight and minimizes scope_dirs collisions across workers.

**Dry-Run Branch:** If `intent.json.dry_run == true`, write `clusters.json` as `[]` (empty JSON array) and proceed to the Exit Gate normally. **The workflow does NOT skip Phase 04** — it continues sequentially. Phase 04 sees the empty array, invokes the cluster worker on it (worker reports "0 clusters to process"), records the subagent + steps, and advances to Phase 05 per the Pass-Through pattern documented in SKILL.md "Dry-Run Pass-Through". Phase 05 then produces WOULD-EDIT/WOULD-RESOLVE reports instead of live edits.

**EXECUTE:**

Load the partitioning heuristics:
```
Read(file_path="references/cluster-partitioning.md")
```
(Forward-declared; if absent, use the inline rule below.)

Inline partitioning rule:
- Aim for clusters of 30–80 files each.
- Partition by top-level directory in `scope_dirs` (e.g., `.claude/agents/`, `.claude/skills/`, `.claude/commands/`, `.claude/hooks/`, `.claude/rules/`, `devforgeai/specs/`).
- For each `.claude/X` cluster, the cluster's `scope_dirs` list MUST include both `.claude/X/` AND `src/claude/X/` (the dual-path mirror).
- If a single directory's file count exceeds 80, split by subdirectory (e.g., `.claude/skills/spec-driven-dev/` becomes its own cluster).
- Apply the migration's `exclusions` to each cluster; also append the design-sensitive file paths from Step 3.2 to each cluster's `exclusions`.

Build the cluster list:
```json
[
  {
    "cluster_id": "agents",
    "scope_dirs": [".claude/agents/", "src/claude/agents/"],
    "exclusions": [<migration-wide exclusions + design-sensitive paths>],
    "file_count_estimate": <integer>
  },
  {
    "cluster_id": "dev-skills",
    "scope_dirs": [".claude/skills/spec-driven-dev/", "src/claude/skills/spec-driven-dev/"],
    "exclusions": [...],
    "file_count_estimate": <integer>
  }
]
```

Write the plan:
```
Write(file_path="tmp/${MIGRATION_ID}/clusters.json", content=<JSON array>)
```

**VERIFY:**
- `tmp/${MIGRATION_ID}/clusters.json` exists and parses as a JSON array.
- Every cluster has the 4 required fields (`cluster_id`, `scope_dirs`, `exclusions`, `file_count_estimate`).
- Every `scope_dirs` list includes BOTH the `.claude/` and `src/claude/` mirror for any covered subtree (or the cluster is explicitly single-path per `intent.json`).
- Cluster IDs are unique kebab-case slugs.
- Sum of `file_count_estimate` across clusters ≈ total in-scope files from `classification.md` minus design-sensitive count (within ±5%).

**RECORD:**
```bash
devforgeai-validate phase-record ${SESSION_ID} --workflow=migration --phase=03 --step=clusters-planned
```

---

## Step 3.4: User Confirmation of the Cluster Plan

The orchestrator MUST surface the cluster plan to the user before Phase 04 dispatches workers in parallel. Parallel dispatch is irreversible (you cannot cleanly "cancel" 5 in-flight Task() calls); the user's last chance to amend scope is here.

**EXECUTE:**

Read `clusters.json` and display it to the user, with file count totals per cluster, exclusions per cluster, and the count of design-sensitive files handled in Step 3.2. Then:

```
AskUserQuestion:
  Question: "Cluster plan: <N> clusters totaling <TOTAL_FILES> files (+ <DESIGN_SENSITIVE_COUNT> design-sensitive files already handled). Proceed to Phase 04 parallel dispatch?"
  Header: "Cluster Plan Confirmation"
  Options:
    - label: "Proceed (Recommended)"
      description: "Dispatch all <N> migration-cluster-worker subagents in parallel."
    - label: "Re-cluster"
      description: "Adjust the partition (orchestrator will re-prompt for the changes)."
    - label: "Abort migration"
      description: "Stop the workflow. All artifacts under tmp/${MIGRATION_ID}/ remain."
  multiSelect: false
```

**VERIFY:**
- The user selected exactly one option.
- "Proceed" → record `phase_03.cluster_plan_confirmed = true` in the checkpoint.
- "Re-cluster" → return to Step 3.3 with the user's amendments; do NOT advance to the Exit Gate.
- "Abort migration" → write `phase_03.status = "aborted"` and HALT.

**RECORD:**
```bash
devforgeai-validate phase-record ${SESSION_ID} --workflow=migration --phase=03 --step=cluster-plan-confirmed
```

---

## Exit Gate [MANDATORY]

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

---

## Phase 03 Success Criteria

- [ ] `tmp/${MIGRATION_ID}/transform-rules.md` authored — every bucket from `classification.md` has a corresponding `## Bucket:` section.
- [ ] The type-change-bug declaration was explicitly confirmed by the user (AskUserQuestion #3) and recorded in the transform-rules document.
- [ ] `tmp/${MIGRATION_ID}/design-sensitive-handled.md` exists; every listed file was hand-edited dual-path identical (`diff` empty) BEFORE Phase 04 dispatch.
- [ ] `tmp/${MIGRATION_ID}/clusters.json` exists; every cluster has all 4 required fields, dual-path scope_dirs (or single-path declared), and unique kebab-case cluster_id.
- [ ] The user explicitly confirmed the cluster plan via AskUserQuestion (Step 3.4) before Phase 04 begins.
- [ ] `phase-record` was called for each of the 4 steps (`rules-authored`, `design-sensitive-handled`, `clusters-planned`, `cluster-plan-confirmed`).
- [ ] If `dry_run == true`, `clusters.json` is empty (`[]`) and the workflow is routed to Phase 05 for report-only verification (skipping Phase 04).
- [ ] The Exit Gate `phase-complete` returned exit 0.
