# Multi-Repo Integration Build  -  Learn Once, Auto-Apply

> **TLDR**  -  When a task touches ≥2 repos that have a producer→consumer dependency (e.g. shared codegen library + consuming UI library), the pipeline MUST build the **host project** that integrates them before commit/PR. Codegen mismatches (nested vs flat keys, missing entries, overwritten files) only surface when the full dependency chain builds together. Building repos in isolation gives false confidence.
>
> The pipeline **learns** the host per repo-combo once, persists to `prefs.global.multiRepoIntegrationHosts`, and auto-applies on subsequent runs.

---

## Why this exists

Codegen outputs (identifiers, localization keys, tokens) in Repo A get referenced by source code in Repo B. Repo B is consumed as a submodule or SPM/Gradle dependency by host Repo C. Changes in A or B can silently break C if key structures diverge (e.g. nested enum vs flat access pattern). Skipping integration build has caused post-merge build failures that required additional fix PRs and wasted review cycles.

---

## When this rule fires

In Phase 6 (Commit & PR), **before** the pre-commit local checkout prompt, if `state.projects.length >= 2`:

1. Compute `repoSet` = sorted array of touched repo names.
2. Look up `prefs.global.multiRepoIntegrationHosts` for an entry whose `repoSet` (sorted) equals this combo.
3. Match found → **auto-run** the learned build (steps below).
4. No match → **learn-once prompt** (below), then run.

Single-repo tasks skip this step entirely  -  no prompt, no overhead.

---

## Learn-once prompt (first encounter of a combo)

```
This task touched N repos: <name>, <name>, <name>
Do any of these need a host project (submodule consumer) built together
to verify codegen / interface integration?

  [1] Yes  -  register host project now (will auto-run on future runs)
  [2] No   -  this combo is independent (save as "no host" so we don't re-ask)
  [3] Skip this run only (no learning)

Select [1-3]:
```

**On [1]**  -  ask follow-ups:

```
Host project path (absolute): _______________________
Platform: [ios | android | mixed]
Scheme / target / module: _____________________________  (iOS: xcodebuild scheme · Android: gradle module)
Submodule paths to refresh (space-separated, relative to host):
  _____________________________________________________
Dependency resolve command (press enter for defaults):
  default ios:     xcodebuild -resolvePackageDependencies -skipMacroValidation
  default android: ./gradlew dependencies --refresh-dependencies
Build command (press enter for defaults):
  default ios:     xcodebuild -scheme <S> -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=latest' build -skipMacroValidation
  default android: ./gradlew :<module>:assembleDebug
```

Persist to `prefs.global.multiRepoIntegrationHosts[]`:

```json
{
  "repoSet": ["<repo-a>", "<repo-b>"],
  "hostPath": "/absolute/path/to/host",
  "platform": "ios",
  "hostScheme": "<Scheme>",
  "submodulePaths": [
    "<submodule-path-a>",
    "<submodule-path-b>"
  ],
  "resolveCommand": "xcodebuild -project <HostProject>.xcodeproj -scheme <Scheme> -resolvePackageDependencies -skipMacroValidation",
  "buildCommand": "xcodebuild -project <HostProject>.xcodeproj -scheme <Scheme> -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=latest' build -skipMacroValidation",
  "lastUsed": "2026-04-20T15:30:00Z",
  "count": 1,
  "lastResult": "success"
}
```

**On [2]**  -  persist a negative entry so the combo never re-asks:

```json
{ "repoSet": ["<repo-a>", "<repo-b>"], "noHost": true, "lastUsed": "..." }
```

**On [3]**  -  no write, re-ask next time.

---

## Auto-run steps (when host is learned)

Progress-contract line `→ integrating <host-scheme> with <N> submodules`. Tracker sub-step on Phase 6 to show live status: `phase-tracker.sh sub 6 0 "Integration build" in_progress`.

```bash
HOST="$(jq -r --arg key "$repoSet_sorted_joined" \
  '.global.multiRepoIntegrationHosts[]
   | select((.repoSet | sort | join(",")) == $key)' "$PREFS_FILE")"

HOST_PATH=$(echo "$HOST" | jq -r '.hostPath')
RESOLVE_CMD=$(echo "$HOST" | jq -r '.resolveCommand')
BUILD_CMD=$(echo "$HOST" | jq -r '.buildCommand')

cd "$HOST_PATH"

# 1. Pull latest submodules to pick up this task's changes
for sm in $(echo "$HOST" | jq -r '.submodulePaths[]'); do
  git submodule update --remote "$sm"
done

# 2. Resolve package dependencies (flush stale SPM/Gradle cache)
eval "$RESOLVE_CMD"

# 3. Run the host build, capture only error lines
BUILD_ERRORS=$(eval "$BUILD_CMD" 2>&1 | grep -E "error:|FAILURE:|error FS" || true)

# 4. Evaluate + decide
if [ -z "$BUILD_ERRORS" ]; then
  phase-tracker.sh sub 6 0 "Integration build" completed
  log "Phase 6.0: Integration build  -  clean"
  # proceed to pre-commit checkout prompt + commit
else
  phase-tracker.sh sub 6 0 "Integration build" failed
  log "Phase 6.0: Integration build  -  NEW errors detected"
  # show errors, ask user
fi
```

Update `lastUsed`, `count++`, `lastResult` after each run.

---

## Error evaluation contract

Three outcomes after the build:

1. **Zero errors** → sub-step `completed`, proceed to pre-commit prompt.
2. **New errors from our changes** → sub-step `failed`, STOP. Print the error lines, ask:
   ```
   Integration build failed with N new errors. Pipeline can go back to
   Phase 3 to fix, or you can fix manually and tell the pipeline to retry.

   [1] Return to Phase 3: Dev with these errors as input (auto-fix attempt)
   [2] Pause  -  I'll fix manually, then /multi-agent resume
   [3] Override  -  proceed to commit anyway (logs a warning in Phase 7 report)

   Select [1-3]:
   ```
3. **Pre-existing errors (unrelated to this task)** → if the pipeline has a baseline error count (from a clean pre-change build), compare deltas. When `current_errors <= baseline`, treat as **no new errors** and proceed; log the pre-existing count in Phase 7 Report.

---

## Autopilot behavior

Autopilot skips the "Override / pause" prompt. If `count` < 3 on this combo (new / low-confidence), autopilot treats a build failure as BLOCKING and returns to Phase 3 automatically (option 1). If `count >= 3` and `lastResult == success`, a new failure is treated as blocking the same way  -  but with lower surprise since we've seen this combo succeed before.

The learn-once prompt itself also skips under autopilot: instead, autopilot logs `"Phase 6.0: SKIPPED  -  no learned host for this combo, autopilot refuses to prompt. Run in normal mode once to teach."` and proceeds. This is explicit and recoverable.

---

## Phase 7 knowledge capture

When a learn-once prompt writes a new entry, Phase 7 Step 5 (knowledge + memory) ALSO writes a project-scoped memory:

```
Type: reference
Name: multi-repo integration host  -  {combo}
Body: For tasks touching {repoA} + {repoB}, the integration build runs in {hostPath} via
      scheme {hostScheme}. Established {date}. Full config lives in
      prefs.global.multiRepoIntegrationHosts.
```

Memories that reference integration hosts NEVER get auto-pruned  -  even if a combo goes many runs without firing, the knowledge is stable and worth keeping indexed for future teammates.

---

## Generic rule (for copilot-instructions.md)

> **Post-Development Integration Build (Multi-Repo)**
>
> When a task touches multiple repositories that have a producer→consumer dependency (e.g. shared library + consuming component library), the pipeline MUST build the **consumer/host project** after all changes are complete  -  before commit/PR.
>
> **When this applies:**
> - Code generation outputs (identifiers, localization keys, tokens) in Repo A are referenced by source code in Repo B
> - Repo B is consumed as a submodule or SPM/Gradle dependency by a host project (Repo C)
> - Changes in Repo A or B can silently break Repo C if key structures diverge (e.g. nested enum vs flat access pattern)
>
> **Required steps (after dev, before commit):**
> 1. Identify the host project  -  the project that integrates all touched repos as submodules or package dependencies. Check `prefs.global.multiRepoIntegrationHosts` first; ask the user once if unknown.
> 2. Update submodules to point at the latest feature branch / merged commit.
> 3. Resolve package dependencies (flush stale SPM/Gradle/CocoaPods cache).
> 4. Build the host project's relevant scheme / target.
> 5. Evaluate: new errors from our changes → fix before proceeding; pre-existing errors unrelated → document and proceed; zero new errors → continue to commit/PR.
>
> **Why this exists:** codegen mismatches only surface when the full dependency chain is built together. Building repos in isolation gives false confidence. Skipping this step has caused post-merge build failures requiring fix PRs + wasted review cycles.

---

## Schema

`prefs.schema.json` adds `global.multiRepoIntegrationHosts`  -  see `$HOME/.claude/schemas/prefs.schema.json` for the authoritative definition. Required fields: `repoSet` (array ≥2), one of `(hostPath + platform)` OR `(noHost: true)`. Optional: `hostScheme`, `submodulePaths`, `resolveCommand`, `buildCommand`, `lastUsed`, `count`, `lastResult`.

## Smoke coverage

`smoke-multi-repo-integration.sh` verifies:
- Match logic: sorted `repoSet` equality
- Negative-entry handling (`noHost: true`)
- Autopilot refuses to prompt, skips gracefully
- Schema validator accepts valid entries, rejects malformed (empty repoSet, missing hostPath without noHost)
- Memory write on first learn
