Skip to main content
Glama
RequirementsManager.tsโ€ข11.2 kB
/** * RequirementsManager - Manage publisher requirements and style constraints * * Enables writers to: * - Store publisher requirements (word counts, citation styles, deadlines) * - Track style decisions (canonical terminology, formatting rules) * - Validate manuscript against requirements * - Maintain consistency across the manuscript */ import { nanoid } from "nanoid"; import type { SQLiteManager } from "../storage/SQLiteManager.js"; export interface WritingRequirement { id: string; requirementType: RequirementType; description: string; value?: string; // Target value (e.g., "50000" for word count, "Chicago" for citation style) enforced: boolean; createdAt: number; } export type RequirementType = | "word_count" | "citation_style" | "formatting" | "deadline" | "target_audience" | "tone" | "reading_level" | "chapter_count" | "other"; export interface StyleDecision { id: string; category: StyleCategory; canonicalChoice: string; alternativesRejected?: string[]; rationale?: string; examples?: string[]; createdAt: number; } export type StyleCategory = | "terminology" | "formatting" | "citations" | "tone" | "headings" | "lists" | "code_blocks" | "quotes" | "other"; export class RequirementsManager { private db: SQLiteManager; constructor(db: SQLiteManager) { this.db = db; } // ============================================================================ // Requirements Management // ============================================================================ /** * Add a new requirement */ addRequirement( requirement: Omit<WritingRequirement, "id" | "createdAt"> ): WritingRequirement { const now = Date.now(); const newRequirement: WritingRequirement = { id: nanoid(), ...requirement, createdAt: now, }; this.db .prepare( `INSERT INTO writing_requirements (id, requirement_type, description, value, enforced, created_at) VALUES (?, ?, ?, ?, ?, ?)` ) .run( newRequirement.id, newRequirement.requirementType, newRequirement.description, newRequirement.value || null, newRequirement.enforced ? 1 : 0, newRequirement.createdAt ); return newRequirement; } /** * Update a requirement */ updateRequirement( requirementId: string, update: Partial<Omit<WritingRequirement, "id" | "createdAt">> ): void { const requirement = this.getRequirement(requirementId); if (!requirement) { throw new Error(`Requirement ${requirementId} not found`); } const fields: string[] = []; const params: unknown[] = []; if (update.description !== undefined) { fields.push("description = ?"); params.push(update.description); } if (update.value !== undefined) { fields.push("value = ?"); params.push(update.value); } if (update.enforced !== undefined) { fields.push("enforced = ?"); params.push(update.enforced ? 1 : 0); } if (fields.length === 0) { return; } params.push(requirementId); const sql = `UPDATE writing_requirements SET ${fields.join(", ")} WHERE id = ?`; this.db.prepare(sql).run(...params); } /** * Get a requirement by ID */ getRequirement(requirementId: string): WritingRequirement | undefined { const row = this.db .prepare( `SELECT id, requirement_type, description, value, enforced, created_at FROM writing_requirements WHERE id = ?` ) .get(requirementId) as RequirementRow | undefined; return row ? this.rowToRequirement(row) : undefined; } /** * Get all requirements */ getAllRequirements(enforced?: boolean): WritingRequirement[] { let sql = `SELECT id, requirement_type, description, value, enforced, created_at FROM writing_requirements`; const params: unknown[] = []; if (enforced !== undefined) { sql += ` WHERE enforced = ?`; params.push(enforced ? 1 : 0); } sql += ` ORDER BY created_at DESC`; const rows = this.db.prepare(sql).all(...params) as RequirementRow[]; return rows.map((row) => this.rowToRequirement(row)); } /** * Get requirements by type */ getRequirementsByType(requirementType: RequirementType): WritingRequirement[] { const rows = this.db .prepare( `SELECT id, requirement_type, description, value, enforced, created_at FROM writing_requirements WHERE requirement_type = ? ORDER BY created_at DESC` ) .all(requirementType) as RequirementRow[]; return rows.map((row) => this.rowToRequirement(row)); } /** * Delete a requirement */ deleteRequirement(requirementId: string): void { this.db .prepare(`DELETE FROM writing_requirements WHERE id = ?`) .run(requirementId); } // ============================================================================ // Style Decisions Management // ============================================================================ /** * Add a style decision */ addStyleDecision( decision: Omit<StyleDecision, "id" | "createdAt"> ): StyleDecision { const now = Date.now(); const newDecision: StyleDecision = { id: nanoid(), ...decision, createdAt: now, }; this.db .prepare( `INSERT INTO style_decisions (id, category, canonical_choice, alternatives_rejected, rationale, examples, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)` ) .run( newDecision.id, newDecision.category, newDecision.canonicalChoice, newDecision.alternativesRejected ? JSON.stringify(newDecision.alternativesRejected) : null, newDecision.rationale || null, newDecision.examples ? JSON.stringify(newDecision.examples) : null, newDecision.createdAt ); return newDecision; } /** * Update a style decision */ updateStyleDecision( decisionId: string, update: Partial<Omit<StyleDecision, "id" | "createdAt">> ): void { const decision = this.getStyleDecision(decisionId); if (!decision) { throw new Error(`Style decision ${decisionId} not found`); } const fields: string[] = []; const params: unknown[] = []; if (update.canonicalChoice !== undefined) { fields.push("canonical_choice = ?"); params.push(update.canonicalChoice); } if (update.alternativesRejected !== undefined) { fields.push("alternatives_rejected = ?"); params.push(JSON.stringify(update.alternativesRejected)); } if (update.rationale !== undefined) { fields.push("rationale = ?"); params.push(update.rationale); } if (update.examples !== undefined) { fields.push("examples = ?"); params.push(JSON.stringify(update.examples)); } if (fields.length === 0) { return; } params.push(decisionId); const sql = `UPDATE style_decisions SET ${fields.join(", ")} WHERE id = ?`; this.db.prepare(sql).run(...params); } /** * Get a style decision by ID */ getStyleDecision(decisionId: string): StyleDecision | undefined { const row = this.db .prepare( `SELECT id, category, canonical_choice, alternatives_rejected, rationale, examples, created_at FROM style_decisions WHERE id = ?` ) .get(decisionId) as StyleDecisionRow | undefined; return row ? this.rowToStyleDecision(row) : undefined; } /** * Get all style decisions */ getAllStyleDecisions(): StyleDecision[] { const rows = this.db .prepare( `SELECT id, category, canonical_choice, alternatives_rejected, rationale, examples, created_at FROM style_decisions ORDER BY created_at DESC` ) .all() as StyleDecisionRow[]; return rows.map((row) => this.rowToStyleDecision(row)); } /** * Get style decisions by category */ getStyleDecisionsByCategory(category: StyleCategory): StyleDecision[] { const rows = this.db .prepare( `SELECT id, category, canonical_choice, alternatives_rejected, rationale, examples, created_at FROM style_decisions WHERE category = ? ORDER BY created_at DESC` ) .all(category) as StyleDecisionRow[]; return rows.map((row) => this.rowToStyleDecision(row)); } /** * Find style decision by canonical choice (for lookups) */ findStyleDecisionByChoice(canonicalChoice: string): StyleDecision | undefined { const row = this.db .prepare( `SELECT id, category, canonical_choice, alternatives_rejected, rationale, examples, created_at FROM style_decisions WHERE canonical_choice = ?` ) .get(canonicalChoice) as StyleDecisionRow | undefined; return row ? this.rowToStyleDecision(row) : undefined; } /** * Delete a style decision */ deleteStyleDecision(decisionId: string): void { this.db.prepare(`DELETE FROM style_decisions WHERE id = ?`).run(decisionId); } // ============================================================================ // Helper Methods // ============================================================================ /** * Get summary of all requirements and style decisions */ getSummary(): { requirements: WritingRequirement[]; styleDecisions: StyleDecision[]; totalRequirements: number; enforcedRequirements: number; } { const requirements = this.getAllRequirements(); const styleDecisions = this.getAllStyleDecisions(); return { requirements, styleDecisions, totalRequirements: requirements.length, enforcedRequirements: requirements.filter((r) => r.enforced).length, }; } /** * Convert database row to WritingRequirement */ private rowToRequirement(row: RequirementRow): WritingRequirement { return { id: row.id, requirementType: row.requirement_type as RequirementType, description: row.description, value: row.value || undefined, enforced: Boolean(row.enforced), createdAt: row.created_at, }; } /** * Convert database row to StyleDecision */ private rowToStyleDecision(row: StyleDecisionRow): StyleDecision { return { id: row.id, category: row.category as StyleCategory, canonicalChoice: row.canonical_choice, alternativesRejected: row.alternatives_rejected ? JSON.parse(row.alternatives_rejected) : undefined, rationale: row.rationale || undefined, examples: row.examples ? JSON.parse(row.examples) : undefined, createdAt: row.created_at, }; } } // Database row types interface RequirementRow { id: string; requirement_type: string; description: string; value: string | null; enforced: number; created_at: number; } interface StyleDecisionRow { id: string; category: string; canonical_choice: string; alternatives_rejected: string | null; rationale: string | null; examples: string | null; created_at: number; }

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/xiaolai/claude-writers-aid-mcp'

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