UNPKG

@stackmemoryai/stackmemory

Version:

Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a

130 lines (129 loc) 3.84 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import { exec } from "child_process"; import { promisify } from "util"; import { BaseVerifier } from "./base-verifier.js"; import { logger } from "../../core/monitoring/logger.js"; const execAsync = promisify(exec); class FormatterVerifier extends BaseVerifier { formatters = /* @__PURE__ */ new Map(); constructor(config) { super({ id: "formatter", name: "Code Formatter", type: "style", enabled: true, stopOnError: false, timeout: 1e4, ...config }); this.initializeFormatters(); } initializeFormatters() { this.formatters.set("typescript", { checkCommand: "npx prettier --check", fixCommand: "npx prettier --write", patterns: [/\[error\]/gi, /File not formatted/gi] }); this.formatters.set("javascript", { checkCommand: "npx prettier --check", fixCommand: "npx prettier --write", patterns: [/\[error\]/gi, /File not formatted/gi] }); this.formatters.set("python", { checkCommand: "black --check", fixCommand: "black", patterns: [/would reformat/gi, /File not formatted/gi] }); this.formatters.set("rust", { checkCommand: "cargo fmt -- --check", fixCommand: "cargo fmt", patterns: [/Diff in/gi, /File not formatted/gi] }); this.formatters.set("go", { checkCommand: "gofmt -l", fixCommand: "gofmt -w", patterns: [/.+\.go$/gm] // gofmt lists unformatted files }); } shouldActivate(context) { if (!context.language || !context.filePath) { return false; } return this.formatters.has(context.language.toLowerCase()); } async verify(input, context) { if (!context.language || !context.filePath) { return this.createResult( false, "Missing language or file path in context", "error" ); } const formatter = this.formatters.get(context.language.toLowerCase()); if (!formatter) { return this.createResult( true, `No formatter configured for ${context.language}`, "info" ); } try { return await this.withTimeout(async () => { return await this.withRetry( () => this.runFormatter(formatter, context), context.filePath ); }); } catch (error) { logger.error( "Formatter verification failed", error instanceof Error ? error : void 0 ); return this.createResult( false, `Formatter error: ${error instanceof Error ? error.message : String(error)}`, "error" ); } } async runFormatter(formatter, context) { const command = `${formatter.checkCommand} "${context.filePath}"`; try { const { _stdout, _stderr } = await execAsync(command, { cwd: process.cwd(), timeout: this.config.timeout }); return this.createResult(true, "Code is properly formatted", "info"); } catch (error) { if (error.code === 1) { const output = error.stdout + error.stderr; const errors = this.extractRelevantErrors(output, formatter.patterns); return this.createResult( false, this.generateFeedback(errors, context), "warning", { code: output.substring(0, 500), suggestion: "Run formatter to fix issues" }, { command: `${formatter.fixCommand} "${context.filePath}"`, description: "Auto-format the file", safe: true, confidence: 0.95 } ); } throw error; } } } export { FormatterVerifier };