import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; import { classifyMessage, logCorrectionFailure } from "./correct.ts"; import { resolveCorrectionModel } from "./model.ts"; import { appendEnglishNote, registerEnglishNoteRenderer } from "./render.ts"; import { buildReport, formatReport } from "./report.ts"; import { loadSettings } from "./settings.ts"; import { countWords, shouldSkip } from "./skip-rules.ts"; import { appendChecked, appendError } from "./store.ts"; import { loadTaxonomy } from "./taxonomy.ts"; const MAX_MESSAGE_CHARS = 2000; /** Latches after the first missing-credentials report so failures don't repeat per message. */ let missingCredentialsReported = false; async function runCorrection(pi: ExtensionAPI, ctx: ExtensionContext, text: string): Promise { const settings = loadSettings(); if (!settings.enabled) return; const taxonomy = loadTaxonomy(); const skipReason = shouldSkip(text, { minWords: settings.skipShortMessages, maxChars: MAX_MESSAGE_CHARS }); if (skipReason) return; if (missingCredentialsReported) return; const resolved = await resolveCorrectionModel(ctx, settings); if (!resolved.ok) { missingCredentialsReported = true; logCorrectionFailure("missing_credentials", resolved.error); return; } const result = await classifyMessage(resolved.model, resolved.auth, taxonomy, text); if (!result.ok) { logCorrectionFailure(result.failureClass, result.error); return; } const ts = new Date().toISOString(); appendChecked(settings.logPath, { ts, project: ctx.cwd, wordCount: countWords(text), errorCount: result.errors.length, }); for (const error of result.errors) { appendError(settings.logPath, { ts, project: ctx.cwd, ...error }); } if (result.errors.length > 0) { appendEnglishNote(pi, result.errors); } } function parseReportArgs(args: string): { since?: string } { const tokens = args.trim().split(/\s+/).filter(Boolean); const filters: { since?: string } = {}; for (let i = 0; i < tokens.length; i++) { if (tokens[i] === "--since") { filters.since = tokens[i + 1]; i++; } } return filters; } export default function (pi: ExtensionAPI) { registerEnglishNoteRenderer(pi); pi.on("input", (event, ctx) => { queueMicrotask(() => { runCorrection(pi, ctx, event.text).catch((err) => { logCorrectionFailure("unexpected", err instanceof Error ? err.message : String(err)); }); }); return { action: "continue" }; }); pi.registerCommand("english", { description: "Show your English error report (--since YYYY-MM)", handler: async (args, ctx) => { const settings = loadSettings(); const outcome = buildReport(settings.logPath, parseReportArgs(args)); const text = formatReport(outcome); if (ctx.hasUI) ctx.ui.notify(text, "info"); if (!ctx.hasUI) console.log(text); }, }); }