Files
stack/packages/harness/tests/library-write-tools.test.mjs

61 lines
3.9 KiB
JavaScript

// Row 288 M3: creative write tools and independent closed-schema coverage.
import test from 'node:test';
import assert from 'node:assert/strict';
import { typedTools, untypedActions } from '../src/tools.mjs';
import { resolvedFor } from './helpers.mjs';
const LOOKUPS = ['library_find', 'library_get', 'library_search', 'library_manuscript', 'library_universe'];
const WRITES = ['library_doc_write', 'library_doc_delete'];
const CREATIVE = ['companion', 'research', 'ideas', 'proofreader', 'fact-checker', 'continuity'];
const role = (t, definition) => resolvedFor(t, 'bean', doc => {
doc.roles.bean = { definition, credentials: { gitea: { ...doc.roles.coder.credentials.gitea } }, vars: { harness: 'pi', model: 'claude-sonnet-5-5', thinking: 'high' } };
});
test('C2-02 M3: each creative role has eight tools, or ten with its web grant', t => {
for (const definition of CREATIVE) {
const resolved = role(t, definition), tools = typedTools({ resolved, library: true, tracker: true, arbiter: true });
const expected = ['send_message', ...(['companion', 'research'].includes(definition) ? ['web_search', 'web_fetch'] : []), ...LOOKUPS, ...WRITES];
assert.deepEqual(tools.map(tool => tool.name), expected, definition);
assert.equal(tools.length, ['companion', 'research'].includes(definition) ? 10 : 8);
assert.equal(untypedActions(resolved, tools).includes('library.write'), false);
}
});
test('C2-02 schemas: both write tools require universe/path/base, close keys, map verb and action', t => {
const tools = typedTools({ resolved: role(t, 'ideas'), library: true });
for (const [name, verb, required] of [
['library_doc_write', 'library.doc.write', ['universe', 'path', 'base', 'content']],
['library_doc_delete', 'library.doc.delete', ['universe', 'path', 'base']],
]) {
const tool = tools.find(tool => tool.name === name);
assert.ok(tool, name); assert.equal(tool.verb, verb); assert.equal(tool.action, 'library.write');
assert.equal(tool.parameters.type, 'object'); assert.equal(tool.parameters.additionalProperties, false);
assert.deepEqual(tool.parameters.required, required);
assert.deepEqual(Object.keys(tool.parameters.properties).sort(), [...required].sort());
assert.equal(tool.parameters.properties.universe.type, 'string');
assert.equal(tool.parameters.properties.path.type, 'string');
assert.deepEqual(tool.parameters.properties.base.type, ['string', 'null']);
assert.equal(tool.parameters.properties.base.pattern, '^[a-f0-9]{40}$');
if (name === 'library_doc_write') {
assert.equal(tool.parameters.properties.content.type, 'string');
assert.match(tool.parameters.properties.base.description, /null to create/);
} else {
assert.match(tool.parameters.properties.base.description, /existing file/);
assert.doesNotMatch(tool.parameters.properties.base.description, /create/);
}
for (const field of ['repo', 'commit', 'business', 'accept', 'project', 'identity', 'author', 'credential', 'agent', 'message'])
assert.equal(Object.hasOwn(tool.parameters.properties, field), false, `${name} ${field}`);
}
});
test('C2-02 tools: no library, noncreative or no within-role write grant exposes neither write tool', t => {
const creative = role(t, 'ideas');
assert.equal(typedTools({ resolved: creative }).some(tool => WRITES.includes(tool.name)), false);
for (const instance of ['coder', 'reviewer', 'pm', 'chief'])
assert.equal(typedTools({ resolved: resolvedFor(t, instance), library: true }).some(tool => WRITES.includes(tool.name)), false);
const narrowed = structuredClone(creative);
narrowed.limits.authority.withinRole = narrowed.limits.authority.withinRole.filter(action => action !== 'library.write');
narrowed.limits.authority.crossRole.push('library.write');
assert.deepEqual(typedTools({ resolved: narrowed, library: true }).map(tool => tool.name), ['send_message', ...LOOKUPS]);
});