From 5da30304e17b721c345353aa58cd888ec4965b75 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Wed, 15 Apr 2026 00:35:40 -0700 Subject: [PATCH] feat: GBrain resolver DX improvements and preamble health check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolver changes: - gbrain query → gbrain search (fast keyword search, not expensive hybrid) - Add keyword extraction guidance for agents - Show explicit gbrain put_page syntax with --title, --tags, heredoc - Add entity enrichment with false-positive filter - Name throttle error patterns (exit code 1, stderr keywords) - Add data-research routing for investigate skill - Expand skillSaveMap from 4 to 8 entries - Add brain operation telemetry summary Preamble changes: - Add gbrain doctor --fast --json health check for gbrain/hermes hosts - Parse check failures/warnings count - Show failing check details when score < 50 Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/resolvers/gbrain.ts | 58 ++++++++++++++++++++++++++--------- scripts/resolvers/preamble.ts | 22 ++++++++++++- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/scripts/resolvers/gbrain.ts b/scripts/resolvers/gbrain.ts index 103dab08..c6e54423 100644 --- a/scripts/resolvers/gbrain.ts +++ b/scripts/resolvers/gbrain.ts @@ -4,33 +4,51 @@ * GBrain is a "mod" for gstack. When installed, coding skills become brain-aware: * they search the brain for context before starting and save results after finishing. * - * These resolvers are suppressed on ALL hosts except gbrain (via suppressedResolvers - * in each host config). For non-gbrain hosts, {{GBRAIN_CONTEXT_LOAD}} and - * {{GBRAIN_SAVE_RESULTS}} resolve to empty string and vanish from the output. + * These resolvers are suppressed on hosts that don't support brain features + * (via suppressedResolvers in each host config). For those hosts, + * {{GBRAIN_CONTEXT_LOAD}} and {{GBRAIN_SAVE_RESULTS}} resolve to empty string. + * + * Compatible with GBrain >= v0.10.0 (search CLI, doctor --fast --json, entity enrichment). */ import type { TemplateContext } from './types'; -export function generateGBrainContextLoad(_ctx: TemplateContext): string { - return `## Brain Context Load +export function generateGBrainContextLoad(ctx: TemplateContext): string { + let base = `## Brain Context Load Before starting this skill, search your brain for relevant context: -1. Search GBrain: \`gbrain query ""\` -2. If results found, read the top 3 pages for context -3. Use this brain context to inform your analysis +1. Extract 2-4 keywords from the user's request (nouns, error names, file paths, technical terms). + Search GBrain: \`gbrain search "keyword1 keyword2"\` + Example: for "the login page is broken after deploy", search \`gbrain search "login broken deploy"\` + Search returns lines like: \`[slug] Title (score: 0.85) - first line of content...\` +2. If few results, broaden to the single most specific keyword and search again. +3. For each result page, read it: \`gbrain get_page ""\` + Read the top 3 pages for context. +4. Use this brain context to inform your analysis. -If GBrain is not available or returns no results, proceed without brain context.`; +If GBrain is not available or returns no results, proceed without brain context. +Any non-zero exit code from gbrain commands should be treated as a transient failure.`; + + if (ctx.skillName === 'investigate') { + base += `\n\nIf the user's request is about tracking, extracting, or researching structured data (e.g., "track this data", "extract from emails", "build a tracker"), route to GBrain's data-research skill instead: \`gbrain call data-research\`. This skill has a 7-phase pipeline optimized for structured data extraction.`; + } + + return base; } export function generateGBrainSaveResults(ctx: TemplateContext): string { const skillSaveMap: Record = { - 'office-hours': 'Save the design document as a brain page: `gbrain put_page` with the design doc content, tagged with the project slug and "design-doc".', - 'investigate': 'Save the root cause analysis as a brain page: `gbrain put_page` with the investigation findings, tagged with affected files and "investigation".', - 'plan-ceo-review': 'Save the CEO plan as a brain page: `gbrain put_page` with the scope decisions and vision, tagged with the feature slug and "ceo-plan".', - 'retro': 'Save the retrospective as a brain page: `gbrain put_page` with the retro output, tagged with the date range and "retro".', + 'office-hours': 'Save the design document as a brain page:\n```bash\ngbrain put_page --title "Office Hours: " --tags "design-doc," <<\'EOF\'\n\nEOF\n```', + 'investigate': 'Save the root cause analysis as a brain page:\n```bash\ngbrain put_page --title "Investigation: " --tags "investigation," <<\'EOF\'\n\nEOF\n```', + 'plan-ceo-review': 'Save the CEO plan as a brain page:\n```bash\ngbrain put_page --title "CEO Plan: " --tags "ceo-plan," <<\'EOF\'\n\nEOF\n```', + 'retro': 'Save the retrospective as a brain page:\n```bash\ngbrain put_page --title "Retro: " --tags "retro," <<\'EOF\'\n\nEOF\n```', + 'plan-eng-review': 'Save the architecture decisions as a brain page:\n```bash\ngbrain put_page --title "Eng Review: " --tags "eng-review," <<\'EOF\'\n\nEOF\n```', + 'ship': 'Save the release notes as a brain page:\n```bash\ngbrain put_page --title "Release: " --tags "release," <<\'EOF\'\n\nEOF\n```', + 'cso': 'Save the security audit as a brain page:\n```bash\ngbrain put_page --title "Security Audit: " --tags "security-audit," <<\'EOF\'\n\nEOF\n```', + 'design-consultation': 'Save the design system as a brain page:\n```bash\ngbrain put_page --title "Design System: " --tags "design-system," <<\'EOF\'\n\nEOF\n```', }; - const saveInstruction = skillSaveMap[ctx.skillName] || 'Save the skill output as a brain page if the results are worth preserving.'; + const saveInstruction = skillSaveMap[ctx.skillName] || 'Save the skill output as a brain page if the results are worth preserving:\n```bash\ngbrain put_page --title "" --tags "" <<\'EOF\'\n\nEOF\n```'; return `## Save Results to Brain @@ -38,5 +56,15 @@ After completing this skill, persist the results to your brain for future refere ${saveInstruction} -Add backlinks to related brain pages if they exist. If GBrain is not available, skip this step.`; +After saving the page, extract and enrich mentioned entities: for each actual person name or company/organization name found in the output, \`gbrain search ""\` to check if a page exists. If not, create a stub page: +\`\`\`bash +gbrain put_page --title "" --tags "entity,person" --content "Stub page. Mentioned in output." +\`\`\` +Only extract actual person names and company/organization names. Skip product names, section headings, technical terms, and file paths. + +Throttle errors appear as: exit code 1 with stderr containing "throttle", "rate limit", "capacity", or "busy". If GBrain returns a throttle or rate-limit error on any save operation, defer the save and move on. The brain is busy — the content is not lost, just not persisted this run. Any other non-zero exit code should also be treated as a transient failure. + +Add backlinks to related brain pages if they exist. If GBrain is not available, skip this step. + +After brain operations complete, note in your completion output: how many pages were found in the initial search, how many entities were enriched, and whether any operations were throttled. This helps the user see brain utilization over time.`; } diff --git a/scripts/resolvers/preamble.ts b/scripts/resolvers/preamble.ts index d9d7bfb2..00ed546e 100644 --- a/scripts/resolvers/preamble.ts +++ b/scripts/resolvers/preamble.ts @@ -98,7 +98,18 @@ if [ -d ".claude/skills/gstack" ] && [ ! -L ".claude/skills/gstack" ]; then fi echo "VENDORED_GSTACK: $_VENDORED" # Detect spawned session (OpenClaw or other orchestrator) -[ -n "$OPENCLAW_SESSION" ] && echo "SPAWNED_SESSION: true" || true +[ -n "$OPENCLAW_SESSION" ] && echo "SPAWNED_SESSION: true" || true${ctx.host === 'gbrain' || ctx.host === 'hermes' ? ` +# GBrain health check (gbrain/hermes host only) +if command -v gbrain &>/dev/null; then + _BRAIN_JSON=$(gbrain doctor --fast --json 2>/dev/null || echo '{}') + _BRAIN_SCORE=$(echo "$_BRAIN_JSON" | grep -o '"health_score":[0-9]*' | cut -d: -f2) + _BRAIN_FAILS=$(echo "$_BRAIN_JSON" | grep -o '"status":"fail"' | wc -l | tr -d ' ') + _BRAIN_WARNS=$(echo "$_BRAIN_JSON" | grep -o '"status":"warn"' | wc -l | tr -d ' ') + echo "BRAIN_HEALTH: \${_BRAIN_SCORE:-unknown} (\${_BRAIN_FAILS:-0} failures, \${_BRAIN_WARNS:-0} warnings)" + if [ "\${_BRAIN_SCORE:-100}" -lt 50 ] 2>/dev/null; then + echo "$_BRAIN_JSON" | grep -o '"name":"[^"]*","status":"[^"]*","message":"[^"]*"' || true + fi +fi` : ''} \`\`\``; } @@ -270,6 +281,14 @@ touch ~/.gstack/.vendoring-warned-\${SLUG:-unknown} This only happens once per project. If the marker file exists, skip entirely.`; } +function generateBrainHealthInstruction(ctx: TemplateContext): string { + if (ctx.host !== 'gbrain' && ctx.host !== 'hermes') return ''; + return `If \`BRAIN_HEALTH\` is shown and the score is below 50, tell the user which checks +failed (shown in the output) and suggest: "Run \\\`gbrain doctor\\\` for full diagnostics." +If the output is not valid JSON or health_score is missing, treat GBrain as unavailable +and proceed without brain features this session.`; +} + function generateSpawnedSessionCheck(): string { return `If \`SPAWNED_SESSION\` is \`"true"\`, you are running inside a session spawned by an AI orchestrator (e.g., OpenClaw). In spawned sessions: @@ -745,6 +764,7 @@ export function generatePreamble(ctx: TemplateContext): string { generateRoutingInjection(ctx), generateVendoringDeprecation(ctx), generateSpawnedSessionCheck(), + generateBrainHealthInstruction(ctx), generateVoiceDirective(tier), ...(tier >= 2 ? [generateContextRecovery(ctx), generateAskUserFormat(ctx), generateCompletenessSection(), generateConfusionProtocol()] : []), ...(tier >= 3 ? [generateRepoModeSection(), generateSearchBeforeBuildingSection(ctx)] : []),