83 lines
5.2 KiB
JavaScript
83 lines
5.2 KiB
JavaScript
// Row224 M3: exact creative tool sets preserve the existing C2-08 web grants.
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdirSync, readFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { typedTools } from '../src/tools.mjs';
|
|
import { buildBundle } from '../src/bundle.mjs';
|
|
import { TYPES } from '../../library/src/mapping.mjs';
|
|
import { HARNESS, REPO_SKILLS, resolvedFor, scratch } from './helpers.mjs';
|
|
|
|
const LOOKUPS = ['library_find', 'library_get', 'library_search', 'library_manuscript', 'library_universe'];
|
|
const CREATIVE = ['companion', 'research', 'ideas', 'proofreader', 'fact-checker', 'continuity'];
|
|
const role = (t, definition, harness = 'pi') => resolvedFor(t, 'bean', d => {
|
|
d.roles.bean = { definition, credentials: { gitea: { ...d.roles.coder.credentials.gitea } }, vars: { harness, model: 'claude-sonnet-5-5', thinking: 'high' } };
|
|
});
|
|
const expected = definition => ['send_message', ...(['companion', 'research'].includes(definition)
|
|
? ['web_search', 'web_fetch'] : []), ...LOOKUPS, 'library_doc_write', 'library_doc_delete'];
|
|
|
|
test('M3: per-role snapshots contain eight names, or ten with the C2-08 web grant', t => {
|
|
for (const definition of CREATIVE) {
|
|
const tools = typedTools({ resolved: role(t, definition), library: true, tracker: true, arbiter: true });
|
|
assert.deepEqual(tools.map(t => t.name), expected(definition), definition);
|
|
assert.ok(tools.every(t => t.parameters.additionalProperties === false));
|
|
}
|
|
});
|
|
|
|
test('every noncreative role gets lookups when a library is configured, not otherwise', t => {
|
|
for (const instance of ['coder', 'reviewer', 'pm', 'chief']) {
|
|
const resolved = resolvedFor(t, instance);
|
|
const ordinary = typedTools({ resolved }).map(t => t.name);
|
|
assert.ok(!ordinary.some(name => name.startsWith('library_')));
|
|
const tools = typedTools({ resolved, library: true });
|
|
assert.deepEqual(tools.filter(t => t.name.startsWith('library_')).map(t => t.name), LOOKUPS);
|
|
assert.deepEqual(tools.filter(t => !t.name.startsWith('library_')).map(t => t.name), ordinary);
|
|
}
|
|
});
|
|
|
|
test('lookup schemas require a universe even for get, include paging, and cannot select repo or commit', t => {
|
|
const tools = typedTools({ resolved: role(t, 'ideas'), library: true });
|
|
const required = [ ['universe', 'query'], ['universe', 'path'], ['universe', 'text'], ['universe', 'book'], ['universe'] ];
|
|
for (const [i, tool] of tools.filter(t => LOOKUPS.includes(t.name)).entries()) {
|
|
assert.deepEqual(tool.parameters.required, required[i]);
|
|
assert.equal(tool.verb, LOOKUPS[i].replace('_', '.'));
|
|
assert.equal(tool.action, null);
|
|
assert.equal(tool.parameters.additionalProperties, false);
|
|
for (const key of ['repo', 'commit', 'business', 'accept']) assert.equal(Object.hasOwn(tool.parameters.properties, key), false);
|
|
}
|
|
const byName = Object.fromEntries(tools.map(t => [t.name, t]));
|
|
assert.deepEqual(byName.library_find.parameters.properties.scope.enum, ['canon', 'playground', 'supporting']);
|
|
assert.deepEqual(byName.library_find.parameters.properties.type.enum, [...TYPES]);
|
|
assert.equal(byName.library_find.parameters.properties.offset.minimum, 0);
|
|
assert.equal(byName.library_get.parameters.properties.from.minimum, 1);
|
|
assert.equal(byName.library_get.parameters.properties.links_from.minimum, 0);
|
|
assert.equal(byName.library_manuscript.parameters.properties.chapter.type, 'string');
|
|
});
|
|
|
|
test('bundle flag reaches both harness tool manifests and creative prompt without generic tools', t => {
|
|
for (const definition of CREATIVE) for (const harness of ['pi', 'claude-code']) {
|
|
const dir = scratch(t), workspace = join(dir, 'ws'); mkdirSync(workspace);
|
|
const b = buildBundle({ dir: join(dir, 'bundle'), resolved: role(t, definition, harness), library: true,
|
|
contract: '# Creative\n', tracker: true, arbiter: true, skillsDir: REPO_SKILLS,
|
|
session: { run: 'r224', launchedBy: 'chief', workspace, toolSocket: join(dir, 't.sock'), turnMarker: join(dir, 'turn.done') },
|
|
harness: HARNESS[harness] });
|
|
assert.deepEqual(b.tools.map(t => t.name), expected(definition), `${definition} ${harness}`);
|
|
const serialized = JSON.parse(readFileSync(b.files.tools, 'utf8'));
|
|
assert.deepEqual(serialized.map(t => t.name), expected(definition));
|
|
const prompt = readFileSync(b.files.prompt, 'utf8');
|
|
assert.match(prompt, /library_find/);
|
|
assert.doesNotMatch(prompt, /Bus tools: [^\n]*(read_spec|list_agents|inbox|trail|write_record|raise_decision|list_tasks)/);
|
|
}
|
|
});
|
|
|
|
test('C2-05c scope schemas expand only find/search/get, keeping manuscript and tool names unchanged', t => {
|
|
const tools = typedTools({ resolved: role(t, 'ideas'), library: true });
|
|
assert.deepEqual(tools.map(t => t.name), ['send_message', ...LOOKUPS, 'library_doc_write', 'library_doc_delete']);
|
|
for (const name of ['library_find', 'library_search', 'library_get']) {
|
|
const tool = tools.find(t => t.name === name);
|
|
assert.deepEqual(tool.parameters.properties.scope.enum, ['canon', 'playground', 'supporting']);
|
|
assert.ok(!tool.parameters.required.includes('scope'));
|
|
}
|
|
assert.deepEqual(tools.find(t => t.name === 'library_manuscript').parameters.properties.scope.enum, ['canon', 'playground']);
|
|
});
|