/**
 * Tests for scripts/lib/install-lifecycle.js
 */

const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');

const {
  buildDoctorReport,
  discoverInstalledStates,
  normalizeTargets,
  repairInstalledStates,
  uninstallInstalledStates,
} = require('../../scripts/lib/install-lifecycle');
const { getInstallTargetAdapter } = require('../../scripts/lib/install-targets/registry');
const {
  createInstallState,
  readInstallState,
  writeInstallState,
} = require('../../scripts/lib/install-state');

const REPO_ROOT = path.join(__dirname, '..', '..');
const CURRENT_PACKAGE_VERSION = JSON.parse(
  fs.readFileSync(path.join(REPO_ROOT, 'package.json'), 'utf8')
).version;
const CURRENT_MANIFEST_VERSION = JSON.parse(
  fs.readFileSync(path.join(REPO_ROOT, 'manifests', 'install-modules.json'), 'utf8')
).version;

function test(name, fn) {
  try {
    fn();
    console.log(`  \u2713 ${name}`);
    return true;
  } catch (error) {
    console.log(`  \u2717 ${name}`);
    console.log(`    Error: ${error.message}`);
    return false;
  }
}

function createTempDir(prefix) {
  return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
}

function cleanup(dirPath) {
  fs.rmSync(dirPath, { recursive: true, force: true });
}

function writeState(filePath, options) {
  const state = createInstallState(options);
  writeInstallState(filePath, state);
  return state;
}

function createCursorStateOptions(projectRoot, overrides = {}) {
  const targetRoot = overrides.targetRoot || path.join(projectRoot, '.cursor');
  const installStatePath = overrides.installStatePath || path.join(targetRoot, 'ecc-install-state.json');

  return {
    adapter: { id: 'cursor-project', target: 'cursor', kind: 'project' },
    targetRoot,
    installStatePath,
    request: {
      profile: null,
      modules: [],
      includeComponents: [],
      excludeComponents: [],
      legacyLanguages: ['typescript'],
      legacyMode: true,
      ...(overrides.request || {}),
    },
    resolution: {
      selectedModules: ['legacy-cursor-install'],
      skippedModules: [],
      ...(overrides.resolution || {}),
    },
    operations: overrides.operations || [],
    source: {
      repoVersion: CURRENT_PACKAGE_VERSION,
      repoCommit: 'abc123',
      manifestVersion: CURRENT_MANIFEST_VERSION,
      ...(overrides.source || {}),
    },
  };
}

function writeCursorState(projectRoot, overrides = {}) {
  const options = createCursorStateOptions(projectRoot, overrides);
  writeState(options.installStatePath, options);
  return {
    targetRoot: options.targetRoot,
    installStatePath: options.installStatePath,
    state: options,
  };
}

function createOpencodeStateOptions(homeDir, overrides = {}) {
  const targetRoot = overrides.targetRoot || path.join(homeDir, '.opencode');
  const installStatePath = overrides.installStatePath || path.join(targetRoot, 'ecc-install-state.json');

  return {
    adapter: { id: 'opencode-home', target: 'opencode', kind: 'home' },
    targetRoot,
    installStatePath,
    request: {
      profile: null,
      modules: ['commands-core'],
      includeComponents: [],
      excludeComponents: [],
      legacyLanguages: [],
      legacyMode: false,
      ...(overrides.request || {}),
    },
    resolution: {
      selectedModules: ['commands-core'],
      skippedModules: [],
      ...(overrides.resolution || {}),
    },
    operations: overrides.operations || [],
    source: {
      repoVersion: CURRENT_PACKAGE_VERSION,
      repoCommit: 'abc123',
      manifestVersion: CURRENT_MANIFEST_VERSION,
      ...(overrides.source || {}),
    },
  };
}

function writeOpencodeState(homeDir, overrides = {}) {
  const options = createOpencodeStateOptions(homeDir, overrides);
  writeState(options.installStatePath, options);
  return {
    targetRoot: options.targetRoot,
    installStatePath: options.installStatePath,
    state: options,
  };
}

function withTemporarilyMovedPath(filePath, callback) {
  if (!fs.existsSync(filePath)) {
    try {
      return callback(null);
    } finally {
      if (fs.existsSync(filePath)) {
        fs.rmSync(filePath, { recursive: true, force: true });
      }
    }
  }

  const backupPath = `${filePath}.backup-${process.pid}-${Date.now()}`;
  fs.renameSync(filePath, backupPath);

  try {
    return callback(backupPath);
  } finally {
    if (fs.existsSync(filePath)) {
      fs.rmSync(filePath, { recursive: true, force: true });
    }
    if (fs.existsSync(backupPath)) {
      fs.renameSync(backupPath, filePath);
    }
  }
}

function managedOperation(kind, destinationPath, overrides = {}) {
  return {
    kind,
    moduleId: 'test-module',
    sourceRelativePath: 'rules/common/coding-style.md',
    destinationPath,
    strategy: kind,
    ownership: 'managed',
    scaffoldOnly: false,
    ...overrides,
  };
}

function runTests() {
  console.log('\n=== Testing install-lifecycle.js ===\n');

  let passed = 0;
  let failed = 0;

  if (test('normalizes default targets and dedupes adapter aliases', () => {
    const defaultTargets = normalizeTargets();

    assert.ok(defaultTargets.includes('claude'));
    assert.ok(defaultTargets.includes('cursor'));
    assert.ok(defaultTargets.includes('codex'));
    assert.deepStrictEqual(
      normalizeTargets(['cursor-project', 'cursor', 'claude-home', 'claude']),
      ['cursor', 'claude']
    );
  })) passed++; else failed++;

  if (test('discovers installed states for multiple targets in the current context', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const claudeStatePath = path.join(homeDir, '.claude', 'ecc', 'install-state.json');
      const cursorStatePath = path.join(projectRoot, '.cursor', 'ecc-install-state.json');

      writeState(claudeStatePath, {
        adapter: { id: 'claude-home', target: 'claude', kind: 'home' },
        targetRoot: path.join(homeDir, '.claude'),
        installStatePath: claudeStatePath,
        request: {
          profile: null,
          modules: [],
          legacyLanguages: ['typescript'],
          legacyMode: true,
        },
        resolution: {
          selectedModules: ['legacy-claude-rules'],
          skippedModules: [],
        },
        operations: [],
        source: {
          repoVersion: CURRENT_PACKAGE_VERSION,
          repoCommit: 'abc123',
          manifestVersion: CURRENT_MANIFEST_VERSION,
        },
      });

      writeState(cursorStatePath, {
        adapter: { id: 'cursor-project', target: 'cursor', kind: 'project' },
        targetRoot: path.join(projectRoot, '.cursor'),
        installStatePath: cursorStatePath,
        request: {
          profile: 'core',
          modules: [],
          legacyLanguages: [],
          legacyMode: false,
        },
        resolution: {
          selectedModules: ['rules-core', 'platform-configs'],
          skippedModules: [],
        },
        operations: [],
        source: {
          repoVersion: CURRENT_PACKAGE_VERSION,
          repoCommit: 'def456',
          manifestVersion: CURRENT_MANIFEST_VERSION,
        },
      });

      const records = discoverInstalledStates({
        homeDir,
        projectRoot,
        targets: ['claude', 'cursor'],
      });

      assert.strictEqual(records.length, 2);
      assert.strictEqual(records[0].exists, true);
      assert.strictEqual(records[1].exists, true);
      assert.strictEqual(records[0].state.target.id, 'claude-home');
      assert.strictEqual(records[1].state.target.id, 'cursor-project');
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('discovers missing and invalid install-state records', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      let records = discoverInstalledStates({
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(records.length, 1);
      assert.strictEqual(records[0].exists, false);
      assert.strictEqual(records[0].state, null);
      assert.strictEqual(records[0].error, null);

      const targetRoot = path.join(projectRoot, '.cursor');
      const statePath = path.join(targetRoot, 'ecc-install-state.json');
      fs.mkdirSync(targetRoot, { recursive: true });
      fs.writeFileSync(statePath, '{not-json', 'utf8');

      records = discoverInstalledStates({
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(records[0].exists, true);
      assert.strictEqual(records[0].state, null);
      assert.ok(records[0].error.includes('Failed to read install-state'));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('doctor reports missing managed files as an error', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const statePath = path.join(targetRoot, 'ecc-install-state.json');
      fs.mkdirSync(targetRoot, { recursive: true });

      writeState(statePath, {
        adapter: { id: 'cursor-project', target: 'cursor', kind: 'project' },
        targetRoot,
        installStatePath: statePath,
        request: {
          profile: null,
          modules: ['platform-configs'],
          legacyLanguages: [],
          legacyMode: false,
        },
        resolution: {
          selectedModules: ['platform-configs'],
          skippedModules: [],
        },
        operations: [
          {
            kind: 'copy-file',
            moduleId: 'platform-configs',
            sourceRelativePath: '.cursor/hooks.json',
            destinationPath: path.join(targetRoot, 'hooks.json'),
            strategy: 'sync-root-children',
            ownership: 'managed',
            scaffoldOnly: false,
          },
        ],
        source: {
          repoVersion: CURRENT_PACKAGE_VERSION,
          repoCommit: 'abc123',
          manifestVersion: CURRENT_MANIFEST_VERSION,
        },
      });

      const report = buildDoctorReport({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(report.results.length, 1);
      assert.strictEqual(report.results[0].status, 'error');
      assert.ok(report.results[0].issues.some(issue => issue.code === 'missing-managed-files'));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('doctor reports target mismatches, missing sources, unverified operations, and version drift', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const actualTargetRoot = path.join(projectRoot, '.cursor');
      const actualStatePath = path.join(actualTargetRoot, 'ecc-install-state.json');
      const recordedTargetRoot = path.join(projectRoot, '.old-cursor');
      const recordedStatePath = path.join(recordedTargetRoot, 'state.json');
      const copyDestination = path.join(actualTargetRoot, 'rules', 'missing-source.md');
      const customDestination = path.join(actualTargetRoot, 'custom.txt');

      fs.mkdirSync(path.dirname(copyDestination), { recursive: true });
      fs.writeFileSync(copyDestination, 'managed copy\n');
      fs.writeFileSync(customDestination, 'custom\n');

      writeState(actualStatePath, createCursorStateOptions(projectRoot, {
        targetRoot: recordedTargetRoot,
        installStatePath: recordedStatePath,
        request: {
          profile: 'missing-profile',
          legacyLanguages: [],
          legacyMode: false,
        },
        resolution: {
          selectedModules: [],
          skippedModules: [],
        },
        source: {
          repoVersion: '0.0.1',
          manifestVersion: CURRENT_MANIFEST_VERSION + 100,
        },
        operations: [
          managedOperation('copy-file', copyDestination, {
            sourceRelativePath: 'missing/source.md',
            strategy: 'copy-file',
          }),
          managedOperation('custom-kind', customDestination),
        ],
      }));

      const report = buildDoctorReport({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });
      const codes = report.results[0].issues.map(issue => issue.code);

      assert.strictEqual(report.results[0].status, 'error');
      assert.ok(codes.includes('missing-target-root'));
      assert.ok(codes.includes('target-root-mismatch'));
      assert.ok(codes.includes('install-state-path-mismatch'));
      assert.ok(codes.includes('missing-source-files'));
      assert.ok(codes.includes('unverified-managed-operations'));
      assert.ok(codes.includes('manifest-version-mismatch'));
      assert.ok(codes.includes('repo-version-mismatch'));
      assert.ok(codes.includes('resolution-unavailable'));
      assert.strictEqual(report.summary.checkedCount, 1);
      assert.ok(report.summary.errorCount >= 3);
      assert.ok(report.summary.warningCount >= 4);
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('doctor verifies render-template and merge-json operations by content', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const templatePath = path.join(targetRoot, 'generated.txt');
      const jsonPath = path.join(targetRoot, 'settings.json');
      fs.mkdirSync(targetRoot, { recursive: true });
      fs.writeFileSync(templatePath, 'generated\n');
      fs.writeFileSync(jsonPath, JSON.stringify({
        keep: true,
        nested: {
          managed: true,
          extra: true,
        },
      }, null, 2));

      writeCursorState(projectRoot, {
        operations: [
          managedOperation('render-template', templatePath, {
            renderedContent: 'generated\n',
          }),
          managedOperation('merge-json', jsonPath, {
            mergePayload: {
              nested: {
                managed: true,
              },
            },
          }),
        ],
      });

      const report = buildDoctorReport({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(report.results[0].status, 'ok');
      assert.strictEqual(report.results[0].issues.length, 0);
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('doctor classifies remove, unverified template/json, and invalid JSON operation health', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const templatePath = path.join(targetRoot, 'template.txt');
      const missingPayloadJsonPath = path.join(targetRoot, 'missing-payload.json');
      const invalidJsonPath = path.join(targetRoot, 'invalid.json');
      const removedPath = path.join(targetRoot, 'already-removed.txt');
      fs.mkdirSync(targetRoot, { recursive: true });
      fs.writeFileSync(templatePath, 'generated\n');
      fs.writeFileSync(missingPayloadJsonPath, '{"managed":true}\n');
      fs.writeFileSync(invalidJsonPath, '{not-json', 'utf8');

      writeCursorState(projectRoot, {
        operations: [
          managedOperation('remove', removedPath),
          managedOperation('render-template', templatePath),
          managedOperation('merge-json', missingPayloadJsonPath),
          managedOperation('merge-json', invalidJsonPath, {
            mergePayload: { managed: true },
          }),
        ],
      });

      const report = buildDoctorReport({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });
      const codes = report.results[0].issues.map(issue => issue.code);

      assert.strictEqual(report.results[0].status, 'warning');
      assert.ok(codes.includes('unverified-managed-operations'));
      assert.ok(codes.includes('drifted-managed-files'));
      assert.ok(!report.results[0].issues.some(issue => issue.code === 'missing-managed-files'));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('doctor reports invalid install-state files as errors', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const statePath = path.join(projectRoot, '.cursor', 'ecc-install-state.json');
      fs.mkdirSync(path.dirname(statePath), { recursive: true });
      fs.writeFileSync(statePath, '{"schemaVersion":"wrong"}\n');

      const report = buildDoctorReport({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(report.results[0].status, 'error');
      assert.ok(report.results[0].issues.some(issue => issue.code === 'invalid-install-state'));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('doctor reports a healthy legacy install when managed files are present', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(homeDir, '.claude');
      const statePath = path.join(targetRoot, 'ecc', 'install-state.json');
      const managedFile = path.join(targetRoot, 'rules', 'common', 'coding-style.md');
      const sourceContent = fs.readFileSync(path.join(REPO_ROOT, 'rules', 'common', 'coding-style.md'), 'utf8');
      fs.mkdirSync(path.dirname(managedFile), { recursive: true });
      fs.writeFileSync(managedFile, sourceContent);

      writeState(statePath, {
        adapter: { id: 'claude-home', target: 'claude', kind: 'home' },
        targetRoot,
        installStatePath: statePath,
        request: {
          profile: null,
          modules: [],
          legacyLanguages: ['typescript'],
          legacyMode: true,
        },
        resolution: {
          selectedModules: ['legacy-claude-rules'],
          skippedModules: [],
        },
        operations: [
          {
            kind: 'copy-file',
            moduleId: 'legacy-claude-rules',
            sourceRelativePath: 'rules/common/coding-style.md',
            destinationPath: managedFile,
            strategy: 'preserve-relative-path',
            ownership: 'managed',
            scaffoldOnly: false,
          },
        ],
        source: {
          repoVersion: CURRENT_PACKAGE_VERSION,
          repoCommit: 'abc123',
          manifestVersion: CURRENT_MANIFEST_VERSION,
        },
      });

      const report = buildDoctorReport({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['claude'],
      });

      assert.strictEqual(report.results.length, 1);
      assert.strictEqual(report.results[0].status, 'ok');
      assert.strictEqual(report.results[0].issues.length, 0);
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('repair dry-run reports planned copy repairs without writing files', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const destinationPath = path.join(targetRoot, 'rules', 'coding-style.md');
      writeCursorState(projectRoot, {
        operations: [
          managedOperation('copy-file', destinationPath, {
            sourceRelativePath: 'rules/common/coding-style.md',
            strategy: 'copy-file',
          }),
        ],
      });

      const result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
        dryRun: true,
      });

      assert.strictEqual(result.dryRun, true);
      assert.strictEqual(result.results[0].status, 'planned');
      assert.deepStrictEqual(result.results[0].plannedRepairs, [destinationPath]);
      assert.ok(!fs.existsSync(destinationPath));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('Claude repair and dry-run preserve user-owned flat skills during legacy migration', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(homeDir, '.claude');
      const installStatePath = path.join(targetRoot, 'ecc', 'install-state.json');
      const flatSkillPath = path.join(targetRoot, 'skills', 'tdd-workflow', 'SKILL.md');
      const legacySkillPath = path.join(
        targetRoot,
        'skills',
        'ecc',
        'tdd-workflow',
        'SKILL.md'
      );
      fs.mkdirSync(path.dirname(flatSkillPath), { recursive: true });
      fs.mkdirSync(path.dirname(legacySkillPath), { recursive: true });
      fs.writeFileSync(flatSkillPath, '# User-owned flat skill\n');
      fs.writeFileSync(legacySkillPath, '# Previously managed nested skill\n');

      writeState(installStatePath, {
        adapter: { id: 'claude-home', target: 'claude', kind: 'home' },
        targetRoot,
        installStatePath,
        request: {
          profile: null,
          modules: ['workflow-quality'],
          includeComponents: [],
          excludeComponents: [],
          legacyLanguages: [],
          legacyMode: false,
        },
        resolution: {
          selectedModules: ['platform-configs', 'workflow-quality'],
          skippedModules: [],
        },
        operations: [{
          kind: 'copy-file',
          moduleId: 'workflow-quality',
          sourcePath: path.join(REPO_ROOT, 'skills', 'tdd-workflow', 'SKILL.md'),
          sourceRelativePath: path.join('skills', 'tdd-workflow', 'SKILL.md'),
          destinationPath: legacySkillPath,
          strategy: 'preserve-relative-path',
          ownership: 'managed',
          scaffoldOnly: false,
        }],
        source: {
          repoVersion: CURRENT_PACKAGE_VERSION,
          repoCommit: 'abc123',
          manifestVersion: CURRENT_MANIFEST_VERSION,
        },
      });

      const dryRun = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['claude'],
        dryRun: true,
      });
      assert.ok(!dryRun.results[0].plannedRepairs.includes(flatSkillPath));
      assert.ok(dryRun.results[0].warnings.some(warning => warning.includes('user-owned')));
      assert.strictEqual(fs.readFileSync(flatSkillPath, 'utf8'), '# User-owned flat skill\n');
      assert.strictEqual(
        fs.readFileSync(legacySkillPath, 'utf8'),
        '# Previously managed nested skill\n'
      );

      const repaired = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['claude'],
      });
      assert.strictEqual(repaired.results[0].status, 'repaired');
      assert.ok(repaired.results[0].warnings.some(warning => warning.includes('user-owned')));
      assert.strictEqual(fs.readFileSync(flatSkillPath, 'utf8'), '# User-owned flat skill\n');
      assert.strictEqual(
        fs.readFileSync(legacySkillPath, 'utf8'),
        fs.readFileSync(
          path.join(REPO_ROOT, 'skills', 'tdd-workflow', 'SKILL.md'),
          'utf8'
        )
      );
      const repairedState = JSON.parse(fs.readFileSync(installStatePath, 'utf8'));
      assert.ok(repairedState.operations.some(operation => (
        operation.destinationPath === legacySkillPath
      )));
      assert.ok(!repairedState.operations.some(operation => (
        operation.destinationPath === flatSkillPath
      )));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('Claude repair migration derives roots from the adapter and removes only the managed legacy file', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');
    const outsideRoot = createTempDir('install-lifecycle-outside-');

    try {
      const targetRoot = path.join(homeDir, '.claude');
      const adapterStatePath = path.join(targetRoot, 'ecc', 'install-state.json');
      const recordedStatePath = path.join(outsideRoot, 'recorded-state.json');
      const flatSkillPath = path.join(targetRoot, 'skills', 'tdd-workflow', 'SKILL.md');
      const legacySkillPath = path.join(
        targetRoot,
        'skills',
        'ecc',
        'tdd-workflow',
        'SKILL.md'
      );
      fs.mkdirSync(path.dirname(legacySkillPath), { recursive: true });
      fs.writeFileSync(legacySkillPath, '# Previously managed nested skill\n');

      writeState(adapterStatePath, {
        adapter: { id: 'claude-home', target: 'claude', kind: 'home' },
        targetRoot: outsideRoot,
        installStatePath: recordedStatePath,
        request: {
          profile: null,
          modules: ['workflow-quality'],
          includeComponents: [],
          excludeComponents: [],
          legacyLanguages: [],
          legacyMode: false,
        },
        resolution: {
          selectedModules: ['platform-configs', 'workflow-quality'],
          skippedModules: [],
        },
        operations: [{
          kind: 'copy-file',
          moduleId: 'workflow-quality',
          sourcePath: path.join(REPO_ROOT, 'skills', 'tdd-workflow', 'SKILL.md'),
          sourceRelativePath: path.join('skills', 'tdd-workflow', 'SKILL.md'),
          destinationPath: legacySkillPath,
          strategy: 'preserve-relative-path',
          ownership: 'managed',
          scaffoldOnly: false,
        }],
        source: {
          repoVersion: CURRENT_PACKAGE_VERSION,
          repoCommit: 'abc123',
          manifestVersion: CURRENT_MANIFEST_VERSION,
        },
      });
      fs.writeFileSync(recordedStatePath, 'outside sentinel\n');

      const result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['claude'],
      });

      assert.strictEqual(result.results[0].status, 'repaired');
      assert.strictEqual(
        fs.readFileSync(flatSkillPath, 'utf8'),
        fs.readFileSync(
          path.join(REPO_ROOT, 'skills', 'tdd-workflow', 'SKILL.md'),
          'utf8'
        )
      );
      assert.ok(!fs.existsSync(legacySkillPath));
      assert.strictEqual(fs.readFileSync(recordedStatePath, 'utf8'), 'outside sentinel\n');
      const refreshedState = readInstallState(adapterStatePath);
      assert.strictEqual(refreshedState.target.root, targetRoot);
      assert.strictEqual(refreshedState.target.installStatePath, adapterStatePath);
      assert.ok(refreshedState.operations.some(operation => (
        operation.destinationPath === flatSkillPath
      )));
      assert.ok(!refreshedState.operations.some(operation => (
        operation.destinationPath === legacySkillPath
      )));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
      cleanup(outsideRoot);
    }
  })) passed++; else failed++;

  if (test('repair copies missing managed files from recorded source paths', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const destinationPath = path.join(targetRoot, 'rules', 'coding-style.md');
      const sourcePath = path.join(REPO_ROOT, 'rules', 'common', 'coding-style.md');
      writeCursorState(projectRoot, {
        operations: [
          managedOperation('copy-file', destinationPath, {
            sourceRelativePath: 'rules/common/coding-style.md',
            strategy: 'copy-file',
          }),
        ],
      });

      const result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'repaired');
      assert.ok(fs.readFileSync(destinationPath).equals(fs.readFileSync(sourcePath)));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('repair reads source content and mode from one no-follow descriptor', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');
    const sourcePath = path.join(REPO_ROOT, 'rules', 'common', 'coding-style.md');
    const originalStatSync = fs.statSync;

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const destinationPath = path.join(targetRoot, 'rules', 'coding-style.md');
      writeCursorState(projectRoot, {
        operations: [
          managedOperation('copy-file', destinationPath, {
            sourceRelativePath: 'rules/common/coding-style.md',
            strategy: 'copy-file',
          }),
        ],
      });

      fs.statSync = function rejectSeparateSourceMetadataLookup(candidatePath, ...args) {
        if (path.resolve(candidatePath) === path.resolve(sourcePath)) {
          throw new Error('source metadata must come from the opened descriptor');
        }
        return originalStatSync.call(fs, candidatePath, ...args);
      };

      const result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'repaired');
      assert.ok(fs.readFileSync(destinationPath).equals(fs.readFileSync(sourcePath)));
    } finally {
      fs.statSync = originalStatSync;
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('repair reports invalid states, missing sources, unsupported operations, and no-op refreshes', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const invalidProjectRoot = createTempDir('install-lifecycle-invalid-');
    const missingSourceProjectRoot = createTempDir('install-lifecycle-missing-source-');
    const unsupportedProjectRoot = createTempDir('install-lifecycle-unsupported-');
    const okProjectRoot = createTempDir('install-lifecycle-ok-');

    try {
      const invalidStatePath = path.join(invalidProjectRoot, '.cursor', 'ecc-install-state.json');
      fs.mkdirSync(path.dirname(invalidStatePath), { recursive: true });
      fs.writeFileSync(invalidStatePath, '{"schemaVersion":"wrong"}\n');

      let result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot: invalidProjectRoot,
        targets: ['cursor'],
      });
      assert.strictEqual(result.results[0].status, 'error');
      assert.ok(result.results[0].error.includes('Invalid install-state'));

      const missingDestination = path.join(missingSourceProjectRoot, '.cursor', 'rules', 'missing.md');
      fs.mkdirSync(path.dirname(missingDestination), { recursive: true });
      fs.writeFileSync(missingDestination, 'managed\n');
      writeCursorState(missingSourceProjectRoot, {
        operations: [
          managedOperation('copy-file', missingDestination, {
            sourceRelativePath: 'missing/source.md',
            strategy: 'copy-file',
          }),
        ],
      });
      result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot: missingSourceProjectRoot,
        targets: ['cursor'],
      });
      assert.strictEqual(result.results[0].status, 'error');
      assert.ok(result.results[0].error.includes('Missing source file(s)'));

      const unsupportedDestination = path.join(unsupportedProjectRoot, '.cursor', 'custom.txt');
      writeCursorState(unsupportedProjectRoot, {
        operations: [
          managedOperation('custom-kind', unsupportedDestination),
        ],
      });
      result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot: unsupportedProjectRoot,
        targets: ['cursor'],
      });
      assert.strictEqual(result.results[0].status, 'error');
      assert.ok(result.results[0].error.includes('Unsupported repair operation kind'));

      writeCursorState(okProjectRoot, { operations: [] });
      result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot: okProjectRoot,
        targets: ['cursor'],
      });
      assert.strictEqual(result.results[0].status, 'ok');
      assert.strictEqual(result.results[0].stateRefreshed, true);
      assert.strictEqual(result.summary.errorCount, 0);
    } finally {
      cleanup(homeDir);
      cleanup(invalidProjectRoot);
      cleanup(missingSourceProjectRoot);
      cleanup(unsupportedProjectRoot);
      cleanup(okProjectRoot);
    }
  })) passed++; else failed++;

  if (test('repair dry-run reports ok when no managed operations need changes', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      writeCursorState(projectRoot, { operations: [] });

      const result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
        dryRun: true,
      });

      assert.strictEqual(result.results[0].status, 'ok');
      assert.strictEqual(result.results[0].stateRefreshed, true);
      assert.deepStrictEqual(result.results[0].plannedRepairs, []);
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('repair builds the OpenCode payload and clears the missing-payload warning', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      withTemporarilyMovedPath(path.join(REPO_ROOT, '.opencode', 'dist'), () => {
        writeOpencodeState(homeDir, {
          request: {
            profile: null,
            modules: ['commands-core'],
            includeComponents: [],
            excludeComponents: [],
            legacyLanguages: [],
            legacyMode: false,
          },
          resolution: {
            selectedModules: ['commands-core'],
            skippedModules: [],
          },
          operations: [],
        });

        const beforeValidate = getInstallTargetAdapter('opencode').validate({
          homeDir,
          repoRoot: REPO_ROOT,
        });
        assert.ok(beforeValidate.some(issue => issue.code === 'opencode-plugin-not-built'));

        const beforeDoctor = buildDoctorReport({
          repoRoot: REPO_ROOT,
          homeDir,
          projectRoot,
          targets: ['opencode'],
        });
        assert.strictEqual(beforeDoctor.results[0].status, 'error');
        assert.ok(beforeDoctor.results[0].issues.some(issue => issue.code === 'resolution-unavailable'));

        let buildCalls = 0;
        const result = repairInstalledStates({
          repoRoot: REPO_ROOT,
          homeDir,
          projectRoot,
          targets: ['opencode'],
          buildOpencodePayload: repoRoot => {
            buildCalls += 1;
            const distDir = path.join(repoRoot, '.opencode', 'dist');
            fs.mkdirSync(path.join(distDir, 'plugins'), { recursive: true });
            fs.mkdirSync(path.join(distDir, 'tools'), { recursive: true });
            fs.writeFileSync(path.join(distDir, 'index.js'), 'module.exports = {};\\n');
          },
        });

        assert.strictEqual(buildCalls, 1);
        assert.strictEqual(result.results[0].status, 'repaired');
        assert.ok(fs.existsSync(path.join(REPO_ROOT, '.opencode', 'dist', 'index.js')));

        const afterValidate = getInstallTargetAdapter('opencode').validate({
          homeDir,
          repoRoot: REPO_ROOT,
        });
        assert.deepStrictEqual(afterValidate, []);

        const afterDoctor = buildDoctorReport({
          repoRoot: REPO_ROOT,
          homeDir,
          projectRoot,
          targets: ['opencode'],
        });
        assert.strictEqual(afterDoctor.results[0].status, 'ok');
        assert.strictEqual(afterDoctor.results[0].issues.length, 0);
      });
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('repair dry-run plans the OpenCode payload build without creating it', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      withTemporarilyMovedPath(path.join(REPO_ROOT, '.opencode', 'dist'), () => {
        writeOpencodeState(homeDir, {
          request: {
            profile: null,
            modules: ['commands-core'],
            includeComponents: [],
            excludeComponents: [],
            legacyLanguages: [],
            legacyMode: false,
          },
          resolution: {
            selectedModules: ['commands-core'],
            skippedModules: [],
          },
          operations: [],
        });

        const result = repairInstalledStates({
          repoRoot: REPO_ROOT,
          homeDir,
          projectRoot,
          targets: ['opencode'],
          dryRun: true,
          buildOpencodePayload: () => {
            throw new Error('build should not run during dry-run');
          },
        });

        assert.strictEqual(result.results[0].status, 'planned');
        assert.ok(result.results[0].plannedRepairs.includes(path.join(REPO_ROOT, '.opencode', 'dist')));
        assert.ok(!fs.existsSync(path.join(REPO_ROOT, '.opencode', 'dist', 'index.js')));
      });
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('withTemporarilyMovedPath cleans up newly created paths when nothing was pre-existing', () => {
    const filePath = path.join(REPO_ROOT, '.opencode', 'dist');
    const backupPath = `${filePath}.backup-${process.pid}-test`;

    try {
      fs.rmSync(filePath, { recursive: true, force: true });
      fs.rmSync(backupPath, { recursive: true, force: true });

      const result = withTemporarilyMovedPath(filePath, receivedBackupPath => {
        assert.strictEqual(receivedBackupPath, null);
        fs.mkdirSync(path.join(filePath, 'plugins'), { recursive: true });
        fs.mkdirSync(path.join(filePath, 'tools'), { recursive: true });
        fs.writeFileSync(path.join(filePath, 'index.js'), '// temp build\n');
        return 'callback-result';
      });

      assert.strictEqual(result, 'callback-result');
      assert.ok(!fs.existsSync(filePath), 'Temporary path should be removed after the callback');
    } finally {
      fs.rmSync(filePath, { recursive: true, force: true });
      fs.rmSync(backupPath, { recursive: true, force: true });
    }
  })) passed++; else failed++;

  if (test('repair surfaces OpenCode build failures without blocking other targets', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      withTemporarilyMovedPath(path.join(REPO_ROOT, '.opencode', 'dist'), () => {
        const cursorTargetRoot = path.join(projectRoot, '.cursor');
        const cursorStatePath = path.join(cursorTargetRoot, 'ecc-install-state.json');
        const cursorDestinationPath = path.join(cursorTargetRoot, 'rules', 'coding-style.md');
        fs.mkdirSync(path.dirname(cursorDestinationPath), { recursive: true });

        writeOpencodeState(homeDir, {
          request: {
            profile: null,
            modules: ['commands-core'],
            includeComponents: [],
            excludeComponents: [],
            legacyLanguages: [],
            legacyMode: false,
          },
          resolution: {
            selectedModules: ['commands-core'],
            skippedModules: [],
          },
          operations: [],
        });

        writeState(cursorStatePath, {
          adapter: { id: 'cursor-project', target: 'cursor', kind: 'project' },
          targetRoot: cursorTargetRoot,
          installStatePath: cursorStatePath,
          request: {
            profile: null,
            modules: [],
            legacyLanguages: ['typescript'],
            legacyMode: true,
          },
          resolution: {
            selectedModules: ['legacy-cursor-install'],
            skippedModules: [],
          },
          operations: [
            managedOperation('copy-file', cursorDestinationPath, {
              sourceRelativePath: 'rules/common/coding-style.md',
              strategy: 'copy-file',
            }),
          ],
          source: {
            repoVersion: CURRENT_PACKAGE_VERSION,
            repoCommit: 'abc123',
            manifestVersion: CURRENT_MANIFEST_VERSION,
          },
        });

        const result = repairInstalledStates({
          repoRoot: REPO_ROOT,
          homeDir,
          projectRoot,
          targets: ['opencode', 'cursor'],
          buildOpencodePayload: () => {
            throw new Error('typescript dependency missing');
          },
        });

        const opencodeResult = result.results.find(entry => entry.adapter.id === 'opencode-home');
        const cursorResult = result.results.find(entry => entry.adapter.id === 'cursor-project');

        assert.strictEqual(opencodeResult.status, 'error');
        assert.ok(opencodeResult.error.includes('typescript dependency missing'));
        assert.strictEqual(cursorResult.status, 'repaired');
        assert.ok(fs.existsSync(cursorDestinationPath));
      });
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('repair surfaces missing source errors from execution when destination is absent', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const destinationPath = path.join(projectRoot, '.cursor', 'rules', 'missing.md');
      writeCursorState(projectRoot, {
        operations: [
          managedOperation('copy-file', destinationPath, {
            sourceRelativePath: 'missing/source.md',
            strategy: 'copy-file',
          }),
        ],
      });

      const result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'error');
      assert.ok(result.results[0].error.includes('Missing source file for repair'));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('repair rejects absolute and parent-relative source metadata outside the repository', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const outsideRoot = createTempDir('install-lifecycle-source-outside-');
    const outsideSourcePath = path.join(outsideRoot, 'secret.txt');
    fs.writeFileSync(outsideSourcePath, 'outside secret\n');

    try {
      const unsafeSources = [
        outsideSourcePath,
        path.relative(REPO_ROOT, outsideSourcePath),
      ];

      for (const sourceRelativePath of unsafeSources) {
        const projectRoot = createTempDir('install-lifecycle-project-');
        try {
          const destinationPath = path.join(projectRoot, '.cursor', 'copied-secret.txt');
          writeCursorState(projectRoot, {
            operations: [
              managedOperation('copy-file', destinationPath, {
                sourceRelativePath,
                strategy: 'copy-file',
              }),
            ],
          });

          const doctor = buildDoctorReport({
            repoRoot: REPO_ROOT,
            homeDir,
            projectRoot,
            targets: ['cursor'],
          });
          const result = repairInstalledStates({
            repoRoot: REPO_ROOT,
            homeDir,
            projectRoot,
            targets: ['cursor'],
          });

          assert.strictEqual(doctor.results[0].status, 'error');
          assert.ok(
            doctor.results[0].issues.some(
              issue => issue.code === 'unsafe-repair-source'
            )
          );
          assert.strictEqual(result.results[0].status, 'error');
          assert.ok(result.results[0].error.includes('unsafe repair source metadata'));
          assert.ok(!result.results[0].error.includes(outsideSourcePath));
          assert.ok(!fs.existsSync(destinationPath));
        } finally {
          cleanup(projectRoot);
        }
      }
    } finally {
      cleanup(homeDir);
      cleanup(outsideRoot);
    }
  })) passed++; else failed++;

  if (test('doctor and repair reject unsafe destinations before health inspection reads them', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const outsideRoot = createTempDir('install-lifecycle-destination-outside-');
    const copySource = fs.readFileSync(
      path.join(REPO_ROOT, 'rules', 'common', 'coding-style.md'),
      'utf8'
    );
    const cases = [
      {
        name: 'matching copy',
        kind: 'copy-file',
        content: copySource,
        overrides: { strategy: 'copy-file' },
      },
      {
        name: 'drifted copy',
        kind: 'copy-file',
        content: 'outside drift\n',
        overrides: { strategy: 'copy-file' },
      },
      {
        name: 'rendered template',
        kind: 'render-template',
        content: 'managed template\n',
        overrides: {
          renderedContent: 'managed template\n',
          strategy: 'render-template',
        },
      },
      {
        name: 'merged JSON',
        kind: 'merge-json',
        content: '{"managed":true,"outside":"sentinel"}\n',
        overrides: {
          mergePayload: { managed: true },
          strategy: 'merge-json',
        },
      },
    ];

    try {
      for (const testCase of cases) {
        const projectRoot = createTempDir('install-lifecycle-project-');
        const destinationPath = path.join(outsideRoot, `${testCase.name}.txt`);
        const originalExistsSync = fs.existsSync;

        try {
          fs.writeFileSync(destinationPath, testCase.content);
          writeCursorState(projectRoot, {
            operations: [
              managedOperation(testCase.kind, destinationPath, testCase.overrides),
            ],
          });

          fs.existsSync = function existsSyncWithoutOutsideInspection(candidatePath) {
            if (path.resolve(candidatePath) === path.resolve(destinationPath)) {
              throw new Error(`unsafe destination inspected: ${testCase.name}`);
            }
            return originalExistsSync.call(fs, candidatePath);
          };

          const doctor = buildDoctorReport({
            repoRoot: REPO_ROOT,
            homeDir,
            projectRoot,
            targets: ['cursor'],
          });
          const repair = repairInstalledStates({
            repoRoot: REPO_ROOT,
            homeDir,
            projectRoot,
            targets: ['cursor'],
          });

          assert.strictEqual(doctor.results[0].status, 'error');
          assert.ok(
            doctor.results[0].issues.some(
              issue => issue.code === 'unsafe-managed-destination'
            )
          );
          assert.strictEqual(repair.results[0].status, 'error');
          assert.ok(repair.results[0].error.includes('unsafe managed destination'));
          assert.strictEqual(
            originalExistsSync.call(fs, destinationPath),
            true,
            `${testCase.name} destination should remain untouched`
          );
        } finally {
          fs.existsSync = originalExistsSync;
          cleanup(projectRoot);
        }
      }
    } finally {
      cleanup(homeDir);
      cleanup(outsideRoot);
    }
  })) passed++; else failed++;

  if (test('doctor reports drifted managed files as a warning', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const statePath = path.join(targetRoot, 'ecc-install-state.json');
      const sourcePath = path.join(REPO_ROOT, '.cursor', 'hooks.json');
      const destinationPath = path.join(targetRoot, 'hooks.json');
      fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
      fs.writeFileSync(destinationPath, '{"drifted":true}\n');

      writeState(statePath, {
        adapter: { id: 'cursor-project', target: 'cursor', kind: 'project' },
        targetRoot,
        installStatePath: statePath,
        request: {
          profile: null,
          modules: ['platform-configs'],
          legacyLanguages: [],
          legacyMode: false,
        },
        resolution: {
          selectedModules: ['platform-configs'],
          skippedModules: [],
        },
        operations: [
          {
            kind: 'copy-file',
            moduleId: 'platform-configs',
            sourcePath,
            sourceRelativePath: '.cursor/hooks.json',
            destinationPath,
            strategy: 'sync-root-children',
            ownership: 'managed',
            scaffoldOnly: false,
          },
        ],
        source: {
          repoVersion: CURRENT_PACKAGE_VERSION,
          repoCommit: 'abc123',
          manifestVersion: CURRENT_MANIFEST_VERSION,
        },
      });

      const report = buildDoctorReport({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(report.results.length, 1);
      assert.strictEqual(report.results[0].status, 'warning');
      assert.ok(report.results[0].issues.some(issue => issue.code === 'drifted-managed-files'));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('doctor reports manifest resolution drift for non-legacy installs', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const statePath = path.join(targetRoot, 'ecc-install-state.json');
      fs.mkdirSync(targetRoot, { recursive: true });

      writeState(statePath, {
        adapter: { id: 'cursor-project', target: 'cursor', kind: 'project' },
        targetRoot,
        installStatePath: statePath,
        request: {
          profile: 'core',
          modules: [],
          legacyLanguages: [],
          legacyMode: false,
        },
        resolution: {
          selectedModules: ['rules-core'],
          skippedModules: [],
        },
        operations: [],
        source: {
          repoVersion: CURRENT_PACKAGE_VERSION,
          repoCommit: 'abc123',
          manifestVersion: CURRENT_MANIFEST_VERSION,
        },
      });

      const report = buildDoctorReport({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(report.results.length, 1);
      assert.strictEqual(report.results[0].status, 'warning');
      assert.ok(report.results[0].issues.some(issue => issue.code === 'resolution-drift'));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('repair restores render-template outputs from recorded rendered content', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(homeDir, '.claude');
      const statePath = path.join(targetRoot, 'ecc', 'install-state.json');
      const destinationPath = path.join(targetRoot, 'plugin.json');
      fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
      fs.writeFileSync(destinationPath, '{"drifted":true}\n');

      writeState(statePath, {
        adapter: { id: 'claude-home', target: 'claude', kind: 'home' },
        targetRoot,
        installStatePath: statePath,
        request: {
          profile: null,
          modules: [],
          legacyLanguages: ['typescript'],
          legacyMode: true,
        },
        resolution: {
          selectedModules: ['legacy-claude-rules'],
          skippedModules: [],
        },
        operations: [
          {
            kind: 'render-template',
            moduleId: 'platform-configs',
            sourceRelativePath: '.claude-plugin/plugin.json.template',
            destinationPath,
            strategy: 'render-template',
            ownership: 'managed',
            scaffoldOnly: false,
            renderedContent: '{"ok":true}\n',
          },
        ],
        source: {
          repoVersion: CURRENT_PACKAGE_VERSION,
          repoCommit: 'abc123',
          manifestVersion: CURRENT_MANIFEST_VERSION,
        },
      });

      const result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['claude'],
      });

      assert.strictEqual(result.results[0].status, 'repaired');
      assert.strictEqual(fs.readFileSync(destinationPath, 'utf8'), '{"ok":true}\n');
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('repair reapplies merge-json operations without clobbering unrelated keys', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const statePath = path.join(targetRoot, 'ecc-install-state.json');
      const destinationPath = path.join(targetRoot, 'hooks.json');
      fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
      fs.writeFileSync(destinationPath, JSON.stringify({
        existing: true,
        nested: {
          enabled: false,
        },
      }, null, 2));

      writeState(statePath, {
        adapter: { id: 'cursor-project', target: 'cursor', kind: 'project' },
        targetRoot,
        installStatePath: statePath,
        request: {
          profile: null,
          modules: [],
          legacyLanguages: ['typescript'],
          legacyMode: true,
        },
        resolution: {
          selectedModules: ['legacy-cursor-install'],
          skippedModules: [],
        },
        operations: [
          {
            kind: 'merge-json',
            moduleId: 'platform-configs',
            sourceRelativePath: '.cursor/hooks.json',
            destinationPath,
            strategy: 'merge-json',
            ownership: 'managed',
            scaffoldOnly: false,
            mergePayload: {
              nested: {
                enabled: true,
              },
              managed: 'yes',
            },
          },
        ],
        source: {
          repoVersion: CURRENT_PACKAGE_VERSION,
          repoCommit: 'abc123',
          manifestVersion: CURRENT_MANIFEST_VERSION,
        },
      });

      const result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'repaired');
      assert.deepStrictEqual(JSON.parse(fs.readFileSync(destinationPath, 'utf8')), {
        existing: true,
        nested: {
          enabled: true,
        },
        managed: 'yes',
      });
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('repair re-applies managed remove operations when files reappear', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const statePath = path.join(targetRoot, 'ecc-install-state.json');
      const destinationPath = path.join(targetRoot, 'legacy-note.txt');
      fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
      fs.writeFileSync(destinationPath, 'stale');

      writeState(statePath, {
        adapter: { id: 'cursor-project', target: 'cursor', kind: 'project' },
        targetRoot,
        installStatePath: statePath,
        request: {
          profile: null,
          modules: [],
          legacyLanguages: ['typescript'],
          legacyMode: true,
        },
        resolution: {
          selectedModules: ['legacy-cursor-install'],
          skippedModules: [],
        },
        operations: [
          {
            kind: 'remove',
            moduleId: 'platform-configs',
            sourceRelativePath: '.cursor/legacy-note.txt',
            destinationPath,
            strategy: 'remove',
            ownership: 'managed',
            scaffoldOnly: false,
          },
        ],
        source: {
          repoVersion: CURRENT_PACKAGE_VERSION,
          repoCommit: 'abc123',
          manifestVersion: CURRENT_MANIFEST_VERSION,
        },
      });

      const result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'repaired');
      assert.ok(!fs.existsSync(destinationPath));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('repair rejects a symlink inserted while creating a missing destination parent', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');
    const outsideRoot = createTempDir('install-lifecycle-outside-');
    const targetRoot = path.join(projectRoot, '.cursor');
    const destinationParent = path.join(targetRoot, 'late-parent');
    const destinationPath = path.join(destinationParent, 'managed.md');
    const outsideDestinationPath = path.join(outsideRoot, 'managed.md');
    const originalMkdirSync = fs.mkdirSync;
    let canonicalDestinationParent;
    let insertedSymlink = false;
    let result;

    try {
      writeCursorState(projectRoot, {
        operations: [
          managedOperation('copy-file', destinationPath, { strategy: 'copy-file' }),
        ],
      });
      canonicalDestinationParent = path.join(
        fs.realpathSync(targetRoot),
        path.basename(destinationParent)
      );

      fs.mkdirSync = function mkdirSyncWithLateSymlink(directoryPath, options) {
        if (!insertedSymlink && path.resolve(directoryPath) === canonicalDestinationParent) {
          originalMkdirSync.call(fs, path.dirname(canonicalDestinationParent), { recursive: true });
          fs.symlinkSync(
            outsideRoot,
            canonicalDestinationParent,
            process.platform === 'win32' ? 'junction' : 'dir'
          );
          insertedSymlink = true;
          return undefined;
        }
        return originalMkdirSync.call(fs, directoryPath, options);
      };

      result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });
    } finally {
      fs.mkdirSync = originalMkdirSync;
    }

    try {
      assert.strictEqual(insertedSymlink, true);
      assert.strictEqual(result.results[0].status, 'error');
      assert.ok(result.results[0].error.includes('outside the install root'));
      assert.ok(!fs.existsSync(outsideDestinationPath));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
      cleanup(outsideRoot);
    }
  })) passed++; else failed++;

  if (test('repair rejects an in-root final symlink without overwriting its victim', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const victimPath = path.join(targetRoot, 'victim.md');
      const destinationPath = path.join(targetRoot, 'managed.md');
      fs.mkdirSync(targetRoot, { recursive: true });
      fs.writeFileSync(victimPath, 'victim sentinel\n');
      try {
        fs.symlinkSync(victimPath, destinationPath);
      } catch {
        console.log('    (symlink unsupported on this platform; skipping)');
        return;
      }
      writeCursorState(projectRoot, {
        operations: [
          managedOperation('render-template', destinationPath, {
            renderedContent: 'managed replacement\n',
            strategy: 'render-template',
          }),
        ],
      });

      const result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'error');
      assert.ok(result.results[0].error.includes('final symlink'));
      assert.strictEqual(fs.readFileSync(victimPath, 'utf8'), 'victim sentinel\n');
      assert.strictEqual(fs.lstatSync(destinationPath).isSymbolicLink(), true);
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('repair uses no-follow writes when a final destination becomes a symlink', () => {
    if (!fs.constants.O_NOFOLLOW) {
      console.log('    (O_NOFOLLOW unsupported on this platform; skipping)');
      return;
    }

    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');
    const outsideRoot = createTempDir('install-lifecycle-outside-');
    const targetRoot = path.join(projectRoot, '.cursor');
    const destinationPath = path.join(targetRoot, 'managed.md');
    const outsideDestinationPath = path.join(outsideRoot, 'managed.md');
    const originalOpenSync = fs.openSync;
    let canonicalDestinationPath;
    let insertedSymlink = false;
    let result;

    try {
      fs.writeFileSync(outsideDestinationPath, 'outside sentinel\n');
      writeCursorState(projectRoot, {
        operations: [
          managedOperation('copy-file', destinationPath, { strategy: 'copy-file' }),
        ],
      });
      canonicalDestinationPath = path.join(
        fs.realpathSync(targetRoot),
        path.basename(destinationPath)
      );

      fs.openSync = function openSyncWithLateSymlink(filePath, flags, mode) {
        if (!insertedSymlink && path.resolve(filePath) === canonicalDestinationPath) {
          fs.symlinkSync(outsideDestinationPath, canonicalDestinationPath);
          insertedSymlink = true;
        }
        return originalOpenSync.call(fs, filePath, flags, mode);
      };

      result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });
    } finally {
      fs.openSync = originalOpenSync;
    }

    try {
      assert.strictEqual(insertedSymlink, true);
      assert.strictEqual(result.results[0].status, 'error');
      assert.strictEqual(
        fs.readFileSync(outsideDestinationPath, 'utf8'),
        'outside sentinel\n'
      );
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
      cleanup(outsideRoot);
    }
  })) passed++; else failed++;

  if (test('repair revalidates a pinned write before a swapped parent can truncate outside files', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');
    const outsideRoot = createTempDir('install-lifecycle-outside-');
    const targetRoot = path.join(projectRoot, '.cursor');
    const destinationParent = path.join(targetRoot, 'late-parent');
    const backupParent = path.join(targetRoot, 'late-parent-backup');
    const destinationPath = path.join(destinationParent, 'managed.md');
    const outsideDestinationPath = path.join(outsideRoot, 'managed.md');
    const originalOpenSync = fs.openSync;
    let canonicalDestinationPath;
    let insertedSymlink = false;
    let result;

    const symlinkProbe = path.join(targetRoot, 'parent-symlink-probe');
    try {
      fs.mkdirSync(targetRoot, { recursive: true });
      fs.symlinkSync(
        outsideRoot,
        symlinkProbe,
        process.platform === 'win32' ? 'junction' : 'dir'
      );
      fs.rmSync(symlinkProbe, { force: true });
    } catch {
      console.log('    (symlink unsupported on this platform; skipping)');
      cleanup(homeDir);
      cleanup(projectRoot);
      cleanup(outsideRoot);
      return;
    }

    try {
      fs.mkdirSync(destinationParent, { recursive: true });
      fs.writeFileSync(destinationPath, 'drifted managed content\n');
      fs.writeFileSync(outsideDestinationPath, 'outside sentinel\n');
      canonicalDestinationPath = fs.realpathSync(destinationPath);
      writeCursorState(projectRoot, {
        operations: [
          managedOperation('copy-file', destinationPath, { strategy: 'copy-file' }),
        ],
      });

      fs.openSync = function openSyncWithLateParentSwap(filePath, flags, mode) {
        const isDestinationWrite = path.resolve(filePath) === canonicalDestinationPath
          && typeof flags === 'number'
          && (flags & fs.constants.O_WRONLY) === fs.constants.O_WRONLY;
        if (!insertedSymlink && isDestinationWrite) {
          fs.renameSync(destinationParent, backupParent);
          fs.symlinkSync(
            outsideRoot,
            destinationParent,
            process.platform === 'win32' ? 'junction' : 'dir'
          );
          insertedSymlink = true;
        }
        return originalOpenSync.call(fs, filePath, flags, mode);
      };

      result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });
    } finally {
      fs.openSync = originalOpenSync;
    }

    try {
      assert.strictEqual(insertedSymlink, true);
      assert.strictEqual(result.results[0].status, 'error');
      assert.strictEqual(
        fs.readFileSync(outsideDestinationPath, 'utf8'),
        'outside sentinel\n'
      );
      assert.strictEqual(
        fs.readFileSync(path.join(backupParent, 'managed.md'), 'utf8'),
        'drifted managed content\n'
      );
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
      cleanup(outsideRoot);
    }
  })) passed++; else failed++;

  if (test('repair refreshes only the adapter-derived install-state path', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');
    const outsideRoot = createTempDir('install-lifecycle-outside-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const adapterStatePath = path.join(targetRoot, 'ecc-install-state.json');
      const recordedStatePath = path.join(outsideRoot, 'recorded-state.json');
      const stateOptions = createCursorStateOptions(projectRoot, {
        installStatePath: recordedStatePath,
      });
      writeState(adapterStatePath, stateOptions);
      fs.writeFileSync(recordedStatePath, 'outside sentinel\n');

      const result = repairInstalledStates({
        repoRoot: REPO_ROOT,
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'ok');
      assert.ok(fs.existsSync(adapterStatePath));
      assert.strictEqual(
        fs.readFileSync(recordedStatePath, 'utf8'),
        'outside sentinel\n'
      );
      const refreshedState = readInstallState(adapterStatePath);
      assert.strictEqual(refreshedState.target.root, targetRoot);
      assert.strictEqual(refreshedState.target.installStatePath, adapterStatePath);
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
      cleanup(outsideRoot);
    }
  })) passed++; else failed++;

  if (test('uninstall restores JSON merged files from recorded previous content', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const statePath = path.join(targetRoot, 'ecc-install-state.json');
      const destinationPath = path.join(targetRoot, 'hooks.json');
      fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
      fs.writeFileSync(destinationPath, JSON.stringify({
        existing: true,
        managed: true,
      }, null, 2));

      writeState(statePath, {
        adapter: { id: 'cursor-project', target: 'cursor', kind: 'project' },
        targetRoot,
        installStatePath: statePath,
        request: {
          profile: null,
          modules: [],
          legacyLanguages: ['typescript'],
          legacyMode: true,
        },
        resolution: {
          selectedModules: ['legacy-cursor-install'],
          skippedModules: [],
        },
        operations: [
          {
            kind: 'merge-json',
            moduleId: 'platform-configs',
            sourceRelativePath: '.cursor/hooks.json',
            destinationPath,
            strategy: 'merge-json',
            ownership: 'managed',
            scaffoldOnly: false,
            mergePayload: {
              managed: true,
            },
            previousContent: JSON.stringify({
              existing: true,
            }, null, 2),
          },
        ],
        source: {
          repoVersion: CURRENT_PACKAGE_VERSION,
          repoCommit: 'abc123',
          manifestVersion: CURRENT_MANIFEST_VERSION,
        },
      });

      const result = uninstallInstalledStates({
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'uninstalled');
      assert.deepStrictEqual(JSON.parse(fs.readFileSync(destinationPath, 'utf8')), {
        existing: true,
      });
      assert.ok(!fs.existsSync(statePath));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('uninstall restores rendered template files from recorded previous content', () => {
    const tempDir = createTempDir('install-lifecycle-');

    try {
      const targetRoot = path.join(tempDir, '.claude');
      const statePath = path.join(targetRoot, 'ecc', 'install-state.json');
      const destinationPath = path.join(targetRoot, 'plugin.json');
      fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
      fs.writeFileSync(destinationPath, '{"generated":true}\n');

      writeInstallState(statePath, createInstallState({
        adapter: { id: 'claude-home', target: 'claude', kind: 'home' },
        targetRoot,
        installStatePath: statePath,
        request: {
          profile: 'core',
          modules: ['platform-configs'],
          includeComponents: [],
          excludeComponents: [],
          legacyLanguages: [],
          legacyMode: false,
        },
        resolution: {
          selectedModules: ['platform-configs'],
          skippedModules: [],
        },
        source: {
          repoVersion: '1.8.0',
          repoCommit: 'abc123',
          manifestVersion: 1,
        },
        operations: [
          {
            kind: 'render-template',
            moduleId: 'platform-configs',
            sourceRelativePath: '.claude/plugin.json.template',
            destinationPath,
            strategy: 'render-template',
            ownership: 'managed',
            scaffoldOnly: false,
            renderedContent: '{"generated":true}\n',
            previousContent: '{"existing":true}\n',
          },
        ],
      }));

      const result = uninstallInstalledStates({
        homeDir: tempDir,
        projectRoot: tempDir,
        targets: ['claude'],
      });

      assert.strictEqual(result.summary.uninstalledCount, 1);
      assert.strictEqual(fs.readFileSync(destinationPath, 'utf8'), '{"existing":true}\n');
      assert.ok(!fs.existsSync(statePath));
    } finally {
      cleanup(tempDir);
    }
  })) passed++; else failed++;

  if (test('uninstall restores files removed during install when previous content is recorded', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const statePath = path.join(targetRoot, 'ecc-install-state.json');
      const destinationPath = path.join(targetRoot, 'legacy-note.txt');
      fs.mkdirSync(targetRoot, { recursive: true });

      writeState(statePath, {
        adapter: { id: 'cursor-project', target: 'cursor', kind: 'project' },
        targetRoot,
        installStatePath: statePath,
        request: {
          profile: null,
          modules: [],
          legacyLanguages: ['typescript'],
          legacyMode: true,
        },
        resolution: {
          selectedModules: ['legacy-cursor-install'],
          skippedModules: [],
        },
        operations: [
          {
            kind: 'remove',
            moduleId: 'platform-configs',
            sourceRelativePath: '.cursor/legacy-note.txt',
            destinationPath,
            strategy: 'remove',
            ownership: 'managed',
            scaffoldOnly: false,
            previousContent: 'restore me\n',
          },
        ],
        source: {
          repoVersion: CURRENT_PACKAGE_VERSION,
          repoCommit: 'abc123',
          manifestVersion: CURRENT_MANIFEST_VERSION,
        },
      });

      const result = uninstallInstalledStates({
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'uninstalled');
      assert.strictEqual(fs.readFileSync(destinationPath, 'utf8'), 'restore me\n');
      assert.ok(!fs.existsSync(statePath));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('uninstall dry-run reports deduped managed removals without deleting files', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const destinationPath = path.join(targetRoot, 'rules', 'coding-style.md');
      fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
      fs.writeFileSync(destinationPath, 'managed\n');
      const { installStatePath } = writeCursorState(projectRoot, {
        operations: [
          managedOperation('copy-file', destinationPath, { strategy: 'copy-file' }),
          managedOperation('copy-file', destinationPath, { strategy: 'copy-file' }),
        ],
      });

      const result = uninstallInstalledStates({
        homeDir,
        projectRoot,
        targets: ['cursor'],
        dryRun: true,
      });

      assert.strictEqual(result.dryRun, true);
      assert.strictEqual(result.results[0].status, 'planned');
      assert.deepStrictEqual(result.results[0].plannedRemovals, [
        destinationPath,
        installStatePath,
      ]);
      assert.ok(fs.existsSync(destinationPath));
      assert.ok(fs.existsSync(installStatePath));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('uninstall reports invalid install states as errors', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const statePath = path.join(projectRoot, '.cursor', 'ecc-install-state.json');
      fs.mkdirSync(path.dirname(statePath), { recursive: true });
      fs.writeFileSync(statePath, '{not-json', 'utf8');

      const result = uninstallInstalledStates({
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'error');
      assert.ok(result.results[0].error.includes('Failed to read install-state'));
      assert.strictEqual(result.summary.errorCount, 1);
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('uninstall removes only the adapter-derived install-state path', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');
    const outsideRoot = createTempDir('install-lifecycle-outside-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const adapterStatePath = path.join(targetRoot, 'ecc-install-state.json');
      const recordedStatePath = path.join(outsideRoot, 'recorded-state.json');
      const stateOptions = createCursorStateOptions(projectRoot, {
        installStatePath: recordedStatePath,
      });
      writeState(adapterStatePath, stateOptions);
      fs.writeFileSync(recordedStatePath, 'outside sentinel\n');

      const result = uninstallInstalledStates({
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'uninstalled');
      assert.ok(!fs.existsSync(adapterStatePath));
      assert.strictEqual(
        fs.readFileSync(recordedStatePath, 'utf8'),
        'outside sentinel\n'
      );
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
      cleanup(outsideRoot);
    }
  })) passed++; else failed++;

  if (test('uninstall removes copied files and cleans empty parent directories', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const destinationPath = path.join(targetRoot, 'rules', 'nested', 'managed.md');
      fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
      fs.writeFileSync(destinationPath, 'managed\n');
      writeCursorState(projectRoot, {
        operations: [
          managedOperation('copy-file', destinationPath, { strategy: 'copy-file' }),
        ],
      });

      const result = uninstallInstalledStates({
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'uninstalled');
      assert.ok(result.results[0].removedPaths.includes(destinationPath));
      assert.ok(!fs.existsSync(destinationPath));
      assert.ok(!fs.existsSync(path.dirname(destinationPath)));
      assert.ok(fs.existsSync(targetRoot));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('uninstall cleanup stops at the adapter-derived target root', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const cleanupBoundaryRoot = createTempDir('install-lifecycle-boundary-');
    const projectRoot = path.join(cleanupBoundaryRoot, 'project');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const adapterStatePath = path.join(targetRoot, 'ecc-install-state.json');
      const destinationPath = path.join(targetRoot, 'rules', 'nested', 'managed.md');
      fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
      fs.writeFileSync(destinationPath, 'managed\n');
      const stateOptions = createCursorStateOptions(projectRoot, {
        targetRoot: cleanupBoundaryRoot,
        installStatePath: adapterStatePath,
        operations: [
          managedOperation('copy-file', destinationPath, { strategy: 'copy-file' }),
        ],
      });
      writeState(adapterStatePath, stateOptions);

      const result = uninstallInstalledStates({
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'uninstalled');
      assert.ok(fs.existsSync(projectRoot));
      assert.ok(fs.existsSync(targetRoot));
      assert.ok(!fs.existsSync(destinationPath));
    } finally {
      cleanup(homeDir);
      cleanup(cleanupBoundaryRoot);
    }
  })) passed++; else failed++;

  if (test('uninstall handles merge-json subset removal and full-file deletion', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const partialProjectRoot = createTempDir('install-lifecycle-partial-');
    const fullProjectRoot = createTempDir('install-lifecycle-full-');

    try {
      let targetRoot = path.join(partialProjectRoot, '.cursor');
      let destinationPath = path.join(targetRoot, 'settings.json');
      fs.mkdirSync(targetRoot, { recursive: true });
      fs.writeFileSync(destinationPath, JSON.stringify({
        keep: true,
        managed: true,
        nested: {
          keep: true,
          remove: true,
        },
        list: ['a', 'b'],
      }, null, 2));
      writeCursorState(partialProjectRoot, {
        operations: [
          managedOperation('merge-json', destinationPath, {
            mergePayload: {
              managed: true,
              nested: { remove: true },
              list: ['a', 'b'],
            },
          }),
        ],
      });

      let result = uninstallInstalledStates({
        homeDir,
        projectRoot: partialProjectRoot,
        targets: ['cursor'],
      });
      assert.strictEqual(result.results[0].status, 'uninstalled');
      assert.deepStrictEqual(JSON.parse(fs.readFileSync(destinationPath, 'utf8')), {
        keep: true,
        nested: {
          keep: true,
        },
      });

      targetRoot = path.join(fullProjectRoot, '.cursor');
      destinationPath = path.join(targetRoot, 'settings.json');
      fs.mkdirSync(targetRoot, { recursive: true });
      fs.writeFileSync(destinationPath, JSON.stringify({ managed: true }, null, 2));
      writeCursorState(fullProjectRoot, {
        operations: [
          managedOperation('merge-json', destinationPath, {
            mergePayload: { managed: true },
          }),
        ],
      });

      result = uninstallInstalledStates({
        homeDir,
        projectRoot: fullProjectRoot,
        targets: ['cursor'],
      });
      assert.strictEqual(result.results[0].status, 'uninstalled');
      assert.ok(!fs.existsSync(destinationPath));
    } finally {
      cleanup(homeDir);
      cleanup(partialProjectRoot);
      cleanup(fullProjectRoot);
    }
  })) passed++; else failed++;

  if (test('uninstall handles merge-json edge shapes and absent destinations', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projects = [
      createTempDir('install-lifecycle-current-primitive-'),
      createTempDir('install-lifecycle-missing-key-'),
      createTempDir('install-lifecycle-nested-delete-'),
      createTempDir('install-lifecycle-array-root-'),
      createTempDir('install-lifecycle-primitive-root-'),
      createTempDir('install-lifecycle-absent-dest-'),
      createTempDir('install-lifecycle-previous-json-'),
    ];

    try {
      const cases = [
        {
          projectRoot: projects[0],
          initial: '"plain"',
          payload: { managed: true },
          expected: 'plain',
        },
        {
          projectRoot: projects[1],
          initial: { keep: true },
          payload: { missing: true },
          expected: { keep: true },
        },
        {
          projectRoot: projects[2],
          initial: { keep: true, nested: { remove: true } },
          payload: { nested: { remove: true } },
          expected: { keep: true },
        },
        {
          projectRoot: projects[3],
          initial: ['a', 'b'],
          payload: ['a', 'b'],
          removed: true,
        },
        {
          projectRoot: projects[4],
          initial: true,
          payload: true,
          removed: true,
        },
        {
          projectRoot: projects[5],
          payload: { managed: true },
          absent: true,
        },
        {
          projectRoot: projects[6],
          initial: { generated: true },
          payload: { generated: true },
          previousJson: { restored: true },
          expected: { restored: true },
        },
      ];

      for (const testCase of cases) {
        const targetRoot = path.join(testCase.projectRoot, '.cursor');
        const destinationPath = path.join(targetRoot, 'settings.json');
        fs.mkdirSync(targetRoot, { recursive: true });
        if (!testCase.absent) {
          fs.writeFileSync(
            destinationPath,
            typeof testCase.initial === 'string'
              ? `${testCase.initial}\n`
              : JSON.stringify(testCase.initial, null, 2)
          );
        }
        writeCursorState(testCase.projectRoot, {
          operations: [
            managedOperation('merge-json', destinationPath, {
              mergePayload: testCase.payload,
              previousJson: testCase.previousJson,
            }),
          ],
        });

        const result = uninstallInstalledStates({
          homeDir,
          projectRoot: testCase.projectRoot,
          targets: ['cursor'],
        });

        assert.strictEqual(result.results[0].status, 'uninstalled');
        if (testCase.removed || testCase.absent) {
          assert.ok(!fs.existsSync(destinationPath));
        } else {
          assert.deepStrictEqual(JSON.parse(fs.readFileSync(destinationPath, 'utf8')), testCase.expected);
        }
      }
    } finally {
      cleanup(homeDir);
      for (const projectRoot of projects) {
        cleanup(projectRoot);
      }
    }
  })) passed++; else failed++;

  if (test('uninstall removes generated render-template files and no-backup remove operations are no-ops', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const templatePath = path.join(targetRoot, 'generated', 'plugin.json');
      const removedPath = path.join(targetRoot, 'already-removed.txt');
      fs.mkdirSync(path.dirname(templatePath), { recursive: true });
      fs.writeFileSync(templatePath, '{"generated":true}\n');

      writeCursorState(projectRoot, {
        operations: [
          managedOperation('render-template', templatePath, {
            renderedContent: '{"generated":true}\n',
          }),
          managedOperation('remove', removedPath),
        ],
      });

      const result = uninstallInstalledStates({
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'uninstalled');
      assert.ok(result.results[0].removedPaths.includes(templatePath));
      assert.ok(!fs.existsSync(templatePath));
      assert.ok(!fs.existsSync(path.dirname(templatePath)));
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('uninstall removes an in-root final symlink without deleting its victim', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const victimPath = path.join(targetRoot, 'victim.md');
      const destinationPath = path.join(targetRoot, 'managed.md');
      fs.mkdirSync(targetRoot, { recursive: true });
      fs.writeFileSync(victimPath, 'victim sentinel\n');
      try {
        fs.symlinkSync(victimPath, destinationPath);
      } catch {
        console.log('    (symlink unsupported on this platform; skipping)');
        return;
      }
      writeCursorState(projectRoot, {
        operations: [
          managedOperation('copy-file', destinationPath, { strategy: 'copy-file' }),
        ],
      });

      const result = uninstallInstalledStates({
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'uninstalled');
      assert.ok(!fs.existsSync(destinationPath));
      assert.strictEqual(fs.readFileSync(victimPath, 'utf8'), 'victim sentinel\n');
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('uninstall rejects a symlink inserted after initial destination validation', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');
    const outsideRoot = createTempDir('install-lifecycle-outside-');
    const targetRoot = path.join(projectRoot, '.cursor');
    const destinationParent = path.join(targetRoot, 'late-parent');
    const backupParent = path.join(targetRoot, 'late-parent-backup');
    const destinationPath = path.join(destinationParent, 'managed.md');
    const outsideDestinationPath = path.join(outsideRoot, 'managed.md');
    const originalExistsSync = fs.existsSync;
    let canonicalDestinationParent;
    let canonicalDestinationPath;
    let insertedSymlink = false;
    let result;

    try {
      fs.mkdirSync(destinationParent, { recursive: true });
      fs.writeFileSync(destinationPath, 'managed\n');
      fs.writeFileSync(outsideDestinationPath, 'outside sentinel\n');
      writeCursorState(projectRoot, {
        operations: [
          managedOperation('copy-file', destinationPath, { strategy: 'copy-file' }),
        ],
      });
      canonicalDestinationPath = fs.realpathSync(destinationPath);
      canonicalDestinationParent = path.dirname(canonicalDestinationPath);

      fs.existsSync = function existsSyncWithLateSymlink(candidatePath) {
        if (!insertedSymlink && path.resolve(candidatePath) === canonicalDestinationPath) {
          fs.renameSync(canonicalDestinationParent, backupParent);
          fs.symlinkSync(
            outsideRoot,
            canonicalDestinationParent,
            process.platform === 'win32' ? 'junction' : 'dir'
          );
          insertedSymlink = true;
        }
        return originalExistsSync.call(fs, candidatePath);
      };

      result = uninstallInstalledStates({
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });
    } finally {
      fs.existsSync = originalExistsSync;
    }

    try {
      assert.strictEqual(insertedSymlink, true);
      assert.strictEqual(result.results[0].status, 'error');
      assert.ok(result.results[0].error.includes('outside the install root'));
      assert.strictEqual(
        fs.readFileSync(outsideDestinationPath, 'utf8'),
        'outside sentinel\n'
      );
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
      cleanup(outsideRoot);
    }
  })) passed++; else failed++;

  if (test('uninstall restores previous JSON snapshots for template and remove operations', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const projectRoot = createTempDir('install-lifecycle-project-');

    try {
      const targetRoot = path.join(projectRoot, '.cursor');
      const templatePath = path.join(targetRoot, 'plugin.json');
      const removedPath = path.join(targetRoot, 'legacy.json');
      fs.mkdirSync(targetRoot, { recursive: true });
      fs.writeFileSync(templatePath, '{"generated":true}\n');

      writeCursorState(projectRoot, {
        operations: [
          managedOperation('render-template', templatePath, {
            previousJson: { existing: true },
            renderedContent: '{"generated":true}\n',
          }),
          managedOperation('remove', removedPath, {
            previousJson: { restored: true },
          }),
        ],
      });

      const result = uninstallInstalledStates({
        homeDir,
        projectRoot,
        targets: ['cursor'],
      });

      assert.strictEqual(result.results[0].status, 'uninstalled');
      assert.deepStrictEqual(JSON.parse(fs.readFileSync(templatePath, 'utf8')), {
        existing: true,
      });
      assert.deepStrictEqual(JSON.parse(fs.readFileSync(removedPath, 'utf8')), {
        restored: true,
      });
    } finally {
      cleanup(homeDir);
      cleanup(projectRoot);
    }
  })) passed++; else failed++;

  if (test('uninstall reports unsupported operations and missing merge payloads as errors', () => {
    const homeDir = createTempDir('install-lifecycle-home-');
    const unsupportedProjectRoot = createTempDir('install-lifecycle-unsupported-');
    const missingPayloadProjectRoot = createTempDir('install-lifecycle-missing-payload-');

    try {
      let targetRoot = path.join(unsupportedProjectRoot, '.cursor');
      let destinationPath = path.join(targetRoot, 'custom.txt');
      fs.mkdirSync(targetRoot, { recursive: true });
      fs.writeFileSync(destinationPath, 'custom\n');
      writeCursorState(unsupportedProjectRoot, {
        operations: [
          managedOperation('custom-kind', destinationPath),
        ],
      });

      let result = uninstallInstalledStates({
        homeDir,
        projectRoot: unsupportedProjectRoot,
        targets: ['cursor'],
      });
      assert.strictEqual(result.results[0].status, 'error');
      assert.ok(result.results[0].error.includes('Unsupported uninstall operation kind'));

      targetRoot = path.join(missingPayloadProjectRoot, '.cursor');
      destinationPath = path.join(targetRoot, 'settings.json');
      fs.mkdirSync(targetRoot, { recursive: true });
      fs.writeFileSync(destinationPath, '{"managed":true}\n');
      writeCursorState(missingPayloadProjectRoot, {
        operations: [
          managedOperation('merge-json', destinationPath),
        ],
      });

      result = uninstallInstalledStates({
        homeDir,
        projectRoot: missingPayloadProjectRoot,
        targets: ['cursor'],
      });
      assert.strictEqual(result.results[0].status, 'error');
      assert.ok(result.results[0].error.includes('Missing merge payload for uninstall'));
    } finally {
      cleanup(homeDir);
      cleanup(unsupportedProjectRoot);
      cleanup(missingPayloadProjectRoot);
    }
  })) passed++; else failed++;

  console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
  process.exit(failed > 0 ? 1 : 0);
}

runTests();
