refactor(librarian): teach proper ast_grep_search discipline

ast_grep_search was mentioned exactly once in the librarian prompt
(bundled as 'grep/ast_grep_search for function/class') with no syntax
guidance. When the librarian cloned a repo and tried to match code
shape, it fell into the same regex-in-AST trap as the main agent.

Two targeted edits, no rewrite of the surrounding request-classification
flow:

- Phase 1 TYPE B 'Find the implementation' now separates ast_grep_search
  (code shape) from grep (text/literals) and reminds the LLM that AST
  patterns use $VAR and $$$ and are not regex.
- TOOL REFERENCE adds a dedicated ast_grep_search row with valid
  examples and the explicit regex anti-pattern list, alongside tightened
  guidance for grep_app and grep so the LLM picks the right tool for
  cross-repo vs single-repo, text vs shape.
This commit is contained in:
YeonGyu-Kim
2026-04-22 12:29:00 +09:00
parent 5ef2a3fa7c
commit f0dd0464d8
2 changed files with 95 additions and 5 deletions
@@ -0,0 +1,84 @@
/// <reference types="bun-types" />
import { describe, expect, it } from "bun:test"
import { createLibrarianAgent } from "./librarian"
describe("librarian agent ast-grep discipline", () => {
const model = "openai/gpt-5.4-mini-fast"
it("#given the prompt #when inspecting TOOL REFERENCE #then documents ast_grep_search", () => {
// given
const agent = createLibrarianAgent(model)
// when
const prompt = agent.prompt ?? ""
// then
expect(prompt).toContain("ast_grep_search")
expect(prompt).toContain("$$$")
expect(prompt).toContain("function $NAME($$$) { $$$ }")
})
it("#given the prompt #when inspecting #then warns against regex inside ast_grep_search", () => {
// given
const agent = createLibrarianAgent(model)
// when
const prompt = agent.prompt ?? ""
// then
expect(prompt.toLowerCase()).toContain("not regex")
expect(prompt).toContain("|")
expect(prompt).toContain(".*")
expect(prompt).toContain("\\w")
})
it("#given the prompt #when inspecting #then directs LLM to grep/grep_app for text search", () => {
// given
const agent = createLibrarianAgent(model)
// when
const prompt = agent.prompt ?? ""
// then
expect(prompt.toLowerCase()).toContain("for text")
expect(prompt).toContain("grep_app")
})
it("#given the prompt #when inspecting Implementation phase #then recommends ast_grep_search for code shape", () => {
// given
const agent = createLibrarianAgent(model)
// when
const prompt = agent.prompt ?? ""
// then
expect(prompt).toContain("ast_grep_search for code shape")
})
it("#given the prompt #when inspecting #then preserves the evidence + permalink contract", () => {
// given
const agent = createLibrarianAgent(model)
// when
const prompt = agent.prompt ?? ""
// then
expect(prompt).toContain("GitHub permalinks")
expect(prompt).toContain("MANDATORY CITATION FORMAT")
})
it("#given the prompt #when inspecting #then preserves request classification phases", () => {
// given
const agent = createLibrarianAgent(model)
// when
const prompt = agent.prompt ?? ""
// then
expect(prompt).toContain("TYPE A: CONCEPTUAL")
expect(prompt).toContain("TYPE B: IMPLEMENTATION")
expect(prompt).toContain("TYPE C: CONTEXT")
expect(prompt).toContain("TYPE D: COMPREHENSIVE")
})
})
+11 -5
View File
@@ -141,9 +141,11 @@ Step 2: Get commit SHA for permalinks
cd \${TMPDIR:-/tmp}/repo-name && git rev-parse HEAD
Step 3: Find the implementation
- grep/ast_grep_search for function/class
- read the specific file
- git blame for context if needed
- ast_grep_search for code shape (function/class/impl declarations)
pattern must be valid source code using $VAR and $$$ - NOT regex
- grep for string literals, log tags, config keys, comments
- read the specific file for surrounding context
- git blame for history if needed
Step 4: Construct permalink
https://github.com/owner/repo/blob/<sha>/path/to/file#L10-L20
@@ -246,8 +248,12 @@ https://github.com/tanstack/query/blob/abc123def/packages/react-query/src/useQue
- **Sitemap Discovery**: Use webfetch - \`webfetch(docs_url + "/sitemap.xml")\` to understand doc structure
- **Read Doc Page**: Use webfetch - \`webfetch(specific_doc_page)\` for targeted documentation
- **Latest Info**: Use websearch_exa - \`websearch_web_search_exa("query ${new Date().getFullYear()}")\`
- **Fast Code Search**: Use grep_app - \`grep_app_searchGitHub(query, language, useRegexp)\`
- **Deep Code Search**: Use gh CLI - \`gh search code "query" --repo owner/repo\`
- **Fast Code Search (cross-repo, text or regex)**: Use grep_app - \`grep_app_searchGitHub(query, language, useRegexp)\`
- **Deep Code Search (single repo, text or regex)**: Use gh CLI - \`gh search code "query" --repo owner/repo\` or \`grep\` on a cloned repo
- **AST Pattern Search (cloned repo, by code shape)**: Use ast_grep_search on a cloned repo. Patterns are strict AST, NOT regex:
- Valid: \`function $NAME($$$) { $$$ }\`, \`class $C { $$$ }\`, \`impl $T for $S { $$$ }\`, \`callee($$$)\`
- Never pass regex: \`|\` (alternation), \`.*\` (wildcards), \`\\w\` / \`\\d\` (escapes), \`[a-z]\` (character classes)
- For text search or cross-repo search prefer grep_app or grep
- **Clone Repo**: Use gh CLI - \`gh repo clone owner/repo \${TMPDIR:-/tmp}/name -- --depth 1\`
- **Issues/PRs**: Use gh CLI - \`gh search issues/prs "query" --repo owner/repo\`
- **View Issue/PR**: Use gh CLI - \`gh issue/pr view <num> --repo owner/repo --comments\`