Files
gbrain/src/core/skillpack/init-brain-pack.ts
T
9d88680a51 v0.42.47.0 feat(skillpack,advisor): brain-resident skillpacks + proactive gbrain advisor (#2180) (#2231)
* 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>
2026-06-16 22:45:22 -07:00

216 lines
7.0 KiB
TypeScript

/**
* skillpack/init-brain-pack.ts — `gbrain skillpack init-brain-pack` scaffold.
*
* Writes a starter brain-resident skillpack into a brain/source repo: skills
* co-evolved with that brain, versioned in its repo, discovered by any harness
* that connects (Topology A `sources add` advisory + the `list_brain_skillpack`
* MCP tool). Distinct from `runInitScaffold` (the registry-pack cathedral with
* test/e2e/eval trees) — a brain-resident pack lives BESIDE brain content, so
* the scaffold is lean and the README is the machine-parseable entry point a
* connecting harness reads.
*
* Reuses the refuse-overwrite `applyWritePlan` contract from init-scaffold.ts.
* Sets `brain_resident: true` and pins `gbrain_min_version` to the EXACT serving
* version (not major.minor.0) so a pack that depends on a just-shipped op can't
* silently install on a binary that predates it.
*/
import { applyWritePlan, type WritePlanEntry } from './init-scaffold.ts';
import { SKILLPACK_API_VERSION, type SkillpackManifest } from './manifest-v1.ts';
import { VERSION } from '../../version.ts';
import { join } from 'path';
export interface InitBrainPackOptions {
/** Brain/source repo root — where skillpack.json + README land. */
targetDir: string;
/** Pack name (lowercase kebab; becomes manifest.name). */
name: string;
/** Schema pack these skills assume (default "gbrain-base"). */
schemaPack?: string;
/** Optional initial skill slug (default: <pack-name>). */
firstSkillSlug?: string;
/** Pre-fill author + license + homepage. */
author?: string;
license?: string;
homepage?: string;
/** Pin gbrain_min_version to this exact version (default: serving VERSION). */
gbrainVersion?: string;
/** Dry-run: report intent without writing. */
dryRun?: boolean;
}
export interface InitBrainPackResult {
targetDir: string;
filesWritten: string[];
filesSkippedExisting: string[];
manifest: SkillpackManifest;
}
export class InitBrainPackError extends Error {
constructor(
message: string,
public code: 'invalid_name',
) {
super(message);
this.name = 'InitBrainPackError';
}
}
const NAME_RE = /^[a-z][a-z0-9-]{1,63}$/;
/** Build the brain-resident pack scaffold. */
export function runInitBrainPack(opts: InitBrainPackOptions): InitBrainPackResult {
if (!NAME_RE.test(opts.name)) {
throw new InitBrainPackError(
`name "${opts.name}" is not lowercase kebab-case (must match ${NAME_RE.source})`,
'invalid_name',
);
}
const firstSlug = opts.firstSkillSlug ?? opts.name;
if (!NAME_RE.test(firstSlug)) {
throw new InitBrainPackError(
`first-skill slug "${firstSlug}" is not lowercase kebab-case`,
'invalid_name',
);
}
const schemaPack = opts.schemaPack ?? 'gbrain-base';
if (!NAME_RE.test(schemaPack)) {
throw new InitBrainPackError(
`schema_pack "${schemaPack}" is not lowercase kebab-case`,
'invalid_name',
);
}
// Pin to the EXACT serving version (#13) so a pack depending on a just-shipped
// op can't install on an older binary that lacks it.
const minVersion = opts.gbrainVersion ?? VERSION;
const manifest: SkillpackManifest = {
api_version: SKILLPACK_API_VERSION,
name: opts.name,
version: '0.1.0',
description: `(edit me) one-line description of the ${opts.name} brain pack`,
author: opts.author ?? 'Your Name <you@example.com>',
license: opts.license ?? 'MIT',
homepage: opts.homepage ?? `https://github.com/your-user/${opts.name}`,
gbrain_min_version: minVersion,
skills: [`skills/${firstSlug}`],
runbooks: { bootstrap: 'runbooks/bootstrap.md' },
changelog: 'CHANGELOG.md',
brain_resident: true,
schema_pack: schemaPack,
};
const dateIso = new Date().toISOString().slice(0, 10);
const plan: WritePlanEntry[] = [];
plan.push({
path: join(opts.targetDir, 'skillpack.json'),
content: JSON.stringify(manifest, null, 2) + '\n',
});
plan.push({
path: join(opts.targetDir, `skills/${firstSlug}/SKILL.md`),
content: [
'---',
`name: ${firstSlug}`,
`description: (edit me) one-line description of what ${firstSlug} does for this brain`,
'mutating: false',
'triggers:',
` - example trigger phrase 1 for ${firstSlug}`,
` - example trigger phrase 2 for ${firstSlug}`,
'# tools: list gbrain ops this skill calls (e.g. [search, put_page]); used by the',
'# version-skew lint to fail loud if a connecting gbrain lacks them.',
'---',
'',
`# ${firstSlug}`,
'',
'(edit me) What this skill does for THIS brain, the conventions it honors, and',
'the user-facing contract. A harness reads this top-to-bottom when the user',
'phrasing matches a trigger above.',
'',
].join('\n'),
});
const intents = [1, 2, 3, 4, 5].map((n) => ({
intent: `example phrase ${n} for ${firstSlug}`,
expected_skill: firstSlug,
}));
plan.push({
path: join(opts.targetDir, `skills/${firstSlug}/routing-eval.jsonl`),
content: intents.map((x) => JSON.stringify(x)).join('\n') + '\n',
});
plan.push({
path: join(opts.targetDir, 'runbooks/bootstrap.md'),
content: [
'# Bootstrap',
'',
'Post-scaffold steps. gbrain displays this but does NOT auto-execute.',
'The harness reads it and walks per-step at its own discretion.',
'',
`1. show user: "${opts.name} is installed. Try a trigger phrase from skills/${firstSlug}/SKILL.md."`,
`2. (edit me) agent: honor the conventions in README.md section 4 before writing.`,
'',
].join('\n'),
});
// Machine-parseable README — the 5 stable headings a connecting harness scans.
plan.push({
path: join(opts.targetDir, 'README.md'),
content: [
`# ${opts.name} (brain-resident skillpack)`,
'',
'## 1. What this brain is',
'',
'(edit me) One paragraph describing this brain — mirrors `get_brain_identity`.',
'',
'## 2. Skills in this pack',
'',
`- \`${firstSlug}\` — (edit me) one-line description (when to use it; any schema/version assumptions)`,
'',
'## 3. Install',
'',
'Ask the user first, then:',
'',
'```bash',
`gbrain skillpack scaffold <this-repo>`,
'```',
'',
'## 4. Conventions this brain expects',
'',
'(edit me) The operating rules a connecting harness should honor (e.g. "always',
'search before writing", "meetings propagate to all entity pages").',
'',
'## 5. Version compatibility',
'',
`- gbrain_min_version: ${minVersion}`,
`- schema_pack: ${schemaPack}`,
'',
].join('\n'),
});
plan.push({
path: join(opts.targetDir, 'CHANGELOG.md'),
content: [
'# Changelog',
'',
'All notable changes documented in Keep-a-Changelog shape.',
'',
`## [0.1.0] - ${dateIso}`,
'',
'- Initial release.',
'',
].join('\n'),
});
const { written, skipped } = applyWritePlan(plan, { dryRun: opts.dryRun });
return {
targetDir: opts.targetDir,
filesWritten: written,
filesSkippedExisting: skipped,
manifest,
};
}