mirror of https://github.com/garrytan/gstack.git
fix(resolvers): rewrite all gbrain put_page instructions to canonical put <slug>
scripts/resolvers/gbrain.ts emitted user-facing copy-paste instructions using the renamed `gbrain put_page` subcommand across 10 skills (office-hours, investigate, plan-ceo-review, retro, plan-eng-review, ship, cso, design-consultation, fallback, entity-stub). Every gstack user copying those snippets hit "unknown command: put_page" on gbrain v0.18+. This commit: - Rewrites all 10 instruction templates to use `gbrain put <slug> --content "$(cat <<EOF...EOF)"` with title/tags moved into YAML frontmatter inside --content, matching the v0.18+ subcommand shape - Updates README.md and USING_GBRAIN_WITH_GSTACK.md "common commands" table to reference `gbrain put` and `gbrain get` - Adds test/resolvers-gbrain-put-rewrite.test.ts pinning two invariants: (a) resolver source ships only canonical instructions, (b) every tracked SKILL.md file is free of `gbrain put_page` CHANGELOG entries are deliberately left untouched (historical record). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
765e0d3195
commit
2da1c4e5cd
|
|
@ -395,7 +395,7 @@ Four paths, pick one:
|
|||
- **PGLite local** — zero accounts, zero network, ~30 seconds. Isolated brain on this Mac only. Great for try-first; migrate to Supabase later with `/setup-gbrain --switch`.
|
||||
- **Remote gbrain MCP** — your brain runs on another machine (Tailscale, ngrok, internal LAN) or a teammate's server; paste an MCP URL and bearer token. Optionally pair with a local PGLite for symbol-aware code search in split-engine mode. Best for cross-machine memory without standing up a local DB.
|
||||
|
||||
After init, the skill offers to register gbrain as an MCP server for Claude Code (`claude mcp add gbrain -- gbrain serve`) so `gbrain search`, `gbrain put_page`, etc. show up as first-class typed tools — not bash shell-outs.
|
||||
After init, the skill offers to register gbrain as an MCP server for Claude Code (`claude mcp add gbrain -- gbrain serve`) so `gbrain search`, `gbrain put`, etc. show up as first-class typed tools — not bash shell-outs.
|
||||
|
||||
**Keeping the brain current.** Run `/sync-gbrain` from any repo to re-index its code into gbrain (incremental by default, `--full` for a full reindex, `--dry-run` to preview). The skill registers the cwd as a federated source via `gbrain sources add`, runs `gbrain sync --strategy code`, and writes a `## GBrain Search Guidance` block to your project's CLAUDE.md so the agent prefers `gbrain search`/`code-def`/`code-refs` over Grep. The block is removed automatically if the capability check fails — no stale guidance pointing at tools that aren't installed.
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ By default the skill asks "Give Claude Code a typed tool surface for gbrain?" If
|
|||
claude mcp add gbrain -- gbrain serve
|
||||
```
|
||||
|
||||
That registers gbrain's stdio MCP server with Claude Code. Now `gbrain search`, `gbrain put_page`, `gbrain get_page`, etc. show up as first-class tools in every session, not bash shell-outs.
|
||||
That registers gbrain's stdio MCP server with Claude Code. Now `gbrain search`, `gbrain put`, `gbrain get`, etc. show up as first-class tools in every session, not bash shell-outs.
|
||||
|
||||
**If `claude` is not on PATH**, the skill skips MCP registration gracefully with a manual-register hint. The CLI resolver still works from any skill that shells out to `gbrain` — MCP is an upgrade, not a prerequisite.
|
||||
|
||||
|
|
@ -224,8 +224,8 @@ Gbrain itself ships with these that gstack wraps:
|
|||
| `gbrain migrate --to supabase --url ...` | Move a PGLite brain to Supabase (lossless, preserves source as backup) |
|
||||
| `gbrain migrate --to pglite` | Reverse migration |
|
||||
| `gbrain search "query"` | Search the brain |
|
||||
| `gbrain put_page --title "..." --tags "a,b" <<<"content"` | Write a page |
|
||||
| `gbrain get_page "<slug>"` | Fetch a page |
|
||||
| `gbrain put "<slug>" --content "<markdown-with-frontmatter>"` | Write a page (title/tags go in YAML frontmatter inside `--content`) |
|
||||
| `gbrain get "<slug>"` | Fetch a page |
|
||||
| `gbrain serve` | Start the MCP stdio server (used by `claude mcp add`) |
|
||||
|
||||
### Config files + state
|
||||
|
|
|
|||
|
|
@ -37,18 +37,22 @@ Any non-zero exit code from gbrain commands should be treated as a transient fai
|
|||
}
|
||||
|
||||
export function generateGBrainSaveResults(ctx: TemplateContext): string {
|
||||
// gbrain v0.18+ renamed `put_page` → `put <slug>` and moved --title/--tags
|
||||
// into YAML frontmatter inside --content. These templates render into
|
||||
// SKILL.md files as user-facing instructions; using the old subcommand
|
||||
// ships broken copy-paste to every gstack user.
|
||||
const skillSaveMap: Record<string, string> = {
|
||||
'office-hours': 'Save the design document as a brain page:\n```bash\ngbrain put_page --title "Office Hours: <project name>" --tags "design-doc,<project-slug>" <<\'EOF\'\n<design doc content in markdown>\nEOF\n```',
|
||||
'investigate': 'Save the root cause analysis as a brain page:\n```bash\ngbrain put_page --title "Investigation: <issue summary>" --tags "investigation,<affected-files>" <<\'EOF\'\n<investigation findings in markdown>\nEOF\n```',
|
||||
'plan-ceo-review': 'Save the CEO plan as a brain page:\n```bash\ngbrain put_page --title "CEO Plan: <feature name>" --tags "ceo-plan,<feature-slug>" <<\'EOF\'\n<scope decisions and vision in markdown>\nEOF\n```',
|
||||
'retro': 'Save the retrospective as a brain page:\n```bash\ngbrain put_page --title "Retro: <date range>" --tags "retro,<date>" <<\'EOF\'\n<retro output in markdown>\nEOF\n```',
|
||||
'plan-eng-review': 'Save the architecture decisions as a brain page:\n```bash\ngbrain put_page --title "Eng Review: <feature name>" --tags "eng-review,<feature-slug>" <<\'EOF\'\n<review findings and decisions in markdown>\nEOF\n```',
|
||||
'ship': 'Save the release notes as a brain page:\n```bash\ngbrain put_page --title "Release: <version>" --tags "release,<version>" <<\'EOF\'\n<changelog entry and deploy details in markdown>\nEOF\n```',
|
||||
'cso': 'Save the security audit as a brain page:\n```bash\ngbrain put_page --title "Security Audit: <date>" --tags "security-audit,<date>" <<\'EOF\'\n<findings and remediation status in markdown>\nEOF\n```',
|
||||
'design-consultation': 'Save the design system as a brain page:\n```bash\ngbrain put_page --title "Design System: <project name>" --tags "design-system,<project-slug>" <<\'EOF\'\n<design decisions in markdown>\nEOF\n```',
|
||||
'office-hours': 'Save the design document as a brain page:\n```bash\ngbrain put "office-hours/<project-slug>" --content "$(cat <<\'EOF\'\n---\ntitle: "Office Hours: <project name>"\ntags: [design-doc, <project-slug>]\n---\n<design doc content in markdown>\nEOF\n)"\n```',
|
||||
'investigate': 'Save the root cause analysis as a brain page:\n```bash\ngbrain put "investigations/<issue-slug>" --content "$(cat <<\'EOF\'\n---\ntitle: "Investigation: <issue summary>"\ntags: [investigation, <affected-files>]\n---\n<investigation findings in markdown>\nEOF\n)"\n```',
|
||||
'plan-ceo-review': 'Save the CEO plan as a brain page:\n```bash\ngbrain put "ceo-plans/<feature-slug>" --content "$(cat <<\'EOF\'\n---\ntitle: "CEO Plan: <feature name>"\ntags: [ceo-plan, <feature-slug>]\n---\n<scope decisions and vision in markdown>\nEOF\n)"\n```',
|
||||
'retro': 'Save the retrospective as a brain page:\n```bash\ngbrain put "retros/<date>" --content "$(cat <<\'EOF\'\n---\ntitle: "Retro: <date range>"\ntags: [retro, <date>]\n---\n<retro output in markdown>\nEOF\n)"\n```',
|
||||
'plan-eng-review': 'Save the architecture decisions as a brain page:\n```bash\ngbrain put "eng-reviews/<feature-slug>" --content "$(cat <<\'EOF\'\n---\ntitle: "Eng Review: <feature name>"\ntags: [eng-review, <feature-slug>]\n---\n<review findings and decisions in markdown>\nEOF\n)"\n```',
|
||||
'ship': 'Save the release notes as a brain page:\n```bash\ngbrain put "releases/<version>" --content "$(cat <<\'EOF\'\n---\ntitle: "Release: <version>"\ntags: [release, <version>]\n---\n<changelog entry and deploy details in markdown>\nEOF\n)"\n```',
|
||||
'cso': 'Save the security audit as a brain page:\n```bash\ngbrain put "security-audits/<date>" --content "$(cat <<\'EOF\'\n---\ntitle: "Security Audit: <date>"\ntags: [security-audit, <date>]\n---\n<findings and remediation status in markdown>\nEOF\n)"\n```',
|
||||
'design-consultation': 'Save the design system as a brain page:\n```bash\ngbrain put "design-systems/<project-slug>" --content "$(cat <<\'EOF\'\n---\ntitle: "Design System: <project name>"\ntags: [design-system, <project-slug>]\n---\n<design decisions in markdown>\nEOF\n)"\n```',
|
||||
};
|
||||
|
||||
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 "<descriptive title>" --tags "<relevant,tags>" <<\'EOF\'\n<content in markdown>\nEOF\n```';
|
||||
const saveInstruction = skillSaveMap[ctx.skillName] || 'Save the skill output as a brain page if the results are worth preserving:\n```bash\ngbrain put "<slug>" --content "$(cat <<\'EOF\'\n---\ntitle: "<descriptive title>"\ntags: [<relevant>, <tags>]\n---\n<content in markdown>\nEOF\n)"\n```';
|
||||
|
||||
return `## Save Results to Brain
|
||||
|
||||
|
|
@ -58,7 +62,14 @@ ${saveInstruction}
|
|||
|
||||
After saving the page, extract and enrich mentioned entities: for each actual person name or company/organization name found in the output, \`gbrain search "<entity name>"\` to check if a page exists. If not, create a stub page:
|
||||
\`\`\`bash
|
||||
gbrain put_page --title "<Person or Company Name>" --tags "entity,person" --content "Stub page. Mentioned in <skill name> output."
|
||||
gbrain put "entities/<entity-slug>" --content "$(cat <<'EOF'
|
||||
---
|
||||
title: "<Person or Company Name>"
|
||||
tags: [entity, person]
|
||||
---
|
||||
Stub page. Mentioned in <skill name> output.
|
||||
EOF
|
||||
)"
|
||||
\`\`\`
|
||||
Only extract actual person names and company/organization names. Skip product names, section headings, technical terms, and file paths.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* Regression pin: scripts/resolvers/gbrain.ts must emit `gbrain put <slug>`
|
||||
* (the v0.18+ subcommand), never the renamed `gbrain put_page`. The resolver
|
||||
* output ships into every generated SKILL.md file as user-facing
|
||||
* copy-paste instructions; using the old subcommand teaches every
|
||||
* gstack user to invoke a command that no longer exists.
|
||||
*
|
||||
* Two checks:
|
||||
* 1. Resolver source: scripts/resolvers/gbrain.ts has no `put_page`
|
||||
* tokens in active strings (comments OK — one annotated reference
|
||||
* explains the rename for future contributors).
|
||||
* 2. Generated SKILL.md: every tracked SKILL.md file is free of
|
||||
* `gbrain put_page`. Run `bun run gen:skill-docs` if this fails.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "bun:test";
|
||||
import { readFileSync, readdirSync, statSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { execFileSync } from "child_process";
|
||||
|
||||
const REPO_ROOT = join(import.meta.dir, "..");
|
||||
const RESOLVER_PATH = join(REPO_ROOT, "scripts", "resolvers", "gbrain.ts");
|
||||
|
||||
function stripComments(src: string): string {
|
||||
// Strip block comments first (may span newlines, may contain `//`).
|
||||
const noBlock = src.replace(/\/\*[\s\S]*?\*\//g, "");
|
||||
return noBlock.replace(/\/\/[^\n]*/g, "");
|
||||
}
|
||||
|
||||
function listTrackedSkillMd(): string[] {
|
||||
const out = execFileSync("git", ["ls-files", "*SKILL.md"], {
|
||||
cwd: REPO_ROOT,
|
||||
encoding: "utf-8",
|
||||
});
|
||||
return out.split("\n").filter((line) => line.trim().length > 0);
|
||||
}
|
||||
|
||||
describe("scripts/resolvers/gbrain.ts — no put_page in emitted instructions (regression for #1346)", () => {
|
||||
it("resolver source ships only `gbrain put` instructions, not the renamed `put_page`", () => {
|
||||
const src = readFileSync(RESOLVER_PATH, "utf-8");
|
||||
const stripped = stripComments(src);
|
||||
expect(stripped).not.toContain("put_page");
|
||||
});
|
||||
|
||||
it("every tracked SKILL.md file is free of the renamed gbrain put_page subcommand", () => {
|
||||
const files = listTrackedSkillMd();
|
||||
const offenders: string[] = [];
|
||||
for (const f of files) {
|
||||
const content = readFileSync(join(REPO_ROOT, f), "utf-8");
|
||||
if (content.includes("gbrain put_page")) {
|
||||
offenders.push(f);
|
||||
}
|
||||
}
|
||||
if (offenders.length > 0) {
|
||||
throw new Error(
|
||||
`Generated SKILL.md files still reference 'gbrain put_page'. ` +
|
||||
`Run 'bun run gen:skill-docs' to regenerate after editing ` +
|
||||
`scripts/resolvers/gbrain.ts. Offenders:\n - ${offenders.join("\n - ")}`,
|
||||
);
|
||||
}
|
||||
expect(offenders).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue