diff --git a/agents/gsd-phase-researcher.md b/agents/gsd-phase-researcher.md index 91f33d438..eeb1032f1 100644 --- a/agents/gsd-phase-researcher.md +++ b/agents/gsd-phase-researcher.md @@ -97,6 +97,22 @@ When researching "best library for X": find what the ecosystem actually uses, do **WebSearch tips:** Always include current year. Use multiple query variations. Cross-verify with authoritative sources. +## Enhanced Web Search (Brave API) + +Check `brave_search` from init context. If `true`, use Brave Search for higher quality results: + +```bash +node ~/.claude/get-shit-done/bin/gsd-tools.js websearch "your query" --limit 10 +``` + +**Options:** +- `--limit N` — Number of results (default: 10) +- `--freshness day|week|month` — Restrict to recent content + +If `brave_search: false` (or not set), use built-in WebSearch tool instead. + +Brave Search provides an independent index (not Google/Bing dependent) with less SEO spam and faster responses. + ## Verification Protocol **WebSearch findings MUST be verified:** diff --git a/agents/gsd-project-researcher.md b/agents/gsd-project-researcher.md index b6fc9a6f6..c872e00aa 100644 --- a/agents/gsd-project-researcher.md +++ b/agents/gsd-project-researcher.md @@ -91,6 +91,22 @@ Problems: "[tech] common mistakes", "[tech] gotchas" Always include current year. Use multiple query variations. Mark WebSearch-only findings as LOW confidence. +### Enhanced Web Search (Brave API) + +Check `brave_search` from orchestrator context. If `true`, use Brave Search for higher quality results: + +```bash +node ~/.claude/get-shit-done/bin/gsd-tools.js websearch "your query" --limit 10 +``` + +**Options:** +- `--limit N` — Number of results (default: 10) +- `--freshness day|week|month` — Restrict to recent content + +If `brave_search: false` (or not set), use built-in WebSearch tool instead. + +Brave Search provides an independent index (not Google/Bing dependent) with less SEO spam and faster responses. + ## Verification Protocol **WebSearch findings must be verified:** diff --git a/get-shit-done/bin/gsd-tools.js b/get-shit-done/bin/gsd-tools.js index 7ae9b8ff9..b04e65b93 100755 --- a/get-shit-done/bin/gsd-tools.js +++ b/get-shit-done/bin/gsd-tools.js @@ -26,6 +26,8 @@ * summary-extract [--fields] Extract structured data from SUMMARY.md * state-snapshot Structured parse of STATE.md * phase-plan-index Index plans with waves and status + * websearch Search web via Brave API (if configured) + * [--limit N] [--freshness day|week|month] * * Phase Operations: * phase next-decimal Calculate next decimal phase number @@ -165,6 +167,7 @@ function loadConfig(cwd) { plan_checker: true, verifier: true, parallelization: true, + brave_search: false, }; try { @@ -197,6 +200,7 @@ function loadConfig(cwd) { plan_checker: get('plan_checker', { section: 'workflow', field: 'plan_check' }) ?? defaults.plan_checker, verifier: get('verifier', { section: 'workflow', field: 'verifier' }) ?? defaults.verifier, parallelization, + brave_search: get('brave_search') ?? defaults.brave_search, }; } catch { return defaults; @@ -584,6 +588,11 @@ function cmdConfigEnsureSection(cwd, raw) { return; } + // Detect Brave Search API key availability + const homedir = require('os').homedir(); + const braveKeyFile = path.join(homedir, '.gsd', 'brave_api_key'); + const hasBraveSearch = !!(process.env.BRAVE_API_KEY || fs.existsSync(braveKeyFile)); + // Create default config const defaults = { model_profile: 'balanced', @@ -598,6 +607,7 @@ function cmdConfigEnsureSection(cwd, raw) { verifier: true, }, parallelization: true, + brave_search: hasBraveSearch, }; try { @@ -1987,6 +1997,70 @@ function cmdSummaryExtract(cwd, summaryPath, fields, raw) { output(fullResult, raw); } +// ─── Web Search (Brave API) ────────────────────────────────────────────────── + +async function cmdWebsearch(query, options, raw) { + const apiKey = process.env.BRAVE_API_KEY; + + if (!apiKey) { + // No key = silent skip, agent falls back to built-in WebSearch + output({ available: false, reason: 'BRAVE_API_KEY not set' }, raw, ''); + return; + } + + if (!query) { + output({ available: false, error: 'Query required' }, raw, ''); + return; + } + + const params = new URLSearchParams({ + q: query, + count: String(options.limit || 10), + country: 'us', + search_lang: 'en', + text_decorations: 'false' + }); + + if (options.freshness) { + params.set('freshness', options.freshness); + } + + try { + const response = await fetch( + `https://api.search.brave.com/res/v1/web/search?${params}`, + { + headers: { + 'Accept': 'application/json', + 'X-Subscription-Token': apiKey + } + } + ); + + if (!response.ok) { + output({ available: false, error: `API error: ${response.status}` }, raw, ''); + return; + } + + const data = await response.json(); + + const results = (data.web?.results || []).map(r => ({ + title: r.title, + url: r.url, + description: r.description, + age: r.age || null + })); + + output({ + available: true, + query, + count: results.length, + results + }, raw, results.map(r => `${r.title}\n${r.url}\n${r.description}`).join('\n\n')); + } catch (err) { + output({ available: false, error: err.message }, raw, ''); + } +} + // ─── Frontmatter CRUD ──────────────────────────────────────────────────────── function cmdFrontmatterGet(cwd, filePath, field, raw) { @@ -3659,6 +3733,11 @@ function cmdInitPlanPhase(cwd, phase, includes, raw) { function cmdInitNewProject(cwd, raw) { const config = loadConfig(cwd); + // Detect Brave Search API key availability + const homedir = require('os').homedir(); + const braveKeyFile = path.join(homedir, '.gsd', 'brave_api_key'); + const hasBraveSearch = !!(process.env.BRAVE_API_KEY || fs.existsSync(braveKeyFile)); + // Detect existing code let hasCode = false; let hasPackageFile = false; @@ -3699,6 +3778,9 @@ function cmdInitNewProject(cwd, raw) { // Git state has_git: pathExistsInternal(cwd, '.git'), + + // Enhanced search + brave_search_available: hasBraveSearch, }; output(result, raw); @@ -3841,6 +3923,7 @@ function cmdInitPhaseOp(cwd, phase, raw) { const result = { // Config commit_docs: config.commit_docs, + brave_search: config.brave_search, // Phase info phase_found: !!phaseInfo, @@ -4128,7 +4211,7 @@ function cmdInitProgress(cwd, includes, raw) { // ─── CLI Router ─────────────────────────────────────────────────────────────── -function main() { +async function main() { const args = process.argv.slice(2); const rawIndex = args.indexOf('--raw'); const raw = rawIndex !== -1; @@ -4495,6 +4578,17 @@ function main() { break; } + case 'websearch': { + const query = args[1]; + const limitIdx = args.indexOf('--limit'); + const freshnessIdx = args.indexOf('--freshness'); + await cmdWebsearch(query, { + limit: limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : 10, + freshness: freshnessIdx !== -1 ? args[freshnessIdx + 1] : null, + }, raw); + break; + } + default: error(`Unknown command: ${command}`); }