mirror of
https://github.com/affaan-m/ECC.git
synced 2026-08-22 23:42:24 +02:00
699 lines
27 KiB
JavaScript
699 lines
27 KiB
JavaScript
/**
|
|
* Focused coverage for migrating Antigravity installs from .agent to .agents.
|
|
*/
|
|
|
|
const assert = require('assert');
|
|
const crypto = require('crypto');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
|
|
const { applyInstallPlan } = require('../../scripts/lib/install/apply');
|
|
const {
|
|
buildDoctorReport,
|
|
discoverInstalledStates,
|
|
repairInstalledStates,
|
|
uninstallInstalledStates,
|
|
} = require('../../scripts/lib/install-lifecycle');
|
|
const {
|
|
createInstallState,
|
|
readInstallState,
|
|
writeInstallState,
|
|
} = require('../../scripts/lib/install-state');
|
|
|
|
const REPO_ROOT = path.join(__dirname, '..', '..');
|
|
const PACKAGE_VERSION = require('../../package.json').version;
|
|
const MANIFEST_VERSION = require('../../manifests/install-modules.json').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 digest(content) {
|
|
return crypto.createHash('sha256').update(content).digest('hex');
|
|
}
|
|
|
|
function managedCopy(destinationPath, sourceRelativePath, content) {
|
|
return {
|
|
kind: 'copy-file',
|
|
moduleId: 'rules-core',
|
|
sourceRelativePath,
|
|
destinationPath,
|
|
strategy: 'copy-file',
|
|
ownership: 'managed',
|
|
scaffoldOnly: false,
|
|
contentSha256: digest(content),
|
|
};
|
|
}
|
|
|
|
function createAntigravityState(targetRoot, installStatePath, operations = []) {
|
|
return createInstallState({
|
|
adapter: { id: 'antigravity-project', target: 'antigravity', kind: 'project' },
|
|
targetRoot,
|
|
installStatePath,
|
|
request: {
|
|
profile: null,
|
|
modules: [],
|
|
includeComponents: [],
|
|
excludeComponents: [],
|
|
legacyLanguages: ['typescript'],
|
|
legacyMode: true,
|
|
},
|
|
resolution: {
|
|
selectedModules: ['legacy-antigravity-install'],
|
|
skippedModules: [],
|
|
},
|
|
source: {
|
|
repoVersion: PACKAGE_VERSION,
|
|
repoCommit: 'test-commit',
|
|
manifestVersion: MANIFEST_VERSION,
|
|
},
|
|
operations,
|
|
});
|
|
}
|
|
|
|
function seedLegacyState(projectRoot, entries = []) {
|
|
const targetRoot = path.join(projectRoot, '.agent');
|
|
const installStatePath = path.join(targetRoot, 'ecc-install-state.json');
|
|
const operations = entries.map(entry => {
|
|
const destinationPath = path.join(targetRoot, entry.relativePath);
|
|
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
fs.writeFileSync(destinationPath, entry.recordedContent, 'utf8');
|
|
return managedCopy(destinationPath, entry.sourceRelativePath, entry.recordedContent);
|
|
});
|
|
writeInstallState(
|
|
installStatePath,
|
|
createAntigravityState(targetRoot, installStatePath, operations)
|
|
);
|
|
return { targetRoot, installStatePath, operations };
|
|
}
|
|
|
|
function createCanonicalPlan(projectRoot, sourcePath) {
|
|
const targetRoot = path.join(projectRoot, '.agents');
|
|
const installStatePath = path.join(targetRoot, 'ecc-install-state.json');
|
|
const operation = {
|
|
kind: 'copy-file',
|
|
moduleId: 'rules-core',
|
|
sourcePath,
|
|
sourceRelativePath: 'rules/common/coding-style.md',
|
|
destinationPath: path.join(targetRoot, 'rules', 'coding-style.md'),
|
|
strategy: 'copy-file',
|
|
ownership: 'managed',
|
|
scaffoldOnly: false,
|
|
};
|
|
|
|
return {
|
|
mode: 'legacy',
|
|
sourceRoot: REPO_ROOT,
|
|
target: 'antigravity',
|
|
adapter: { id: 'antigravity-project', target: 'antigravity', kind: 'project' },
|
|
targetRoot,
|
|
installRoot: targetRoot,
|
|
installStatePath,
|
|
operations: [operation],
|
|
warnings: [],
|
|
statePreview: createAntigravityState(targetRoot, installStatePath, [operation]),
|
|
};
|
|
}
|
|
|
|
function runTests() {
|
|
console.log('\n=== Testing Antigravity legacy migration ===\n');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
if (test('writes canonical state before removing unchanged legacy-managed files', () => {
|
|
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-migrate-'));
|
|
try {
|
|
const legacy = seedLegacyState(projectRoot, [{
|
|
relativePath: 'rules/common-coding-style.md',
|
|
sourceRelativePath: 'rules/common/coding-style.md',
|
|
recordedContent: fs.readFileSync(
|
|
path.join(REPO_ROOT, 'rules', 'common', 'coding-style.md'),
|
|
'utf8'
|
|
),
|
|
}]);
|
|
const sourcePath = path.join(projectRoot, 'source.md');
|
|
fs.writeFileSync(sourcePath, 'canonical managed\n', 'utf8');
|
|
const plan = createCanonicalPlan(projectRoot, sourcePath);
|
|
|
|
applyInstallPlan(plan, {
|
|
writeInstallState(filePath, state) {
|
|
assert.ok(fs.existsSync(legacy.installStatePath));
|
|
assert.ok(fs.existsSync(legacy.operations[0].destinationPath));
|
|
return writeInstallState(filePath, state);
|
|
},
|
|
});
|
|
|
|
assert.ok(fs.existsSync(plan.installStatePath));
|
|
assert.ok(fs.existsSync(plan.operations[0].destinationPath));
|
|
assert.ok(!fs.existsSync(legacy.operations[0].destinationPath));
|
|
assert.ok(!fs.existsSync(legacy.installStatePath));
|
|
} finally {
|
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('does not clean legacy files when canonical state persistence fails', () => {
|
|
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-migrate-fail-'));
|
|
try {
|
|
const legacy = seedLegacyState(projectRoot, [{
|
|
relativePath: 'rules/common-coding-style.md',
|
|
sourceRelativePath: 'rules/common/coding-style.md',
|
|
recordedContent: fs.readFileSync(
|
|
path.join(REPO_ROOT, 'rules', 'common', 'coding-style.md'),
|
|
'utf8'
|
|
),
|
|
}]);
|
|
const sourcePath = path.join(projectRoot, 'source.md');
|
|
fs.writeFileSync(sourcePath, 'canonical managed\n', 'utf8');
|
|
|
|
assert.throws(
|
|
() => applyInstallPlan(createCanonicalPlan(projectRoot, sourcePath), {
|
|
writeInstallState() {
|
|
throw new Error('simulated canonical state failure');
|
|
},
|
|
}),
|
|
/simulated canonical state failure/
|
|
);
|
|
|
|
assert.ok(fs.existsSync(legacy.operations[0].destinationPath));
|
|
assert.ok(fs.existsSync(legacy.installStatePath));
|
|
} finally {
|
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('preserves recorded legacy content when the current ECC source has changed', () => {
|
|
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-source-drift-'));
|
|
try {
|
|
const legacy = seedLegacyState(projectRoot, [{
|
|
relativePath: 'rules/common-coding-style.md',
|
|
sourceRelativePath: 'rules/common/coding-style.md',
|
|
recordedContent: 'historical ECC content\n',
|
|
}]);
|
|
const sourcePath = path.join(projectRoot, 'source.md');
|
|
fs.writeFileSync(sourcePath, 'canonical managed\n', 'utf8');
|
|
|
|
const result = applyInstallPlan(createCanonicalPlan(projectRoot, sourcePath));
|
|
|
|
assert.ok(fs.existsSync(legacy.operations[0].destinationPath));
|
|
assert.ok(fs.existsSync(legacy.installStatePath));
|
|
assert.ok(result.warnings.some(warning => warning.includes(
|
|
'current ECC source differs from the recorded installed content'
|
|
)));
|
|
} finally {
|
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('preserves drifted and unmanaged legacy files and retains legacy state', () => {
|
|
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-migrate-partial-'));
|
|
try {
|
|
const legacy = seedLegacyState(projectRoot, [
|
|
{
|
|
relativePath: 'rules/common-coding-style.md',
|
|
sourceRelativePath: 'rules/common/coding-style.md',
|
|
recordedContent: fs.readFileSync(
|
|
path.join(REPO_ROOT, 'rules', 'common', 'coding-style.md'),
|
|
'utf8'
|
|
),
|
|
},
|
|
{
|
|
relativePath: 'rules/common-patterns.md',
|
|
sourceRelativePath: 'rules/common/patterns.md',
|
|
recordedContent: fs.readFileSync(
|
|
path.join(REPO_ROOT, 'rules', 'common', 'patterns.md'),
|
|
'utf8'
|
|
),
|
|
},
|
|
]);
|
|
fs.writeFileSync(legacy.operations[1].destinationPath, 'customer edit\n', 'utf8');
|
|
const unmanagedPath = path.join(legacy.targetRoot, 'customer-note.md');
|
|
fs.writeFileSync(unmanagedPath, 'keep me\n', 'utf8');
|
|
const sourcePath = path.join(projectRoot, 'source.md');
|
|
fs.writeFileSync(sourcePath, 'canonical managed\n', 'utf8');
|
|
|
|
applyInstallPlan(createCanonicalPlan(projectRoot, sourcePath));
|
|
|
|
assert.ok(!fs.existsSync(legacy.operations[0].destinationPath));
|
|
assert.strictEqual(fs.readFileSync(legacy.operations[1].destinationPath, 'utf8'), 'customer edit\n');
|
|
assert.strictEqual(fs.readFileSync(unmanagedPath, 'utf8'), 'keep me\n');
|
|
assert.ok(fs.existsSync(legacy.installStatePath));
|
|
const remainingLegacyState = readInstallState(legacy.installStatePath);
|
|
assert.strictEqual(remainingLegacyState.operations.length, 2);
|
|
const report = buildDoctorReport({
|
|
repoRoot: REPO_ROOT,
|
|
projectRoot,
|
|
targets: ['antigravity'],
|
|
});
|
|
const legacyReport = report.results.find(result => result.legacy);
|
|
assert.ok(legacyReport.issues.some(issue => issue.code === 'legacy-antigravity-layout'));
|
|
assert.ok(legacyReport.issues.some(issue => issue.code === 'drifted-managed-files'));
|
|
assert.ok(legacyReport.issues.some(issue => issue.code === 'missing-managed-files'));
|
|
|
|
const uninstall = uninstallInstalledStates({ projectRoot, targets: ['antigravity'] });
|
|
assert.strictEqual(
|
|
fs.readFileSync(legacy.operations[1].destinationPath, 'utf8'),
|
|
'customer edit\n'
|
|
);
|
|
assert.strictEqual(fs.readFileSync(unmanagedPath, 'utf8'), 'keep me\n');
|
|
assert.ok(fs.existsSync(legacy.installStatePath));
|
|
assert.strictEqual(uninstall.summary.partialCount, 1);
|
|
const dryRun = uninstallInstalledStates({
|
|
projectRoot,
|
|
targets: ['antigravity'],
|
|
dryRun: true,
|
|
});
|
|
const legacyDryRun = dryRun.results.find(result => (
|
|
result.installStatePath === legacy.installStatePath
|
|
));
|
|
assert.deepStrictEqual(legacyDryRun.plannedRemovals, []);
|
|
assert.ok(legacyDryRun.retainedPaths.includes(legacy.operations[1].destinationPath));
|
|
} finally {
|
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('does not trust a forged legacy digest to delete customer content', () => {
|
|
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-migrate-forged-'));
|
|
try {
|
|
const legacy = seedLegacyState(projectRoot, [
|
|
{
|
|
relativePath: 'rules/common-coding-style.md',
|
|
sourceRelativePath: 'rules/common/coding-style.md',
|
|
recordedContent: 'customer-owned content\n',
|
|
},
|
|
{
|
|
relativePath: 'customer-note.md',
|
|
sourceRelativePath: 'rules/common/patterns.md',
|
|
recordedContent: 'customer note\n',
|
|
},
|
|
{
|
|
relativePath: 'README.md',
|
|
sourceRelativePath: 'commands/../README.md',
|
|
recordedContent: fs.readFileSync(path.join(REPO_ROOT, 'README.md'), 'utf8'),
|
|
},
|
|
]);
|
|
const sourcePath = path.join(projectRoot, 'source.md');
|
|
fs.writeFileSync(sourcePath, 'canonical managed\n', 'utf8');
|
|
|
|
const result = applyInstallPlan(createCanonicalPlan(projectRoot, sourcePath));
|
|
|
|
assert.strictEqual(
|
|
fs.readFileSync(legacy.operations[0].destinationPath, 'utf8'),
|
|
'customer-owned content\n'
|
|
);
|
|
assert.strictEqual(
|
|
fs.readFileSync(legacy.operations[1].destinationPath, 'utf8'),
|
|
'customer note\n'
|
|
);
|
|
assert.ok(fs.existsSync(legacy.operations[2].destinationPath));
|
|
assert.ok(fs.existsSync(legacy.installStatePath));
|
|
assert.ok(result.warnings.some(warning => warning.includes('migration is incomplete')));
|
|
} finally {
|
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('warns when digestless legacy files require manual migration', () => {
|
|
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-migrate-digestless-'));
|
|
try {
|
|
const legacy = seedLegacyState(projectRoot, [{
|
|
relativePath: 'rules/common-coding-style.md',
|
|
sourceRelativePath: 'rules/common/coding-style.md',
|
|
recordedContent: fs.readFileSync(
|
|
path.join(REPO_ROOT, 'rules', 'common', 'coding-style.md'),
|
|
'utf8'
|
|
),
|
|
}]);
|
|
const legacyState = readInstallState(legacy.installStatePath);
|
|
delete legacyState.operations[0].contentSha256;
|
|
writeInstallState(legacy.installStatePath, legacyState);
|
|
const sourcePath = path.join(projectRoot, 'source.md');
|
|
fs.writeFileSync(sourcePath, 'canonical managed\n', 'utf8');
|
|
|
|
const result = applyInstallPlan(createCanonicalPlan(projectRoot, sourcePath));
|
|
|
|
assert.ok(fs.existsSync(legacy.operations[0].destinationPath));
|
|
assert.ok(fs.existsSync(legacy.installStatePath));
|
|
assert.ok(result.warnings.some(warning => (
|
|
warning.includes('Legacy Antigravity migration is incomplete')
|
|
&& warning.includes('.agent')
|
|
)));
|
|
} finally {
|
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('preserves unrelated empty legacy directories after complete cleanup', () => {
|
|
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-migrate-empty-dir-'));
|
|
try {
|
|
const legacy = seedLegacyState(projectRoot, [{
|
|
relativePath: 'rules/common-coding-style.md',
|
|
sourceRelativePath: 'rules/common/coding-style.md',
|
|
recordedContent: fs.readFileSync(
|
|
path.join(REPO_ROOT, 'rules', 'common', 'coding-style.md'),
|
|
'utf8'
|
|
),
|
|
}]);
|
|
const userDirectory = path.join(legacy.targetRoot, 'customer-empty-directory');
|
|
fs.mkdirSync(userDirectory);
|
|
const sourcePath = path.join(projectRoot, 'source.md');
|
|
fs.writeFileSync(sourcePath, 'canonical managed\n', 'utf8');
|
|
|
|
applyInstallPlan(createCanonicalPlan(projectRoot, sourcePath));
|
|
|
|
assert.ok(fs.existsSync(userDirectory));
|
|
assert.ok(!fs.existsSync(legacy.installStatePath));
|
|
} finally {
|
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('list discovery and doctor report both canonical and remaining legacy states', () => {
|
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-home-'));
|
|
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-discover-'));
|
|
try {
|
|
const canonicalRoot = path.join(projectRoot, '.agents');
|
|
const canonicalStatePath = path.join(canonicalRoot, 'ecc-install-state.json');
|
|
writeInstallState(
|
|
canonicalStatePath,
|
|
createAntigravityState(canonicalRoot, canonicalStatePath)
|
|
);
|
|
const legacy = seedLegacyState(projectRoot);
|
|
|
|
const records = discoverInstalledStates({
|
|
homeDir,
|
|
projectRoot,
|
|
targets: ['antigravity'],
|
|
}).filter(record => record.exists);
|
|
assert.deepStrictEqual(
|
|
records.map(record => record.installStatePath).sort(),
|
|
[canonicalStatePath, legacy.installStatePath].sort()
|
|
);
|
|
|
|
const report = buildDoctorReport({
|
|
repoRoot: REPO_ROOT,
|
|
homeDir,
|
|
projectRoot,
|
|
targets: ['antigravity'],
|
|
});
|
|
assert.strictEqual(report.results.length, 2);
|
|
assert.strictEqual(report.summary.checkedCount, 2);
|
|
const legacyReport = report.results.find(result => result.legacy);
|
|
assert.ok(legacyReport);
|
|
assert.ok(legacyReport.issues.some(issue => (
|
|
issue.severity === 'warning'
|
|
&& issue.code === 'legacy-antigravity-layout'
|
|
)));
|
|
|
|
fs.rmSync(canonicalStatePath);
|
|
const legacyOnlyRecords = discoverInstalledStates({
|
|
homeDir,
|
|
projectRoot,
|
|
targets: ['antigravity'],
|
|
}).filter(record => record.exists);
|
|
assert.strictEqual(legacyOnlyRecords.length, 1);
|
|
assert.strictEqual(legacyOnlyRecords[0].installStatePath, legacy.installStatePath);
|
|
assert.strictEqual(legacyOnlyRecords[0].legacy, true);
|
|
} finally {
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('uninstall discovers and removes both canonical and legacy states', () => {
|
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-home-'));
|
|
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-uninstall-'));
|
|
try {
|
|
const canonicalRoot = path.join(projectRoot, '.agents');
|
|
const canonicalStatePath = path.join(canonicalRoot, 'ecc-install-state.json');
|
|
writeInstallState(
|
|
canonicalStatePath,
|
|
createAntigravityState(canonicalRoot, canonicalStatePath)
|
|
);
|
|
const legacy = seedLegacyState(projectRoot);
|
|
|
|
const dryRun = uninstallInstalledStates({
|
|
homeDir,
|
|
projectRoot,
|
|
targets: ['antigravity'],
|
|
dryRun: true,
|
|
});
|
|
assert.strictEqual(dryRun.results.length, 2);
|
|
assert.ok(dryRun.results.some(result => result.installStatePath === canonicalStatePath));
|
|
assert.ok(dryRun.results.some(result => result.installStatePath === legacy.installStatePath));
|
|
|
|
const result = uninstallInstalledStates({
|
|
homeDir,
|
|
projectRoot,
|
|
targets: ['antigravity'],
|
|
});
|
|
assert.strictEqual(result.summary.uninstalledCount, 2);
|
|
assert.ok(!fs.existsSync(canonicalStatePath));
|
|
assert.ok(!fs.existsSync(legacy.installStatePath));
|
|
} finally {
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('does not repair residual legacy state over preserved customer edits', () => {
|
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-home-'));
|
|
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-repair-'));
|
|
try {
|
|
const canonicalRoot = path.join(projectRoot, '.agents');
|
|
const canonicalStatePath = path.join(canonicalRoot, 'ecc-install-state.json');
|
|
writeInstallState(
|
|
canonicalStatePath,
|
|
createAntigravityState(canonicalRoot, canonicalStatePath)
|
|
);
|
|
const legacy = seedLegacyState(projectRoot, [{
|
|
relativePath: 'rules/coding-style.md',
|
|
sourceRelativePath: 'rules/common/coding-style.md',
|
|
recordedContent: fs.readFileSync(
|
|
path.join(REPO_ROOT, 'rules', 'common', 'coding-style.md'),
|
|
'utf8'
|
|
),
|
|
}]);
|
|
fs.writeFileSync(legacy.operations[0].destinationPath, 'customer edit\n', 'utf8');
|
|
|
|
const result = repairInstalledStates({
|
|
repoRoot: REPO_ROOT,
|
|
homeDir,
|
|
projectRoot,
|
|
targets: ['antigravity'],
|
|
});
|
|
|
|
assert.strictEqual(
|
|
fs.readFileSync(legacy.operations[0].destinationPath, 'utf8'),
|
|
'customer edit\n'
|
|
);
|
|
assert.ok(!result.results.some(entry => entry.installStatePath === legacy.installStatePath));
|
|
} finally {
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('does not discover mismatched or symlinked legacy state', () => {
|
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-home-'));
|
|
const mismatchedProject = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-mismatch-'));
|
|
const symlinkProject = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-symlink-'));
|
|
const externalRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-external-'));
|
|
try {
|
|
const mismatchedRoot = path.join(mismatchedProject, '.agent');
|
|
const mismatchedStatePath = path.join(mismatchedRoot, 'ecc-install-state.json');
|
|
writeInstallState(mismatchedStatePath, createInstallState({
|
|
adapter: { id: 'cursor-project', target: 'cursor', kind: 'project' },
|
|
targetRoot: mismatchedRoot,
|
|
installStatePath: mismatchedStatePath,
|
|
request: {
|
|
profile: null,
|
|
modules: [],
|
|
includeComponents: [],
|
|
excludeComponents: [],
|
|
legacyLanguages: [],
|
|
legacyMode: true,
|
|
},
|
|
resolution: { selectedModules: [], skippedModules: [] },
|
|
source: {
|
|
repoVersion: PACKAGE_VERSION,
|
|
repoCommit: 'test-commit',
|
|
manifestVersion: MANIFEST_VERSION,
|
|
},
|
|
operations: [],
|
|
}));
|
|
|
|
const mismatchedRecords = discoverInstalledStates({
|
|
homeDir,
|
|
projectRoot: mismatchedProject,
|
|
targets: ['antigravity'],
|
|
}).filter(record => record.exists);
|
|
assert.strictEqual(mismatchedRecords.length, 0);
|
|
|
|
if (process.platform !== 'win32') {
|
|
const symlinkStatePath = path.join(externalRoot, 'ecc-install-state.json');
|
|
const linkedRoot = path.join(symlinkProject, '.agent');
|
|
fs.symlinkSync(externalRoot, linkedRoot, 'dir');
|
|
writeInstallState(
|
|
symlinkStatePath,
|
|
createAntigravityState(linkedRoot, path.join(linkedRoot, 'ecc-install-state.json'))
|
|
);
|
|
|
|
const symlinkRecords = discoverInstalledStates({
|
|
homeDir,
|
|
projectRoot: symlinkProject,
|
|
targets: ['antigravity'],
|
|
}).filter(record => record.exists);
|
|
assert.strictEqual(symlinkRecords.length, 0);
|
|
}
|
|
} finally {
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
fs.rmSync(mismatchedProject, { recursive: true, force: true });
|
|
fs.rmSync(symlinkProject, { recursive: true, force: true });
|
|
fs.rmSync(externalRoot, { recursive: true, force: true });
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('reports corrupt legacy state instead of treating it as absent', () => {
|
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-home-'));
|
|
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-corrupt-'));
|
|
try {
|
|
const legacyRoot = path.join(projectRoot, '.agent');
|
|
const legacyStatePath = path.join(legacyRoot, 'ecc-install-state.json');
|
|
fs.mkdirSync(legacyRoot, { recursive: true });
|
|
fs.writeFileSync(legacyStatePath, '{not valid json\n', 'utf8');
|
|
|
|
const records = discoverInstalledStates({
|
|
homeDir,
|
|
projectRoot,
|
|
targets: ['antigravity'],
|
|
}).filter(record => record.exists);
|
|
assert.strictEqual(records.length, 1);
|
|
assert.strictEqual(records[0].legacy, true);
|
|
assert.match(records[0].error, /Unable to inspect legacy Antigravity install-state/);
|
|
|
|
const sourcePath = path.join(projectRoot, 'source.md');
|
|
fs.writeFileSync(sourcePath, 'canonical managed\n', 'utf8');
|
|
const result = applyInstallPlan(createCanonicalPlan(projectRoot, sourcePath));
|
|
assert.ok(result.warnings.some(warning => warning.includes(
|
|
'Unable to inspect legacy Antigravity install-state'
|
|
)));
|
|
assert.ok(fs.existsSync(legacyStatePath));
|
|
} finally {
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('doctor and repair compare Antigravity agents using transformed content', () => {
|
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-home-'));
|
|
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-transform-health-'));
|
|
try {
|
|
const sourcePath = path.join(REPO_ROOT, 'agents', 'architect.md');
|
|
const targetRoot = path.join(projectRoot, '.agents');
|
|
const installStatePath = path.join(targetRoot, 'ecc-install-state.json');
|
|
const operation = {
|
|
kind: 'copy-file',
|
|
moduleId: 'agents-core',
|
|
sourcePath,
|
|
sourceRelativePath: 'agents/architect.md',
|
|
destinationPath: path.join(targetRoot, 'agents', 'architect.md'),
|
|
strategy: 'copy-file',
|
|
ownership: 'managed',
|
|
scaffoldOnly: false,
|
|
contentTransform: 'antigravity-agent-frontmatter',
|
|
};
|
|
const plan = {
|
|
mode: 'legacy',
|
|
target: 'antigravity',
|
|
adapter: { id: 'antigravity-project', target: 'antigravity', kind: 'project' },
|
|
targetRoot,
|
|
installRoot: targetRoot,
|
|
installStatePath,
|
|
operations: [operation],
|
|
warnings: [],
|
|
statePreview: createAntigravityState(targetRoot, installStatePath, [operation]),
|
|
};
|
|
applyInstallPlan(plan);
|
|
const installedContent = fs.readFileSync(operation.destinationPath, 'utf8');
|
|
assert.notStrictEqual(installedContent, fs.readFileSync(sourcePath, 'utf8'));
|
|
|
|
const report = buildDoctorReport({
|
|
repoRoot: REPO_ROOT,
|
|
homeDir,
|
|
projectRoot,
|
|
targets: ['antigravity'],
|
|
});
|
|
assert.ok(!report.results[0].issues.some(issue => issue.code === 'drifted-managed-files'));
|
|
|
|
fs.writeFileSync(operation.destinationPath, 'drifted\n', 'utf8');
|
|
const repair = repairInstalledStates({
|
|
repoRoot: REPO_ROOT,
|
|
homeDir,
|
|
projectRoot,
|
|
targets: ['antigravity'],
|
|
});
|
|
assert.strictEqual(repair.results[0].status, 'repaired');
|
|
assert.strictEqual(fs.readFileSync(operation.destinationPath, 'utf8'), installedContent);
|
|
} finally {
|
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('applies a declared Antigravity transform without link-index metadata', () => {
|
|
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'antigravity-transform-only-'));
|
|
try {
|
|
const sourcePath = path.join(REPO_ROOT, 'agents', 'architect.md');
|
|
const targetRoot = path.join(projectRoot, '.agents');
|
|
const installStatePath = path.join(targetRoot, 'ecc-install-state.json');
|
|
const operation = {
|
|
kind: 'copy-file',
|
|
moduleId: 'agents-core',
|
|
sourcePath,
|
|
sourceRelativePath: null,
|
|
destinationPath: path.join(targetRoot, 'agents', 'architect.md'),
|
|
strategy: 'copy-file',
|
|
ownership: 'managed',
|
|
scaffoldOnly: false,
|
|
contentTransform: 'antigravity-agent-frontmatter',
|
|
};
|
|
const plan = {
|
|
mode: 'legacy',
|
|
target: 'antigravity',
|
|
adapter: { id: 'antigravity-project', target: 'antigravity', kind: 'project' },
|
|
targetRoot,
|
|
installRoot: targetRoot,
|
|
installStatePath,
|
|
operations: [operation],
|
|
warnings: [],
|
|
statePreview: createAntigravityState(targetRoot, installStatePath, []),
|
|
};
|
|
|
|
applyInstallPlan(plan, { writeInstallState() {} });
|
|
|
|
const installedContent = fs.readFileSync(operation.destinationPath, 'utf8');
|
|
assert.notStrictEqual(installedContent, fs.readFileSync(sourcePath, 'utf8'));
|
|
assert.ok(!installedContent.includes('color:'));
|
|
} finally {
|
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
|
process.exit(failed > 0 ? 1 : 0);
|
|
}
|
|
|
|
runTests();
|