mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 21:19:18 +00:00
* feat(skillpack): brain_resident manifest fields + init-brain-pack scaffolder + tools version-skew lint Add optional brain_resident/schema_pack to the v1 manifest (additive, forward-compatible). New runInitBrainPack scaffolds a brain-resident pack (brain_resident:true, exact gbrain_min_version, 5-section machine-parseable README) beside brain content; applyWritePlan factored out of init-scaffold. brain-pack-lint validates each skill's declared tools: against the serving op set (E6 version-skew). Wires gbrain skillpack init-brain-pack. * feat(skillpack): Topology A brain-pack discovery on sources add + bounded nag After 'gbrain sources add', if the source ships a brain_resident pack, print an agent-readable advisory (ask the user before scaffolding). nag-state.ts tracks declines per (source-repo brain_id, source, pack) with escalate-then-suppress; declines count only on CLI-interactive displays, never cron/MCP. Fail-open: a malformed/absent pack never breaks sources add. * feat(advisor,skillpack): list_brain_skillpack MCP tool + gbrain advisor Topology B: dedicated source-scoped list_brain_skillpack op + get_skill source_id disambiguation (brain-resident-locate.ts); git scaffold-spec never a server FS path; source-aware schema match. LEARN_INSTRUCTION + serve-http banner. gbrain advisor: read-only ranked actions from brain state (8 resilient collectors, shared renderer, JSONL history, --json severity exit codes, local-only argv --apply dispatcher). Exposed over MCP behind mcp.publish_advisor (default off, read-only on remote; workspace collectors no-op remotely). Generalizes post-install-advisory to a single current-state recommended set (install→scaffold). * feat(skills): bundle gbrain-advisor skill + weekly cron recipe + ranking eval skills/gbrain-advisor teaches a harness to run gbrain advisor on a cadence and ping the user (read-only; ask before fixing). Registered in manifest.json, RESOLVER.md, openclaw.plugin.json. E4 ranking-precision eval on seeded-defect fixtures (100%). * chore: bump version and changelog (v0.42.47.0) Brain-resident skillpacks + gbrain advisor (#2180). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: sync CLAUDE.md + KEY_FILES for brain-resident skillpacks + advisor (#2180) Skill count 29->30, Skills section gains the brain-resident skillpacks + advisor capability, KEY_FILES gets current-state entries for the new modules. Regenerate llms bundles. * test,fix: align stale assertions with generalized advisory + advisor resolver triggers (#2180) - post-install-advisory.test.ts: install→scaffold wording (the install verb was removed); restore two-column in book-mirror copy; drop the removed skillpack-list line. - RESOLVER.md: gbrain-advisor trigger now fuzzy-matches a declared frontmatter trigger (resolver round-trip D5/C). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: regenerate llms bundle for updated advisor resolver row (#2180) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
84 lines
3.7 KiB
TypeScript
84 lines
3.7 KiB
TypeScript
/**
|
|
* Tests for src/core/skillpack/init-brain-pack.ts (the brain-resident pack
|
|
* scaffolder) and src/core/skillpack/brain-pack-lint.ts (E6 version-skew lint).
|
|
*/
|
|
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
import { mkdtempSync, rmSync, readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
import { tmpdir } from 'os';
|
|
import { join } from 'path';
|
|
|
|
import {
|
|
runInitBrainPack,
|
|
InitBrainPackError,
|
|
} from '../src/core/skillpack/init-brain-pack.ts';
|
|
import { validateSkillpackManifest } from '../src/core/skillpack/manifest-v1.ts';
|
|
import { lintBrainPackTools } from '../src/core/skillpack/brain-pack-lint.ts';
|
|
import { VERSION } from '../src/version.ts';
|
|
|
|
let dir: string;
|
|
beforeEach(() => {
|
|
dir = mkdtempSync(join(tmpdir(), 'gbrain-brainpack-'));
|
|
});
|
|
afterEach(() => {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
describe('runInitBrainPack', () => {
|
|
test('writes a brain_resident manifest pinned to exact VERSION', () => {
|
|
const result = runInitBrainPack({ targetDir: dir, name: 'deal-brain', schemaPack: 'gbrain-base' });
|
|
expect(result.manifest.brain_resident).toBe(true);
|
|
expect(result.manifest.schema_pack).toBe('gbrain-base');
|
|
expect(result.manifest.gbrain_min_version).toBe(VERSION); // exact, not major.minor.0
|
|
// round-trips through the validator
|
|
const raw = JSON.parse(readFileSync(join(dir, 'skillpack.json'), 'utf-8'));
|
|
expect(() => validateSkillpackManifest(raw)).not.toThrow();
|
|
});
|
|
|
|
test('README has all 5 stable machine-parseable headings', () => {
|
|
runInitBrainPack({ targetDir: dir, name: 'deal-brain' });
|
|
const readme = readFileSync(join(dir, 'README.md'), 'utf-8');
|
|
expect(readme).toContain('## 1. What this brain is');
|
|
expect(readme).toContain('## 2. Skills in this pack');
|
|
expect(readme).toContain('## 3. Install');
|
|
expect(readme).toContain('## 4. Conventions this brain expects');
|
|
expect(readme).toContain('## 5. Version compatibility');
|
|
});
|
|
|
|
test('refuses to overwrite existing files', () => {
|
|
writeFileSync(join(dir, 'skillpack.json'), '{"keep":"me"}');
|
|
const result = runInitBrainPack({ targetDir: dir, name: 'deal-brain' });
|
|
expect(result.filesSkippedExisting).toContain(join(dir, 'skillpack.json'));
|
|
expect(readFileSync(join(dir, 'skillpack.json'), 'utf-8')).toBe('{"keep":"me"}');
|
|
});
|
|
|
|
test('dry-run writes nothing', () => {
|
|
const result = runInitBrainPack({ targetDir: dir, name: 'deal-brain', dryRun: true });
|
|
expect(result.filesWritten.length).toBeGreaterThan(0);
|
|
expect(existsSync(join(dir, 'skillpack.json'))).toBe(false);
|
|
});
|
|
|
|
test('rejects non-kebab name', () => {
|
|
expect(() => runInitBrainPack({ targetDir: dir, name: 'Deal Brain' })).toThrow(InitBrainPackError);
|
|
});
|
|
});
|
|
|
|
describe('lintBrainPackTools (E6)', () => {
|
|
test('flags a declared tool the serving op set does not have', () => {
|
|
runInitBrainPack({ targetDir: dir, name: 'deal-brain', firstSkillSlug: 'diligence' });
|
|
// Inject a tools: frontmatter with one known + one unknown op.
|
|
const skillMd = join(dir, 'skills/diligence/SKILL.md');
|
|
writeFileSync(
|
|
skillMd,
|
|
['---', 'name: diligence', 'description: x', 'tools: [search, totally_made_up_op]', '---', '', '# diligence', ''].join('\n'),
|
|
);
|
|
const result = lintBrainPackTools(dir, new Set(['search', 'put_page']));
|
|
expect(result.unknownTools).toEqual([{ skill: 'skills/diligence', tool: 'totally_made_up_op' }]);
|
|
});
|
|
|
|
test('no findings when all tools known (fresh pack has no tools:)', () => {
|
|
runInitBrainPack({ targetDir: dir, name: 'deal-brain', firstSkillSlug: 'diligence' });
|
|
const result = lintBrainPackTools(dir, new Set(['search', 'put_page']));
|
|
expect(result.unknownTools).toEqual([]);
|
|
});
|
|
});
|