Files
stack/scripts/test-goal-discovery.mjs

82 lines
5.2 KiB
JavaScript

#!/usr/bin/env node
// No-model native resource-loader check: real project discovery plus seat extensions.
// Usage: node scripts/test-goal-discovery.mjs <cwd> <settings.json> conflict|explicit|shared|unified [brain]
// Reads only the extensions list; never copies credentials, starts sessions or runs tools.
import assert from 'node:assert/strict';
import { mkdtempSync, readFileSync, realpathSync, rmSync, existsSync, unlinkSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join, relative } from 'node:path';
import { pathToFileURL } from 'node:url';
import { execFileSync } from 'node:child_process';
import { randomUUID } from 'node:crypto';
const [cwd, settingsPath, expected, brain = cwd] = process.argv.slice(2);
assert.ok(cwd && settingsPath && ['conflict', 'explicit', 'shared', 'unified'].includes(expected));
const wantsShared = expected === 'shared' || expected === 'unified';
const extensions = JSON.parse(readFileSync(settingsPath, 'utf8')).extensions;
assert.ok(Array.isArray(extensions) && extensions.every(x => typeof x === 'string'));
const agentDir = mkdtempSync(join(tmpdir(), 'goal-discovery-'));
process.env.PI_CODING_AGENT_DIR = agentDir;
process.env.MOSAIC_LAUNCH_INCARNATION = 'discovery-test-' + randomUUID();
process.env.PI_OFFLINE = '1';
// Loading the shared extension must not quarantine any operator-owned legacy state.
for (const dir of new Set([cwd, brain])) {
assert.equal(existsSync(join(dir, '.pi/state/goal/goal-state.json')), false);
}
try {
const binary = realpathSync(execFileSync('which', ['pi'], { encoding: 'utf8' }).trim());
const { DefaultResourceLoader, SettingsManager } = await import(pathToFileURL(join(dirname(binary), 'index.js')).href);
const settingsManager = SettingsManager.inMemory({ extensions });
settingsManager.setProjectTrusted(true);
const loader = new DefaultResourceLoader({ cwd, agentDir, settingsManager,
noExtensions: expected === 'explicit' || expected === 'shared',
additionalExtensionPaths: [...(expected === 'shared'
? [join(brain, '.pi/extensions/goal'), join(brain, 'fleet/extensions/wrapper-guard')]
: expected === 'explicit' ? extensions : []), join(brain, 'tools/unslop-hook/extension.ts')],
noSkills: true, noPromptTemplates: true, noThemes: true, noContextFiles: true });
await loader.reload();
const loaded = loader.getExtensions();
if (expected === 'conflict') {
assert.ok(loaded.errors.some(e => e.error.includes('Tool "goal_report" conflicts with')), JSON.stringify(loaded.errors));
console.log('PASS negative control: combined discovery reproduces goal_report conflict');
} else {
assert.deepEqual(loaded.errors, []);
const owners = loaded.extensions.filter(e => e.tools.has('goal_report'));
assert.equal(owners.length, 1);
assert.equal(realpathSync(owners[0].path.replace(/\/index\.ts$/, '')),
realpathSync(join(brain, wantsShared ? '.pi/extensions/goal' : 'fleet/extensions/goal')));
const guardNames = extensions.filter(p => /\/(wrapper-guard|mosaic-core)$/.test(p)).map(p => p.split('/').at(-1));
assert.ok(guardNames.length, 'test must include the configured enforcement extension');
for (const name of guardNames) {
const guards = loaded.extensions.filter(e => e.path.replace(/\/index\.ts$/, '').endsWith('/' + name));
assert.equal(guards.length, 1, `${name} must remain loaded`);
assert.ok(guards[0].handlers.get('tool_call')?.length, `${name} interception must remain registered`);
}
assert.equal(owners[0].commands.has('goal'), true);
assert.equal(loaded.extensions.filter(e => e.path.endsWith('/unslop-hook/extension.ts')).length, 1,
'launcher unslop extension must remain loaded');
if (wantsShared) {
assert.ok(owners[0].shortcuts.has('alt+g'), 'NG full recall shortcut must be registered');
}
// Only the synthetic native suite opts into an owned fixture write. Live checks
// merely load registrations and never run a goal command or read private state.
if (process.env.MOSAIC_GOAL_DISCOVERY_FIXTURE_ROOT === brain) {
assert.ok(brain.startsWith(join(tmpdir(), 'goal58-native-')));
assert.equal(readFileSync(join(brain, '.goal-discovery-owned-fixture'), 'utf8'), 'synthetic goal58 fixture');
const filename = `goal-state.${process.env.MOSAIC_LAUNCH_INCARNATION}.json`;
const possible = [...new Set([join(brain, '.pi/state/goal', filename),
join(brain, 'fleet/state/goal', filename), join(cwd, '.pi/state/goal', filename)])];
assert.ok(possible.every(p => !existsSync(p)), 'fixture identity must be new');
await owners[0].commands.get('goal').handler('owned goal58 fixture', { mode: 'print', hasUI: false });
const written = possible.filter(existsSync);
assert.equal(written.length, 1, 'one incarnation state store per selected entrypoint');
assert.equal(JSON.parse(readFileSync(written[0], 'utf8')).text, 'owned goal58 fixture');
console.log(`PASS owned fixture state path: ${relative(brain, dirname(written[0]))}`);
unlinkSync(written[0]);
}
console.log(`PASS ${expected} discovery: exactly one ${wantsShared ? 'shared NG' : 'legacy'} goal_report and /goal; ${guardNames.join(', ')} interception and unslop retained`);
}
} finally {
rmSync(agentDir, { recursive: true, force: true });
}