mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +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>
50 lines
1.9 KiB
TypeScript
50 lines
1.9 KiB
TypeScript
/**
|
|
* Tests for the `advisor` MCP op gate (T7 / C3): remote callers need
|
|
* mcp.publish_advisor; local callers bypass; the op is read-only (drops
|
|
* workspace-dependent findings over MCP via runAdvisor's remote filter).
|
|
*/
|
|
import { describe, test, expect } from 'bun:test';
|
|
import { operationsByName, OperationError, type OperationContext } from '../src/core/operations.ts';
|
|
|
|
const advisor = operationsByName['advisor']!;
|
|
|
|
function ctx(over: Partial<OperationContext>, cfg: Record<string, string | null> = {}): OperationContext {
|
|
return {
|
|
engine: {
|
|
getConfig: async (k: string) => cfg[k] ?? null,
|
|
getStats: async () => { throw new Error('no'); },
|
|
getHealth: async () => { throw new Error('no'); },
|
|
executeRaw: async () => { throw new Error('no'); },
|
|
} as unknown as OperationContext['engine'],
|
|
config: {} as OperationContext['config'],
|
|
logger: { info() {}, warn() {}, error() {}, debug() {} } as unknown as OperationContext['logger'],
|
|
dryRun: false,
|
|
remote: true,
|
|
...over,
|
|
} as OperationContext;
|
|
}
|
|
|
|
describe('advisor op gate', () => {
|
|
test('op exists, is read-scoped and not localOnly (exposed over MCP)', () => {
|
|
expect(advisor).toBeDefined();
|
|
expect(advisor.scope).toBe('read');
|
|
expect(advisor.localOnly).not.toBe(true);
|
|
});
|
|
|
|
test('remote without mcp.publish_advisor → permission_denied', async () => {
|
|
await expect(advisor.handler(ctx({ remote: true }), {})).rejects.toBeInstanceOf(OperationError);
|
|
});
|
|
|
|
test('remote WITH mcp.publish_advisor → returns a report', async () => {
|
|
const report = (await advisor.handler(ctx({ remote: true }, { 'mcp.publish_advisor': 'true' }), {})) as {
|
|
findings: unknown[];
|
|
};
|
|
expect(Array.isArray(report.findings)).toBe(true);
|
|
});
|
|
|
|
test('local caller bypasses the gate', async () => {
|
|
const report = (await advisor.handler(ctx({ remote: false }), {})) as { findings: unknown[] };
|
|
expect(Array.isArray(report.findings)).toBe(true);
|
|
});
|
|
});
|