/** * Community Research Scout (standalone, one-shot, run from /senai-brainstorm only). * * Pure function: takes a topic + project keywords + source choice, returns a * structured result with up to 5 entries per category (community / official / * similar). No persistent agent file, no GENERATED_ROLES entry, no agents.json * mapping. Caller wires it through the parent LLM via the subagent tool or * runs it directly inside the discussion handler. * * Design rules (locked from plan v1.0): * - 4 user-pickable sources: "web" | "official" | "community" | "similar" * - 1 hidden short-circuit: cache (24h TTL, content-hash keyed, project-local) * - Cache lives at .IDE_Plans/pi-senai/.cache/community-research/.json * - Hard caps: 5 per category, 1500 tokens output, 30s wall-clock * - Retry once with new keywords on empty result * - Working-result check: HTTP 2xx, body matches topic, <=2yr for version-sensitive * - No license filter on "similar" — quality (working + trusted) wins */ export declare const RESEARCH_SOURCES: readonly ["web", "official", "community", "similar"]; export type ResearchSource = (typeof RESEARCH_SOURCES)[number]; export type Confidence = "high" | "medium" | "low" | "cached"; /** Tier ranking (lower = more trusted). Used by callers that want to filter * by source authority. The scout itself does not filter by tier; it includes * the tier in the output so the caller / display layer can decide. */ export type SourceTier = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8; export interface ResearchSourceEntry { title: string; url: string; summary: string; tier: SourceTier; lastVerified: string; status: number; working: boolean; } export interface ResearchAttempt { query: string; source: ResearchSource; resultCount: number; at: string; } export interface CommunityResearchOutput { source: ResearchSource; community: ResearchSourceEntry[]; official: ResearchSourceEntry[]; similar: ResearchSourceEntry[]; confidence: Confidence; queries: string[]; attempts: ResearchAttempt[]; cached: boolean; ttlExpiresAt: string; } export interface CommunityResearchInput { topic: string; projectKeywords: string[]; source: ResearchSource; cwd: string; /** Optional transcript path to log to on completion. */ transcriptPath?: string; /** Optional brief path to append ## External references to on completion. */ briefPath?: string; /** Wall-clock cap (ms). Default 30000. */ timeoutMs?: number; /** Token cap for output JSON. Default 1500. */ tokenCap?: number; /** Max results per category. Default 5. */ maxPerCategory?: number; /** Inject a fetcher for unit tests. Default uses global fetch with cap. */ fetcher?: ResearchFetcher; } export interface ResearchFetcher { /** Fetch one URL, return status + first N bytes of body. */ fetch(url: string, timeoutMs: number): Promise<{ status: number; body: string; }>; /** Search the chosen source for the given query, return candidate URLs. */ search(query: string, source: ResearchSource): Promise; } /** * Deterministic content-hash for cache lookup. * Key inputs: topic + projectKeywords (sorted) + source + dayBucket (UTC date). */ export declare function cacheKey(topic: string, keywords: string[], source: ResearchSource, dayBucket: string): string; /** UTC date in YYYY-MM-DD form. Used as the dayBucket. */ export declare function dayBucketUtc(d?: Date): string; export declare function readCache(cwd: string, hash: string): CommunityResearchOutput | null; export declare function writeCache(cwd: string, hash: string, output: CommunityResearchOutput): void; export declare function purgeCache(cwd: string): number; export declare function cacheSize(cwd: string): number; export declare function oldestCacheTimestamp(cwd: string): string | null; export declare function isWorking(entry: ResearchSourceEntry): boolean; /** * Build a small set of keyword variations for the retry pass. Drops filler * words, adds version-suffix hints, swaps synonyms. Returns at least one * candidate that differs from the original query. */ export declare function retryWithNewKeywords(originalQuery: string, _topic: string): string[]; /** * Run community research for a single source. Pure-ish: writes cache + * (optionally) transcript + brief section as side-effects. Returns the * structured output so the caller can render it. */ export declare function runCommunityResearch(input: CommunityResearchInput): Promise; /** Slim input shape used by the web-researcher subagent. The subagent * doesn't pass a transcript/brief path — it just returns inline results * to the parent LLM, which records the decision in the audit log. */ export interface WebResearcherInput { topic: string; projectKeywords: readonly string[]; source: ResearchSource; cwd: string; /** Optional override for the wall-clock cap (ms). Default 30_000. */ timeoutMs?: number; /** Optional override for the token cap. Default 1500. */ tokenCap?: number; /** Optional override for results-per-category. Default 5. */ maxPerCategory?: number; /** Inject a fetcher for unit tests. Default uses the stub fetcher. */ fetcher?: ResearchFetcher; } /** Slim return shape used by the web-researcher subagent. The subagent * reports the inline findings; the parent LLM uses them to ask better * brainstorm questions. */ export interface WebResearcherResult { source: ResearchSource; confidence: Confidence; cached: boolean; official: Array>; community: Array>; similar: Array>; /** True when the scout returned no useful findings. The parent LLM * should fall back to inline answers in that case. */ empty: boolean; } /** Web-researcher entry point. Used by the brainstorm `community-researcher` * subagent (and by tests). Internally calls `runCommunityResearch` with * the same cache + caps, but trims the output to a minimal shape that * the subagent can return inline without bloating the parent context. * * Caps still apply: 5 per category, 1500 tokens, 30s wall-clock. The * cache is keyed on (topic, keywords, source, UTC day) — see * `cacheKey()`. */ export declare function runWebResearcher(input: WebResearcherInput): Promise;