# Reconcile Mainline Divergence Fix — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Fix false-positive remote/local divergence detection when local main is ahead of origin, and surface `remote_local_divergence` errors as BLOCKED status with dashboard notification.

**Architecture:** Two changes — (1) replace SHA comparison in `checkRemoteLocalDivergence()` with `git merge-base --is-ancestor` to distinguish "local ahead" from true divergence, (2) add try/catch in `prepareFeaturesForPhase()` to patch features to BLOCKED and emit a new `remote_local_divergence` notification event.

**Tech Stack:** TypeScript, Vitest, git CLI

**Spec:** `docs/superpowers/specs/2026-03-23-reconcile-divergence-fix-design.md`

---

## File Structure

| File                                                                        | Action | Responsibility                                                                |
| --------------------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------- |
| `apps/control-plane/src/application/services/git-reconciliation-service.ts` | Modify | Add `local_ahead` to result type; replace SHA comparison with `--is-ancestor` |
| `apps/control-plane/src/application/services/notifier-service.ts`           | Modify | Add `remote_local_divergence` event type and critical priority                |
| `apps/control-plane/src/supervisor/run-coordinator.ts`                      | Modify | Add try/catch in `prepareFeaturesForPhase`; update `notifyStatusTransitions`  |
| `apps/control-plane/test/git-reconciliation-service.spec.ts`                | Modify | Add 4 new test cases for divergence detection                                 |
| `apps/control-plane/test/notifier-service.spec.ts`                          | Modify | Add event routing test                                                        |
| `apps/control-plane/test/run-coordinator.spec.ts`                           | Modify | Add 2 new test cases for error handling                                       |

---

### Task 1: Add `local_ahead` to `RemoteLocalDivergenceResult` type

**Files:**

- Modify: `apps/control-plane/src/application/services/git-reconciliation-service.ts:81-85`

- [ ] **Step 1: Update the type definition**

At line 81, change:

```typescript
export type RemoteLocalDivergenceResult = {
  diverged: boolean;
  local_head: string;
  remote_head: string;
};
```

to:

```typescript
export type RemoteLocalDivergenceResult = {
  diverged: boolean;
  local_ahead: boolean;
  local_head: string;
  remote_head: string;
};
```

- [ ] **Step 2: Run typecheck to see what breaks**

Run: `npm run typecheck`
Expected: Errors in `checkRemoteLocalDivergence` where `local_ahead` is missing from the return.

---

### Task 2: Replace SHA comparison with `--is-ancestor` check (TDD)

**Files:**

- Modify: `apps/control-plane/test/git-reconciliation-service.spec.ts`
- Modify: `apps/control-plane/src/application/services/git-reconciliation-service.ts:305-330`

- [ ] **Step 1: Write failing test — identical SHAs short-circuit**

Add inside the `describe('GitReconciliationService.checkRemoteLocalDivergence', ...)` block:

```typescript
it('GIVEN_identical_shas_WHEN_check_divergence_THEN_returns_not_diverged_and_not_local_ahead', async () => {
  runGitMock
    .mockResolvedValueOnce(cmdResult({ code: 0 })) // fetch
    .mockResolvedValueOnce(cmdResult({ code: 0, stdout: 'abc123\n' })) // rev-parse main
    .mockResolvedValueOnce(cmdResult({ code: 0, stdout: 'abc123\n' })); // rev-parse origin/main

  const svc = makeSvc();
  const result = await svc.checkRemoteLocalDivergence('main');

  expect(result.ok).toBe(true);
  if (!result.ok) return;
  expect(result.data.diverged).toBe(false);
  expect(result.data.local_ahead).toBe(false);
  // Should NOT call merge-base when SHAs are identical
  expect(runGitMock).toHaveBeenCalledTimes(3);
});
```

- [ ] **Step 2: Write failing test — local ahead (remote is ancestor)**

```typescript
it('GIVEN_local_ahead_of_remote_WHEN_check_divergence_THEN_returns_not_diverged_and_local_ahead', async () => {
  runGitMock
    .mockResolvedValueOnce(cmdResult({ code: 0 })) // fetch
    .mockResolvedValueOnce(cmdResult({ code: 0, stdout: 'localsha\n' })) // rev-parse main
    .mockResolvedValueOnce(cmdResult({ code: 0, stdout: 'remotesha\n' })) // rev-parse origin/main
    .mockResolvedValueOnce(cmdResult({ code: 0 })); // merge-base --is-ancestor exits 0

  const svc = makeSvc();
  const result = await svc.checkRemoteLocalDivergence('main');

  expect(result.ok).toBe(true);
  if (!result.ok) return;
  expect(result.data.diverged).toBe(false);
  expect(result.data.local_ahead).toBe(true);
  expect(runGitMock).toHaveBeenCalledWith(expect.any(String), [
    'merge-base',
    '--is-ancestor',
    'origin/main',
    'main',
  ]);
});
```

- [ ] **Step 3: Write failing test — true divergence**

```typescript
it('GIVEN_true_divergence_WHEN_check_divergence_THEN_returns_diverged', async () => {
  runGitMock
    .mockResolvedValueOnce(cmdResult({ code: 0 })) // fetch
    .mockResolvedValueOnce(cmdResult({ code: 0, stdout: 'localsha\n' })) // rev-parse main
    .mockResolvedValueOnce(cmdResult({ code: 0, stdout: 'remotesha\n' })) // rev-parse origin/main
    .mockResolvedValueOnce(cmdResult({ code: 1 })); // merge-base --is-ancestor exits 1

  const svc = makeSvc();
  const result = await svc.checkRemoteLocalDivergence('main');

  expect(result.ok).toBe(true);
  if (!result.ok) return;
  expect(result.data.diverged).toBe(true);
  expect(result.data.local_ahead).toBe(false);
});
```

- [ ] **Step 4: Write failing test — remote ref resolution failure fallback**

```typescript
it('GIVEN_remote_ref_unresolvable_WHEN_check_divergence_THEN_returns_not_diverged', async () => {
  runGitMock
    .mockResolvedValueOnce(cmdResult({ code: 0 })) // fetch
    .mockResolvedValueOnce(cmdResult({ code: 0, stdout: 'localsha\n' })) // rev-parse main
    .mockResolvedValueOnce(cmdResult({ code: 128, stderr: 'fatal: bad ref' })); // rev-parse origin/main fails

  const svc = makeSvc();
  const result = await svc.checkRemoteLocalDivergence('main');

  expect(result.ok).toBe(true);
  if (!result.ok) return;
  expect(result.data.diverged).toBe(false);
  expect(result.data.local_ahead).toBe(false);
  expect(result.data.remote_head).toBe('localsha');
  // Should NOT call merge-base when remote ref fails
  expect(runGitMock).toHaveBeenCalledTimes(3);
});
```

- [ ] **Step 5: Run tests to verify they fail**

Run: `npx vitest run apps/control-plane/test/git-reconciliation-service.spec.ts`
Expected: 4 new tests FAIL (missing `local_ahead` in result, no `merge-base` call).

- [ ] **Step 6: Implement the fix in `checkRemoteLocalDivergence`**

Replace lines 305-330 of `git-reconciliation-service.ts`:

```typescript
async checkRemoteLocalDivergence(
  baseRef: string,
): Promise<ToolResponse<RemoteLocalDivergenceResult>> {
  const repoRoot = this.port.getRepoRoot();

  await runGit(repoRoot, ['fetch', 'origin', baseRef]);

  const localResult = await runGit(repoRoot, ['rev-parse', baseRef]);
  const remoteResult = await runGit(repoRoot, ['rev-parse', `origin/${baseRef}`]);

  if (localResult.code !== 0) {
    return fail(ERROR_CODES.GIT_FAILURE, `Cannot resolve local ref ${baseRef}`, {
      retryable: false,
      requires_human: true,
    });
  }

  const localHead = localResult.stdout.trim();

  // Preserve fallback: if remote ref fails to resolve, treat as no divergence
  if (remoteResult.code !== 0) {
    return ok<RemoteLocalDivergenceResult>({
      diverged: false,
      local_ahead: false,
      local_head: localHead,
      remote_head: localHead,
    });
  }

  const remoteHead = remoteResult.stdout.trim();

  // Identical SHAs — short-circuit without merge-base call
  if (localHead === remoteHead) {
    return ok<RemoteLocalDivergenceResult>({
      diverged: false,
      local_ahead: false,
      local_head: localHead,
      remote_head: remoteHead,
    });
  }

  // Check if remote is an ancestor of local (local is ahead)
  const ancestorResult = await runGit(repoRoot, [
    'merge-base',
    '--is-ancestor',
    `origin/${baseRef}`,
    baseRef,
  ]);

  if (ancestorResult.code === 0) {
    // Remote is ancestor of local — local is ahead, no real divergence
    return ok<RemoteLocalDivergenceResult>({
      diverged: false,
      local_ahead: true,
      local_head: localHead,
      remote_head: remoteHead,
    });
  }

  // True divergence — remote has commits local doesn't
  return ok<RemoteLocalDivergenceResult>({
    diverged: true,
    local_ahead: false,
    local_head: localHead,
    remote_head: remoteHead,
  });
}
```

- [ ] **Step 7: Update existing tests to include `local_ahead` assertions**

The two pre-existing tests need `local_ahead` added to their assertions:

For the "same SHA" test: add `expect(result.data.local_ahead).toBe(false);`
For the "different SHA" test: this test previously asserted `diverged: true` because SHAs differ. Now with `--is-ancestor`, the mock needs a 4th call for `merge-base`. Update:

```typescript
it('GIVEN_local_and_remote_different_sha_WHEN_check_divergence_THEN_returns_diverged', async () => {
  runGitMock
    .mockResolvedValueOnce(cmdResult({ code: 0 })) // fetch
    .mockResolvedValueOnce(cmdResult({ code: 0, stdout: 'localsha\n' })) // rev-parse main
    .mockResolvedValueOnce(cmdResult({ code: 0, stdout: 'remotesha\n' })) // rev-parse origin/main
    .mockResolvedValueOnce(cmdResult({ code: 1 })); // merge-base --is-ancestor exits 1

  const svc = makeSvc();
  const result = await svc.checkRemoteLocalDivergence('main');

  expect(result.ok).toBe(true);
  if (!result.ok) return;
  expect(result.data.diverged).toBe(true);
  expect(result.data.local_ahead).toBe(false);
  expect(result.data.local_head).toBe('localsha');
  expect(result.data.remote_head).toBe('remotesha');
});
```

- [ ] **Step 8: Run tests to verify they pass**

Run: `npx vitest run apps/control-plane/test/git-reconciliation-service.spec.ts`
Expected: All tests PASS.

- [ ] **Step 9: Run full typecheck**

Run: `npm run typecheck`
Expected: PASS — kernel.ts call sites only inspect `diverged`, so adding `local_ahead` is backward-compatible.

- [ ] **Step 10: Commit**

```bash
git add apps/control-plane/src/application/services/git-reconciliation-service.ts apps/control-plane/test/git-reconciliation-service.spec.ts
git commit -m "fix: replace SHA comparison with merge-base --is-ancestor in divergence check

Local main ahead of origin/main no longer triggers false-positive
remote_local_divergence errors."
```

---

### Task 3: Add `remote_local_divergence` notification event (TDD)

**Files:**

- Modify: `apps/control-plane/test/notifier-service.spec.ts`
- Modify: `apps/control-plane/src/application/services/notifier-service.ts:8-18,60-71`

- [ ] **Step 1: Write failing test — event routes as critical**

Add inside the `describe('NotifierService', ...)` block:

```typescript
it('GIVEN_notifications_enabled_WHEN_remote_local_divergence_THEN_routes_as_critical', async () => {
  const fetchSpy = vi
    .spyOn(globalThis, 'fetch')
    .mockResolvedValue(new Response('ok', { status: 200 }));

  const config: NotificationConfig = {
    enabled: true,
    channels: {
      slack: { enabled: true, webhook: 'https://hooks.slack.com/test', channel: '#alerts' },
    },
  };
  const service = new NotifierService(config);

  await service.notify('remote_local_divergence', {
    feature_id: 'feat_diverged',
    message: 'Local main diverges from origin/main',
  });

  expect(fetchSpy).toHaveBeenCalledTimes(1);
  const body = JSON.parse((fetchSpy.mock.calls[0][1] as RequestInit).body as string) as {
    text: string;
  };
  expect(body.text).toContain('[CRITICAL]');
  expect(body.text).toContain('feat_diverged');
  fetchSpy.mockRestore();
});
```

- [ ] **Step 2: Run test to verify it fails**

Run: `npx vitest run apps/control-plane/test/notifier-service.spec.ts`
Expected: FAIL — TypeScript error: `'remote_local_divergence'` is not assignable to `NotificationEvent`.

- [ ] **Step 3: Add event type and priority mapping**

In `notifier-service.ts`, update the `NotificationEvent` type (line 8):

```typescript
export type NotificationEvent =
  | 'gate_failed'
  | 'collision_detected'
  | 'feature_blocked'
  | 'ready_to_merge'
  | 'feature_merged'
  | 'stale_lease'
  | 'agent_stuck'
  | 'changes_requested'
  | 'budget_exceeded'
  | 'budget_alert'
  | 'remote_local_divergence';
```

In `EVENT_PRIORITY` (line 60), add the entry:

```typescript
const EVENT_PRIORITY: Record<NotificationEvent, CanonicalPriority> = {
  gate_failed: 'critical',
  collision_detected: 'critical',
  feature_blocked: 'warning',
  ready_to_merge: 'info',
  feature_merged: 'info',
  stale_lease: 'warning',
  agent_stuck: 'warning',
  changes_requested: 'warning',
  budget_exceeded: 'critical',
  budget_alert: 'warning',
  remote_local_divergence: 'critical',
};
```

- [ ] **Step 4: Run test to verify it passes**

Run: `npx vitest run apps/control-plane/test/notifier-service.spec.ts`
Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add apps/control-plane/src/application/services/notifier-service.ts apps/control-plane/test/notifier-service.spec.ts
git commit -m "feat: add remote_local_divergence notification event with critical priority"
```

---

### Task 4: Add try/catch in `prepareFeaturesForPhase` and update `notifyStatusTransitions` (TDD)

**Files:**

- Modify: `apps/control-plane/test/run-coordinator.spec.ts`
- Modify: `apps/control-plane/src/supervisor/run-coordinator.ts:854-867,913-938,962-968`

- [ ] **Step 1: Write failing test — feature blocks on divergence error**

Find the test file's describe block structure and add a new describe section. The test needs to set up a toolCaller mock that throws when `REPO_RECONCILE_MAINLINE` is called, and verify the feature gets patched to BLOCKED and a notification is emitted.

```typescript
describe('prepareFeaturesForPhase divergence handling', () => {
  it('GIVEN_reconcile_throws_remote_local_divergence_WHEN_prepare_phase_THEN_patches_to_blocked_and_notifies', async () => {
    const notifySpy = vi.fn();
    const notifier = { notify: notifySpy } as unknown as NotifierService;

    const callToolMock = vi.fn(async (_role: string, toolName: string, args: Record<string, unknown>) => {
      if (toolName === TOOLS.FEATURE_STATE_GET) {
        return {
          ok: true,
          data: {
            front_matter: {
              status: 'building',
              version: 1,
            },
          },
        };
      }
      if (toolName === TOOLS.REPO_RECONCILE_MAINLINE) {
        const err = new Error('Local main differs from origin/main') as Error & { code?: string };
        err.code = 'remote_local_divergence';
        throw err;
      }
      if (toolName === TOOLS.FEATURE_STATE_PATCH) {
        return { ok: true, data: {} };
      }
      if (toolName === TOOLS.REPORT_DASHBOARD) {
        return { ok: true, data: { features: [] } };
      }
      return { ok: true, data: {} };
    });

    const toolCaller = { callTool: callToolMock };
    const kernel = {
      getPolicySnapshot: () => ({
        reconciliation: {
          enabled: true,
          check_before_phases: ['building'],
          auto_merge_non_conflicting: true,
        },
      }),
    };

    const coordinator = new RunCoordinator(
      kernel as unknown as Parameters<typeof RunCoordinator['prototype']['constructor']>[0],
      toolCaller as unknown as ToolCaller,
      { notifier },
    );

    const eligible = await (coordinator as any).prepareFeaturesForPhase(
      ['feat_diverged'],
      'building',
    );

    expect(eligible).toEqual([]);

    // Verify FEATURE_STATE_PATCH was called to set BLOCKED
    const patchCall = callToolMock.mock.calls.find(
      (c) => c[1] === TOOLS.FEATURE_STATE_PATCH,
    );
    expect(patchCall).toBeDefined();
    const patchArgs = patchCall![2] as Record<string, unknown>;
    expect(patchArgs.feature_id).toBe('feat_diverged');
    const patch = patchArgs.patch as Record<string, unknown>;
    const fm = patch.front_matter as Record<string, unknown>;
    expect(fm.status).toBe('blocked');
    expect(fm.status_reason).toContain('remote_local_divergence');

    // Verify notification was emitted
    expect(notifySpy).toHaveBeenCalledWith(
      'remote_local_divergence',
      expect.objectContaining({
        feature_id: 'feat_diverged',
      }),
    );
  });
```

- [ ] **Step 2: Write failing test — other features still processed after one divergence**

```typescript
  it('GIVEN_first_feature_throws_divergence_WHEN_prepare_phase_THEN_second_feature_still_evaluated', async () => {
    const notifySpy = vi.fn();
    const notifier = { notify: notifySpy } as unknown as NotifierService;

    let reconcileCallCount = 0;
    const callToolMock = vi.fn(async (_role: string, toolName: string, args: Record<string, unknown>) => {
      if (toolName === TOOLS.FEATURE_STATE_GET) {
        return {
          ok: true,
          data: {
            front_matter: {
              status: 'building',
              version: 1,
            },
          },
        };
      }
      if (toolName === TOOLS.REPO_RECONCILE_MAINLINE) {
        reconcileCallCount++;
        if (reconcileCallCount === 1) {
          const err = new Error('diverged') as Error & { code?: string };
          err.code = 'remote_local_divergence';
          throw err;
        }
        return { ok: true, data: { status: 'up_to_date' } };
      }
      if (toolName === TOOLS.FEATURE_STATE_PATCH) {
        return { ok: true, data: {} };
      }
      if (toolName === TOOLS.REPORT_DASHBOARD) {
        return { ok: true, data: { features: [] } };
      }
      return { ok: true, data: {} };
    });

    const toolCaller = { callTool: callToolMock };
    const kernel = {
      getPolicySnapshot: () => ({
        reconciliation: {
          enabled: true,
          check_before_phases: ['building'],
          auto_merge_non_conflicting: true,
        },
      }),
    };

    const coordinator = new RunCoordinator(
      kernel as unknown as Parameters<typeof RunCoordinator['prototype']['constructor']>[0],
      toolCaller as unknown as ToolCaller,
      { notifier },
    );

    const eligible = await (coordinator as any).prepareFeaturesForPhase(
      ['feat_bad', 'feat_good'],
      'building',
    );

    expect(eligible).toEqual(['feat_good']);
    expect(reconcileCallCount).toBe(2);
  });
});
```

- [ ] **Step 3: Run tests to verify they fail**

Run: `npx vitest run apps/control-plane/test/run-coordinator.spec.ts`
Expected: FAIL — the thrown error is unhandled (no try/catch exists in `prepareFeaturesForPhase`).

- [ ] **Step 4: Implement try/catch in `prepareFeaturesForPhase`**

In `run-coordinator.ts`, replace lines 854-867:

```typescript
try {
  const reconcile = await this.toolCaller.callTool<{
    status?: string;
  }>('orchestrator', TOOLS.REPO_RECONCILE_MAINLINE, {
    feature_id: featureId,
    auto_merge_non_conflicting: autoMergeNonConflicting,
    commit_message: RunCoordinator.buildPhaseReconciliationCheckpointMessage(featureId, phase),
  });
  if (reconcile.data.status === 'conflict_detected') {
    console.warn(
      `[reconciliation] ${phase} preflight blocked ${featureId}: mainline conflict detected`,
    );
    continue;
  }
} catch (err: unknown) {
  const code = err instanceof Error && 'code' in err ? (err as Error & { code: string }).code : '';
  if (code === 'remote_local_divergence') {
    console.warn(
      `[reconciliation] ${phase} preflight blocked ${featureId}: remote/local mainline divergence`,
    );
    try {
      const currentState = await this.toolCaller.callTool<FeatureStatePayload>(
        'orchestrator',
        TOOLS.FEATURE_STATE_GET,
        { feature_id: featureId },
      );
      const currentVersion =
        typeof currentState.data.front_matter?.version === 'number'
          ? currentState.data.front_matter.version
          : 0;
      await this.toolCaller.callTool('orchestrator', TOOLS.FEATURE_STATE_PATCH, {
        feature_id: featureId,
        expected_version: currentVersion,
        patch: {
          front_matter: {
            status: 'blocked',
            status_reason: 'remote_local_divergence',
          },
        },
      });
    } catch {
      // Best-effort patch — feature remains in previous status
    }
    if (this.notifier) {
      await this.notifier.notify('remote_local_divergence', {
        feature_id: featureId,
        message: `Local main diverges from origin/main for feature '${featureId}' — pull or sync remote changes to unblock.`,
      });
    }
    continue;
  }
  throw err;
}
```

- [ ] **Step 5: Update `readFeatureStateSnapshot` to return `statusReason`**

In `run-coordinator.ts`, update the return type and body of `readFeatureStateSnapshot` (lines 913-938):

Change the return type from:

```typescript
): Promise<{ status: string | null; branch: string; hasPr: boolean }> {
```

to:

```typescript
): Promise<{ status: string | null; statusReason: string | null; branch: string; hasPr: boolean }> {
```

Inside the try block, after `const status = ...` (line 925), add:

```typescript
const statusReason =
  typeof frontMatter.status_reason === 'string' ? frontMatter.status_reason : null;
```

Update the return (line 934) to include `statusReason`:

```typescript
return { status, statusReason, branch, hasPr };
```

Update the catch return (line 936) to include `statusReason: null`:

```typescript
return { status: null, statusReason: null, branch: featureId, hasPr: false };
```

- [ ] **Step 6: Update `notifyStatusTransitions` to use `remote_local_divergence` event**

In `notifyStatusTransitions` (lines 962-968), replace:

```typescript
        if (newStatus === STATUS.BLOCKED) {
          const event = prevStatus === STATUS.PLANNING ? 'collision_detected' : 'gate_failed';
          await this.notifier.notify(event, {
            feature_id: featureId,
            message: `Feature ${featureId} blocked (event: ${event})`,
          });
```

with:

```typescript
        if (newStatus === STATUS.BLOCKED) {
          const event =
            snapshot.statusReason === 'remote_local_divergence'
              ? 'remote_local_divergence'
              : prevStatus === STATUS.PLANNING
                ? 'collision_detected'
                : 'gate_failed';
          await this.notifier.notify(event, {
            feature_id: featureId,
            message:
              event === 'remote_local_divergence'
                ? `Local main diverges from origin/main for feature '${featureId}' — pull or sync remote changes to unblock.`
                : `Feature ${featureId} blocked (event: ${event})`,
          });
```

- [ ] **Step 7: Run tests to verify they pass**

Run: `npx vitest run apps/control-plane/test/run-coordinator.spec.ts`
Expected: All tests PASS including the 2 new ones.

- [ ] **Step 8: Commit**

```bash
git add apps/control-plane/src/supervisor/run-coordinator.ts apps/control-plane/test/run-coordinator.spec.ts
git commit -m "feat: block feature and notify on remote_local_divergence in prepareFeaturesForPhase

Wraps reconcile_mainline callTool in try/catch, patches feature to
BLOCKED with status_reason, emits critical notification, and continues
processing remaining features."
```

---

### Task 5: Update kernel divergence test

**Files:**

- Modify: `apps/control-plane/test/kernel.coverage2.spec.ts`

- [ ] **Step 1: Find and update the existing divergence test**

Find the test `GIVEN_remote_local_diverged_and_no_strategy_WHEN_featureReadyToMerge_called_THEN_returns_remote_local_divergence` (around line 1280). The mock for `checkRemoteLocalDivergence` needs to include `local_ahead: false` in its return:

Ensure the mock returns:

```typescript
{ diverged: true, local_ahead: false, local_head: 'localsha', remote_head: 'remotesha' }
```

- [ ] **Step 2: Add a test for local-ahead scenario succeeding**

Add a test near the existing divergence test:

```typescript
it('GIVEN_local_ahead_of_remote_and_no_strategy_WHEN_featureReadyToMerge_called_THEN_proceeds_without_error', async () => {
  // Mock checkRemoteLocalDivergence to return local_ahead=true, diverged=false
  // Verify featureReadyToMerge does NOT throw remote_local_divergence
  // and proceeds to the merge service call
});
```

The exact mock setup depends on the test's existing factory patterns — follow the same pattern used by the adjacent divergence test.

- [ ] **Step 3: Run the test**

Run: `npx vitest run apps/control-plane/test/kernel.coverage2.spec.ts`
Expected: PASS.

- [ ] **Step 4: Commit**

```bash
git add apps/control-plane/test/kernel.coverage2.spec.ts
git commit -m "test: add kernel coverage for local-ahead divergence scenario"
```

---

### Task 6: Full validation

- [ ] **Step 1: Run full test suite**

Run: `npm run test`
Expected: All tests pass with coverage thresholds met.

- [ ] **Step 2: Run lint**

Run: `npm run lint`
Expected: 0 warnings, 0 errors.

- [ ] **Step 3: Run typecheck**

Run: `npm run typecheck`
Expected: PASS.

- [ ] **Step 4: Run architecture validator**

Run: `npm run validate:architecture`
Expected: PASS (silent).

- [ ] **Step 5: Update progress.md**

Append a new entry to `spec-files/progress.md`:

```
✅ **Entry 177 — Fix false-positive remote/local divergence + add dashboard notification**
   - **Goal:** Stop `checkRemoteLocalDivergence` from erroring when local main is ahead of origin, and surface `remote_local_divergence` errors as BLOCKED status with critical notification.
   - **Changes made:**
     - `apps/control-plane/src/application/services/git-reconciliation-service.ts` — Replaced SHA comparison with `git merge-base --is-ancestor` check; added `local_ahead` to `RemoteLocalDivergenceResult`.
     - `apps/control-plane/src/application/services/notifier-service.ts` — Added `remote_local_divergence` to `NotificationEvent` with `critical` priority.
     - `apps/control-plane/src/supervisor/run-coordinator.ts` — Added try/catch in `prepareFeaturesForPhase` to patch feature to BLOCKED and emit notification on divergence error; updated `notifyStatusTransitions` to use specific event.
     - Tests: 8 new test cases across git-reconciliation-service, notifier-service, run-coordinator, and kernel.coverage2 spec files.
   - **Result:** `npm test` ✅. `npm run lint` ✅. `npm run typecheck` ✅.
```

- [ ] **Step 6: Final commit**

```bash
git add spec-files/progress.md
git commit -m "docs: update progress.md with entry 177 — divergence detection fix"
```
