Skip to main content
Glama
lint.ts4.1 kB
import { z } from 'zod'; import * as path from 'path'; import { resolveWorkspacePath } from './workspace.js'; import { lintSpec, generateLintReport, LintConfig } from '../speckit/linter.js'; /** * Spec Linting Tool * * Provides automated quality checking for spec.md files with markdownlint, * spec-specific rules, and prose quality analysis. */ // Schema for spec_lint tool export const SpecLintSchema = z.object({ specPath: z.string().optional().describe('Path to spec file (defaults to .dincoder/spec.md or auto-detect)'), workspacePath: z.string().optional().describe('Workspace directory path'), autoFix: z.boolean().default(false).describe('Automatically fix issues when possible'), config: z.object({ markdownlint: z.object({ enabled: z.boolean().default(true), config: z.record(z.any()).optional(), }).optional(), specRules: z.object({ requiredSections: z.boolean().default(true), acceptanceCriteria: z.boolean().default(true), codeBlocks: z.boolean().default(true), }).optional(), proseRules: z.object({ passiveVoice: z.boolean().default(true), vagueLanguage: z.boolean().default(true), ambiguousPronouns: z.boolean().default(true), sentenceComplexity: z.boolean().default(true), }).optional(), customRules: z.object({ clarificationFormat: z.boolean().default(true), zodValidation: z.boolean().default(true), edgeCaseFormat: z.boolean().default(true), }).optional(), severityOverrides: z.record(z.enum(['ERROR', 'WARNING', 'INFO'])).optional(), }).optional().describe('Custom lint configuration (overrides defaults)'), format: z.enum(['json', 'markdown']).default('markdown').describe('Output format'), }); export interface SpecLintResult { success: boolean; filePath: string; summary: { errors: number; warnings: number; info: number; fixable: number; }; issues?: any[]; report?: string; fixed?: boolean; message: string; } /** * Run spec linting */ export async function specLint(params: z.infer<typeof SpecLintSchema>): Promise<SpecLintResult> { const { specPath, workspacePath, autoFix, config, format } = params; const resolvedPath = resolveWorkspacePath(workspacePath); try { // Resolve spec file path let fullSpecPath: string; if (specPath) { fullSpecPath = path.isAbsolute(specPath) ? specPath : path.resolve(resolvedPath, specPath); } else { // Auto-detect spec file fullSpecPath = await autoDetectSpecFile(resolvedPath); } // Prepare lint config const lintConfig: LintConfig = { ...config, autoFix, }; // Run linting const result = await lintSpec(fullSpecPath, lintConfig); // Generate report const report = format === 'markdown' ? generateLintReport(result) : undefined; // Determine success const success = result.summary.errors === 0; return { success, filePath: result.filePath, summary: result.summary, issues: format === 'json' ? result.issues : undefined, report, fixed: result.fixed, message: success ? `✅ Spec passed linting (${result.summary.warnings} warnings, ${result.summary.info} info)` : `❌ Spec has ${result.summary.errors} error(s), ${result.summary.warnings} warning(s)`, }; } catch (error) { throw new Error(`Failed to lint spec: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Auto-detect spec file location */ async function autoDetectSpecFile(workspacePath: string): Promise<string> { const fs = await import('fs/promises'); // Try common locations const candidates = [ path.join(workspacePath, '.dincoder', 'spec.md'), path.join(workspacePath, 'spec.md'), path.join(workspacePath, 'specs', 'spec.md'), ]; for (const candidate of candidates) { try { await fs.access(candidate); return candidate; } catch { // File doesn't exist, try next } } throw new Error('Spec file not found. Tried: .dincoder/spec.md, spec.md, specs/spec.md'); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/flight505/MCP_DinCoder'

If you have feedback or need assistance with the MCP directory API, please join our Discord server