import type { LlmClient } from "@slowcook-ai/core"; export interface AnsweredQ { question: string; answer: string; source: string; } export interface UnansweredQ { question: string; why_unanswered: string; } export interface BrownfieldAnswerResult { answered: AnsweredQ[]; unanswered: UnansweredQ[]; costUsd: number; usage: { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreateTokens: number; }; } /** * Detect whether the consumer's repo has ANY brownfield signal that * Pass B could draw from. Greenfield = none of these present: * * - `.brewing/context.md` with non-empty content * - `src/lib/entities/*.ts` (at least one entity file) * - `specs/_index.yaml` (at least one active story) * - `.brewing/diagrams/entities.md` or `.brewing/diagrams/schema.mmd` * * Cheap check (filesystem stat + small reads). No LLM. Used to gate * Pass B so greenfield projects don't pay for an empty-answer call. */ export declare function hasBrownfield(repoRoot: string): boolean; export interface BrownfieldAnswerInputs { draftQuestionsMarkdown: string; projectContext: string; mockExcerpt?: string; } /** * Run Pass B. Returns structured answered/unanswered split + cost. * Throws on JSON parse failure — caller decides whether to fall back * (today: caller catches + posts the original Pass A markdown unchanged). */ export declare function answerQuestionsFromBrownfield(inputs: BrownfieldAnswerInputs, opts: { llm: LlmClient; model: string; }): Promise; /** * Parse Pass B's JSON output. Tolerant of stray code fences / leading * prose (the model occasionally adds "Here is the JSON:" despite the * prompt). Falls back to JSON.parse of the largest brace-balanced * substring. */ export declare function parseAnswerJson(text: string): { answered: AnsweredQ[]; unanswered: UnansweredQ[]; }; /** * Render the `
` audit-trail block listing brownfield-answered * questions. Returns empty string when there are no answered Qs (no * point spamming the comment with an empty disclosure). */ export declare function formatAnsweredDetails(answered: AnsweredQ[]): string; /** * Render the visible PM-facing question list from `unanswered`. Each * question text is preserved verbatim (Pass A's labeled-options format * survives). Numbered list, blank-line separated. Empty when there are * no unanswered Qs (caller decides what to do — probably emit a spec * directly next round, or post a "nothing more to ask" note). */ export declare function formatUnansweredQuestions(unanswered: UnansweredQ[]): string; /** * Compose the final question-comment markdown body, replacing Pass A's * raw questions output with the Pass B filtered view + audit details. * Leading "I checked first..." note appears only when at least one * answer was found, so greenfield-like cases (Pass B ran but found * nothing) don't show a misleading "I checked first" banner. */ export declare function composePassBComment(result: BrownfieldAnswerResult, fallbackMarkdown: string): string; //# sourceMappingURL=brownfield-answer.d.ts.map