# Type-Change-Bug Flagging

The trap catalog and FLAGGING protocol for occurrences whose transformation requires more than a noun-swap. This is the single most important reference in the skill — it encodes the lesson from PR #68 where a pure noun-swap silently broke adjacent logic that the swap couldn't see.

When a migration's `to_type` differs from the noun's original type (a file becomes a folder, `.md` becomes `.json`, a Read target becomes a CLI invocation), some call sites in the codebase read the noun's TYPE, not just its VALUE. Replacing the value but leaving the type-coupled logic intact silently breaks workflows. Cluster workers MUST FLAG (not transform) these sites; the orchestrator hand-resolves them in Phase 05.

Reference: `[[feedback_dual_path_mechanical_migration]]` "The type-change-bug trap (most important)" section.

---

## The Trap Categories

There are four canonical trap categories. Cluster workers recognise and FLAG every occurrence matching any one of them. The first two are the exemplar rows in `migration-cluster-worker.md`'s Output Format Section `(d)`; the latter two are the harder-to-detect ones that surfaced during the source-tree migration.

### Category 1 — `Glob("*.md")` count/existence checks

**Pattern signature:**

```python
files = Glob(pattern="…/<from_pattern_parent>/*.md")
IF len(files) < 6: …                # count check
IF "<from_pattern>" not in files: … # existence check
```

**Example (from the source-tree migration):**

```python
context_files = Glob(pattern="devforgeai/specs/context/*.md")
required = ["tech-stack.md", "source-tree.md", "dependencies.md", "coding-standards.md", "architecture-constraints.md", "anti-patterns.md"]
missing = [r for r in required if r not in context_files]
```

A pure noun-swap would change `source-tree.md` → `source-tree/` in the `required` list — but `source-tree/` is a folder; it will NEVER match the `*.md` Glob; `missing` becomes always-non-empty and the workflow now always-reports a missing context file.

**Suggested resolution:** split the count/existence check into a separate file-set + folder-existence test. The orchestrator hand-edits the surrounding logic, not just the noun.

**Worker action:** FLAG; do NOT transform. Report row:

```markdown
| `<file>:NN` | `Glob("*.md")` expects 6 — new target is a folder | Split into 5 .md glob + separate folder check |
```

### Category 2 — `[ -f path ]` shell tests on a path whose new target is a directory

**Pattern signature:**

```bash
if [ -f "<path>/<from_pattern>" ]; then
  …
fi
```

**Example:**

```bash
if [ -f "devforgeai/specs/context/source-tree.md" ]; then
  load_context_file "devforgeai/specs/context/source-tree.md"
fi
```

After the noun-swap the test becomes `if [ -f "…/source-tree/" ]` — a directory fails `-f` (which tests for regular file), so the `if` branch never executes and the consumer silently skips loading.

**Suggested resolution:** change the test to `[ -d ]` (directory test) — and adjust the loader inside the branch to read the new artifact's entry point (`_manifest.json` for the source-tree case).

**Worker action:** FLAG; do NOT transform. Report row:

```markdown
| `<file>:NN` | `[ -f path ]` test on a path whose new target is a directory | Change to `[ -d ]` |
```

### Category 3 — "the X section of `<from_pattern>`" content-greps where X doesn't exist in the new artifact

**Pattern signature:**

```python
# Reads a specific section by heading
section = Grep(pattern="^## Agents$", path="<from_pattern>")
# Or in prose:
"the Agents section of source-tree.md says …"
```

**Example (from the source-tree migration):**

The legacy `source-tree.md` had a top-level `## Agents` section. The new `source-tree/` folder redistributes that content into `components/agents.json` — there is no `## Agents` Markdown heading anywhere in the new artifact. A consumer that greps `^## Agents$` in the new artifact gets ZERO hits and silently degrades (always-empty section content, downstream logic that depends on the section being non-empty fails closed).

**Suggested resolution:** rewrite the consumer to consume the new artifact's schema (read `components/agents.json` for the agent inventory). This is structural, not a noun-swap — the consumer's parsing logic changes.

**Worker action:** FLAG; do NOT transform. Report row:

```markdown
| `<file>:NN` | content-grep for `^## Agents$` — section name doesn't exist in new artifact | Rewrite consumer to read `components/agents.json` |
```

### Category 4 — `Read()` whose target file's content was REDISTRIBUTED

**Pattern signature:**

```python
content = Read(file_path="<from_pattern>")
# … parse content with assumptions about its structure
```

**Example (the source-tree migration's most subtle trap):**

A workflow grepped the legacy `source-tree.md` content for the phrase "Dual-Location Architecture" to test whether dual-path was enabled. After the migration the noun-swap changes the `Read` target to `source-tree/_manifest.json` — but the manifest doesn't contain the phrase "Dual-Location Architecture" (the concept is now encoded in the manifest's `dual_path` boolean field). The Grep returns zero hits and the test becomes always-false.

Worse: if the post-migration `Read` happens to return content that ALSO contains some unrelated occurrence of "Dual-Location Architecture", the Grep is always-true and the workflow silently over-applies.

**Suggested resolution:** rewrite the consumer to read the new artifact's structured field (the manifest's `dual_path` boolean) instead of grepping prose. This is structural.

**Worker action:** FLAG; do NOT transform. Report row:

```markdown
| `<file>:NN` | `Read()` of source-tree.md grepped for "Dual-Location Architecture" — manifest doesn't contain that phrase; signal is now always-true/always-false | Rewrite consumer to read `_manifest.json` `dual_path` field |
```

---

## The FLAGGING Protocol

When a cluster worker encounters a type-change-risk site, it MUST follow this protocol:

1. **Do NOT transform the occurrence.** Leave the surrounding code byte-identical to its pre-migration state. The orchestrator will hand-resolve.
2. **Record the site in section `(d) FLAGGED type-sensitive sites`** of the cluster report:
   - `File:line` — exact location.
   - `Why flagged` — which trap category matched + a one-line rationale citing the type transition.
   - `Suggested resolution` — the worker's best-effort recommendation (orchestrator decides whether to accept).
3. **Re-grep the site as RESIDUAL.** The unchanged `from_pattern` will show up in the worker's Step 6 residual grep — record it as `FLAGGED-type-sensitive` (NOT as `READ-OP` / `LISTING` / etc — the unchanged occurrence is a deliberate flag, not an unmigrated transform candidate).
4. **Continue processing the rest of the cluster.** A FLAGGED site does NOT abort the worker — it's a sideband signal, not a halt condition.

### What FLAG does NOT mean

- FLAG does NOT mean "uncertain" — the worker is CERTAIN this is type-coupled; it's signalling that the transformation requires logic restructuring, not noun-swapping.
- FLAG is distinct from `(e) Uncertain calls` (which is for ambiguous classification calls the orchestrator decides). FLAG is for confidently-identified type traps.

---

## Orchestrator Hand-Resolution (Phase 05)

The orchestrator consolidates all FLAGGED sites across N cluster reports into a single list and hand-resolves in Phase 05:

1. **Read the FLAGGED site's surrounding code** to confirm the trap category.
2. **Apply the structural fix** — `Edit` the surrounding logic (not just the noun) to match the new artifact's type and entry-point shape.
3. **Edit BOTH dual-path copies identically** (the dual-path rule applies to the orchestrator's hand-edits the same as to cluster workers).
4. **Run the affected workflow's tests / smoke-check** to confirm the structural fix works.
5. **Document the resolution** in the final migration report — every FLAGGED site appears with `RESOLVED — <one-line description>` or `DEFERRED — <reason + tracking-ID>`.

### When to defer instead of resolve

Defer a FLAGGED site (do NOT resolve it inside this migration) when:

- The fix requires a logic rewrite that has its own ADR or story.
- The bug is coupled to a DIFFERENT phase of the larger migration (e.g. a count-logic break is coupled to the OLD artifact's physical deletion — it should be resolved in the deletion phase, not the consumer-migration phase).
- The fix's verification requires test coverage that doesn't yet exist.

A deferred FLAGGED site MUST have a tracking ID (a STORY-NNN, an issue, or a follow-up plan entry) recorded in the migration report — never "we'll do it later" with no tracking.

---

## Why this protocol exists

The source-tree migration (`[[feedback_dual_path_mechanical_migration]]`) surfaced the type-change-bug trap when a cluster worker FLAGGED a `Glob("*.md")` count check instead of guessing — and saved the workflow from a silently-broken count assertion shipping in main. Without the FLAGGING protocol, a confident worker would have noun-swapped the occurrence, the test would have passed (because the consumer's IF branch never executed), and the workflow would have been broken silently.

**The principle: a worker FLAGS what it cannot transform safely; an orchestrator that sees a FLAG MUST treat it as a structural issue, not a noun problem.**

---

## References

- `[[feedback_dual_path_mechanical_migration]]` — the source incident that proved the trap categories.
- `migration-cluster-worker.md` Output Format Section `(d)` — the per-cluster FLAGGED report format.
- `transform-rules-schema.md` Section 4 — the per-migration type-change declaration.
- `phase-04-cluster-execution.md` Step 4.3 — orchestrator consolidates FLAGGED sites.
- `phase-05-verification.md` — orchestrator hand-resolution loop.
- `verification-protocol.md` — Phase 05's residual classification (where `FLAGGED-type-sensitive` is one of the valid residual buckets).
