UNPKG

@context-action/llms-generator

Version:

Enterprise-grade LLM content generation framework with mismatch detection and integrity management

1,184 lines (1,179 loc) β€’ 152 kB
#!/usr/bin/env node import { DEFAULT_CONFIG, EnhancedConfigManager } from "../src-6pZS-004.js"; import { existsSync, promises } from "fs"; import path from "path"; import matter from "gray-matter"; import { Command } from "commander"; import { exec } from "child_process"; import { promisify } from "util"; import { glob } from "glob"; //#region src/cli/commands/WorkNextCommand.ts var WorkNextCommand = class { constructor(config) { this.config = config; } async execute(options = {}) { console.log("πŸ” Analyzing document work status...\n"); const workItems = await this.scanWorkItems(options); const filteredItems = this.filterAndSort(workItems, options); if (filteredItems.length === 0) { console.log("πŸŽ‰ No pending work items found!"); console.log(" All documents are completed or no documents match your criteria.\n"); return; } if (options.limit && options.limit > 1) { this.displayPriorityList(filteredItems.slice(0, options.limit), options); this.displaySummaryStats(workItems); return; } const nextItem = filteredItems[0]; this.displayNextWorkItem(nextItem); this.displaySummaryStats(workItems); } async scanWorkItems(options) { const workItems = []; const languages = options.language ? [options.language] : this.config.generation.supportedLanguages; const characterLimits = options.characterLimit ? [options.characterLimit] : this.config.generation.characterLimits; for (const language of languages) { const languageDir = path.join(this.config.paths.llmContentDir, language); try { await promises.access(languageDir); } catch { continue; } const documentDirs = await promises.readdir(languageDir, { withFileTypes: true }); for (const docDir of documentDirs) { if (!docDir.isDirectory()) continue; const documentId = docDir.name; if (documentId === "guide" || documentId === "examples" || documentId === "concept") continue; const category = this.extractCategory(documentId); if (options.category && category !== options.category) continue; for (const charLimit of characterLimits) { const workItem = await this.analyzeWorkItem(documentId, category, language, charLimit); if (workItem) workItems.push(workItem); } } } return workItems; } async analyzeWorkItem(documentId, category, language, characterLimit) { const basePath = path.join(this.config.paths.llmContentDir, language, documentId); const sourceDocPath = this.resolveSourceDocumentPath(documentId, language); const priorityJsonPath = path.join(basePath, "priority.json"); const templatePath = path.join(basePath, `${documentId}-${characterLimit}.md`); try { const [sourceExists, priorityExists, templateExists] = await Promise.all([ this.fileExists(sourceDocPath), this.fileExists(priorityJsonPath), this.fileExists(templatePath) ]); const metadata = { sourceExists, priorityExists, templateExists, hasContent: false }; if (sourceExists) { const sourceStat = await promises.stat(sourceDocPath); metadata.sourceSize = sourceStat.size; metadata.lastModified = sourceStat.mtime.toISOString(); } if (templateExists) metadata.hasContent = await this.checkTemplateHasContent(templatePath); let status; if (!sourceExists) return null; else if (!priorityExists) status = "missing_priority"; else if (!templateExists) status = "missing_template"; else if (!metadata.hasContent) status = "needs_content"; else status = "completed"; let priority = this.config.categories?.[category]?.priority || 50; if (priorityExists) try { const priorityData = JSON.parse(await promises.readFile(priorityJsonPath, "utf-8")); priority = priorityData.priority?.score || priority; } catch {} return { documentId, category, language, characterLimit, status, priority, paths: { sourceDocument: sourceDocPath, priorityJson: priorityJsonPath, templateFile: templatePath }, metadata }; } catch (error) { console.warn(`⚠️ Error analyzing ${documentId} (${language}/${characterLimit}):`, error); return null; } } resolveSourceDocumentPath(documentId, language) { const parts = documentId.split("--"); if (parts.length >= 2) { const [category, ...nameParts] = parts; const fileName = nameParts.join("-") + ".md"; return path.join(this.config.paths.docsDir, language, category, fileName); } return path.join(this.config.paths.docsDir, language, documentId + ".md"); } extractCategory(documentId) { const parts = documentId.split("--"); return parts[0] || "unknown"; } async fileExists(filePath) { try { await promises.access(filePath); return true; } catch { return false; } } async checkTemplateHasContent(templatePath) { try { const content = await promises.readFile(templatePath, "utf-8"); if (content.includes("completion_status: completed")) return true; const lines = content.split("\n"); let inFrontmatter = false; let frontmatterCount = 0; let contentAfterFrontmatter = ""; for (const line of lines) { if (line.trim() === "---") { frontmatterCount++; if (frontmatterCount === 2) { inFrontmatter = false; continue; } inFrontmatter = true; continue; } if (!inFrontmatter && frontmatterCount >= 2) contentAfterFrontmatter += line + "\n"; } const cleanContent = contentAfterFrontmatter.replace(/<!--[\s\S]*?-->/g, "").replace(/^\s*\n/gm, "").trim(); const hasRealContent = cleanContent.length > 30 && !cleanContent.includes("여기에") && !cleanContent.includes("μž‘μ„±ν•˜μ„Έμš”") && !cleanContent.includes("ν…œν”Œλ¦Ώ λ‚΄μš©") && !cleanContent.includes("Provide comprehensive guidance on") && !cleanContent.includes("의 핡심 κ°œλ…κ³Ό Context-Action ν”„λ ˆμž„μ›Œν¬μ—μ„œμ˜ 역할을 κ°„λ‹¨νžˆ μ„€λͺ…"); return hasRealContent; } catch { return false; } } extractContentSection(templateContent) { const contentMatch = templateContent.match(/## ν…œν”Œλ¦Ώ λ‚΄μš©[^]*?```markdown\s*([\s\S]*?)\s*```/); if (contentMatch && contentMatch[1]) return contentMatch[1].trim(); const fallbackMatch = templateContent.match(/```markdown\s*([\s\S]*?)\s*```/); if (fallbackMatch && fallbackMatch[1]) return fallbackMatch[1].trim(); return ""; } filterAndSort(workItems, options) { let filtered = workItems; if (!options.showCompleted) filtered = filtered.filter((item) => item.status !== "completed"); const sortBy = options.sortBy || "priority"; filtered.sort((a, b) => { switch (sortBy) { case "priority": if (a.priority !== b.priority) return b.priority - a.priority; return this.getStatusPriority(a.status) - this.getStatusPriority(b.status); case "category": return a.category.localeCompare(b.category); case "status": return this.getStatusPriority(a.status) - this.getStatusPriority(b.status); case "modified": { const aTime = a.metadata.lastModified || "0"; const bTime = b.metadata.lastModified || "0"; return aTime.localeCompare(bTime); } default: return 0; } }); return filtered; } getStatusPriority(status) { const priorities = { "missing_priority": 1, "missing_template": 2, "needs_content": 3, "completed": 4 }; return priorities[status] || 99; } displayNextWorkItem(item) { console.log("🎯 Next Work Item\n"); console.log(`πŸ“„ Document: ${item.documentId}`); console.log(`πŸ“ Category: ${item.category}`); console.log(`🌐 Language: ${item.language}`); console.log(`πŸ“ Character Limit: ${item.characterLimit}`); console.log(`⭐ Priority: ${item.priority}`); console.log(`πŸ“Š Status: ${this.getStatusEmoji(item.status)} ${this.getStatusDescription(item.status)}`); if (item.metadata.sourceSize) console.log(`πŸ“– Source Size: ${this.formatFileSize(item.metadata.sourceSize)}`); console.log("\nπŸ“‚ File Paths:"); console.log(` πŸ“„ Source: ${item.paths.sourceDocument}`); console.log(` 🏷️ Priority: ${item.paths.priorityJson}`); console.log(` πŸ“ Template: ${item.paths.templateFile}`); console.log("\nπŸ”§ Recommended Actions:"); this.displayRecommendedActions(item); console.log(); } displayRecommendedActions(item) { switch (item.status) { case "missing_priority": console.log(" 1. Generate priority.json:"); console.log(` npx @context-action/llms-generator priority-generate ${item.language} --document-id ${item.documentId}`); console.log(" 2. Then re-run work-next to see next steps"); break; case "missing_template": console.log(" 1. Generate template file:"); console.log(` npx @context-action/llms-generator template-generate --document-id ${item.documentId} --character-limit ${item.characterLimit}`); console.log(" 2. Then edit the template content"); break; case "needs_content": console.log(" 1. Read the source document:"); console.log(` # Source document (${this.formatFileSize(item.metadata.sourceSize || 0)})`); console.log(` cat "${item.paths.sourceDocument}"`); console.log(" 2. Edit the template file:"); console.log(` # Template file`); console.log(` code "${item.paths.templateFile}"`); console.log(" 3. Write a concise summary in the \"ν…œν”Œλ¦Ώ λ‚΄μš©\" section"); console.log(` 4. Keep it under ${item.characterLimit} characters`); break; case "completed": console.log(" βœ… This item is completed!"); console.log(" You can review or update the content if needed."); break; } } displayPriorityList(items, options) { const documentGroups = /* @__PURE__ */ new Map(); items.forEach((item) => { const key = `${item.documentId}-${item.language}`; if (!documentGroups.has(key)) documentGroups.set(key, []); documentGroups.get(key).push(item); }); const uniqueItems = []; documentGroups.forEach((group) => { group.sort((a, b) => a.characterLimit - b.characterLimit); uniqueItems.push(group[0]); }); uniqueItems.sort((a, b) => { if (a.priority !== b.priority) return b.priority - a.priority; return this.getStatusPriority(a.status) - this.getStatusPriority(b.status); }); const displayItems = uniqueItems.slice(0, options.limit || 10); const title = options.showCompleted ? `πŸ“‹ Top ${displayItems.length} Priority Documents (All Statuses)` : `πŸ“‹ Top ${displayItems.length} Priority Documents (Pending Work)`; console.log(`${title}\n`); console.log("Rank | Priority | Status | Lang | Category | Document ID | Char Limits Needed"); console.log("-----|----------|--------|------|----------|------------------------------|-------------------"); displayItems.forEach((item, index) => { const rank = String(index + 1).padStart(4); const priority = String(item.priority).padStart(8); const statusIcon = this.getStatusEmoji(item.status); const status = `${statusIcon}`; const lang = item.language.padEnd(4); const category = item.category.padEnd(8); const docId = item.documentId.substring(0, 28).padEnd(28); const key = `${item.documentId}-${item.language}`; const group = documentGroups.get(key) || []; const needsWork = group.filter((g) => g.status !== "completed"); const charLimits = needsWork.map((g) => g.characterLimit).join(", "); console.log(`${rank} | ${priority} | ${status} | ${lang} | ${category} | ${docId} | ${charLimits}`); }); console.log("\nπŸ”§ Recommended Actions:"); console.log(" β€’ Use `work-next` without --limit to see detailed information for the top item"); console.log(" β€’ Add `--character-limit <num>` to focus on specific template size"); console.log(" β€’ Add `--verbose` for more detailed information"); console.log(" β€’ Add `--show-completed` to include completed items"); console.log(" β€’ Use `--sort-by <priority|category|status|modified>` to change ordering"); console.log(); } displayWorkQueue(items, _options) { if (items.length === 0) return; console.log(`πŸ“‹ Work Queue (Next ${items.length} items)\n`); items.forEach((item, index) => { const statusIcon = this.getStatusEmoji(item.status); console.log(`${index + 2}. ${statusIcon} ${item.documentId} (${item.language}/${item.characterLimit})`); console.log(` Priority: ${item.priority} | Category: ${item.category}`); }); console.log(); } displaySummaryStats(workItems) { const stats = { total: workItems.length, byStatus: {}, byLanguage: {}, byCategory: {} }; workItems.forEach((item) => { stats.byStatus[item.status] = (stats.byStatus[item.status] || 0) + 1; stats.byLanguage[item.language] = (stats.byLanguage[item.language] || 0) + 1; stats.byCategory[item.category] = (stats.byCategory[item.category] || 0) + 1; }); console.log("πŸ“Š Summary Statistics\n"); console.log(`Total Items: ${stats.total}`); console.log("\nBy Status:"); Object.entries(stats.byStatus).forEach(([status, count]) => { const emoji = this.getStatusEmoji(status); console.log(` ${emoji} ${this.getStatusDescription(status)}: ${count}`); }); console.log("\nBy Language:"); Object.entries(stats.byLanguage).forEach(([lang, count]) => { console.log(` 🌐 ${lang}: ${count}`); }); console.log("\nBy Category:"); Object.entries(stats.byCategory).forEach(([category, count]) => { console.log(` πŸ“ ${category}: ${count}`); }); console.log(); } getStatusEmoji(status) { const emojis = { "missing_priority": "πŸ”΄", "missing_template": "🟑", "needs_content": "🟠", "completed": "βœ…" }; return emojis[status] || "❓"; } getStatusDescription(status) { const descriptions = { "missing_priority": "Missing Priority JSON", "missing_template": "Missing Template", "needs_content": "Needs Content", "completed": "Completed" }; return descriptions[status] || "Unknown"; } formatFileSize(bytes) { if (bytes < 1024) return `${bytes} bytes`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } }; //#endregion //#region src/core/LLMSOutputPathManager.ts var LLMSOutputPathManager = class { constructor(config) { this.config = config; } /** * Generate output path with language-specific directory structure * Format: {outputDir}/{language}/llms/{filename} */ generateOutputPath(options) { const { language, characterLimit, category, pattern, outputDir } = options; const filename = this.generateFilename(language, characterLimit, category, pattern); const baseOutputDir = outputDir || this.config.paths.outputDir; const languageOutputDir = path.join(baseOutputDir, language, "llms"); const outputPath = path.join(languageOutputDir, filename); return { outputPath, filename }; } /** * Generate filename based on parameters */ generateFilename(language, characterLimit, category, pattern) { let filename = "llms"; if (characterLimit) filename += `-${characterLimit}chars`; if (category) filename += `-${category}`; if (pattern && pattern !== "standard" && pattern !== "clean") filename += `-${pattern}`; filename += ".txt"; return filename; } /** * Get relative source path for document traceability */ getRelativeSourcePath(documentId, language, characterLimit, filePath) { const parts = documentId.split("--"); if (parts.length >= 2) { const category = parts[0]; const pathParts = parts.slice(1); if (category === "api") return `${language}/api/${pathParts.join("-")}.md`; else if (category === "guide") return `${language}/guide/${pathParts.join("-")}.md`; else if (category === "concept") return `${language}/concept/${pathParts.join("-")}.md`; else if (category === "examples") return `${language}/example/${pathParts.join("-")}.md`; } if (filePath) { const relativePath = filePath.replace(this.config.paths.llmContentDir, "").replace(/^\//, ""); const pathParts = relativePath.split("/"); if (pathParts.length >= 2) { const lang = pathParts[0]; const docDirName = pathParts[1]; const docParts$1 = docDirName.split("--"); if (docParts$1.length >= 2) { const category = docParts$1[0]; const pathName = docParts$1.slice(1).join("-"); return `${lang}/${category}/${pathName}.md`; } } } const docParts = documentId.split("--"); if (docParts.length >= 2) { const category = docParts[0]; const pathParts = docParts.slice(1).join("-"); return `${language}/${category}/${pathParts}.md`; } return `${language}/${documentId}.md`; } }; //#endregion //#region src/cli/commands/LLMSGenerateCommand.ts var LLMSGenerateCommand = class { pathManager; constructor(config) { this.config = config; this.pathManager = new LLMSOutputPathManager(config); } async execute(options = {}) { console.log("πŸš€ Generating LLMS-TXT file...\n"); const validatedOptions = this.validateOptions(options); if (options.dryRun) console.log("πŸ” DRY RUN - No files will be created\n"); const documents = await this.collectDocuments(validatedOptions); if (documents.length === 0) { console.log("❌ No documents found matching the specified criteria"); this.displayFilterCriteria(validatedOptions); return; } const filteredDocuments = this.filterDocuments(documents, validatedOptions); const sortedDocuments = this.sortDocuments(filteredDocuments, validatedOptions.sortBy); if (options.verbose || options.dryRun) this.displayDocumentList(sortedDocuments); if (options.dryRun) { this.displayDryRunSummary(sortedDocuments, validatedOptions); return; } const content = this.generateContent(sortedDocuments, validatedOptions); const outputPath = await this.writeOutput(content, validatedOptions); const result = { outputPath, pattern: validatedOptions.pattern, totalDocuments: sortedDocuments.length, totalCharacters: content.length, averageCharacters: Math.round(sortedDocuments.reduce((sum, doc) => sum + doc.metadata.content_length, 0) / sortedDocuments.length), filters: { characterLimit: validatedOptions.characterLimit, category: validatedOptions.category, language: validatedOptions.language }, documents: sortedDocuments }; this.displayResults(result); } validateOptions(options) { const language = options.language || this.config.generation?.defaultLanguage || "en"; const pattern = options.pattern || "standard"; const sortBy = options.sortBy || "priority"; const includeMetadata = options.includeMetadata !== false; const dryRun = options.dryRun || false; const verbose = options.verbose || false; if (!this.config.generation?.supportedLanguages?.includes(language)) throw new Error(`Unsupported language: ${language}. Supported: ${this.config.generation?.supportedLanguages?.join(", ") || "none configured"}`); if (options.characterLimit && !this.config.generation?.characterLimits?.includes(options.characterLimit)) console.warn(`⚠️ Warning: Character limit ${options.characterLimit} is not in standard limits: ${this.config.generation?.characterLimits?.join(", ") || "none configured"}`); if (options.category && !Object.keys(this.config.categories || {}).includes(options.category)) console.warn(`⚠️ Warning: Category ${options.category} is not in configured categories: ${Object.keys(this.config.categories || {}).join(", ")}`); return { characterLimit: options.characterLimit, category: options.category, language, pattern, outputDir: options.outputDir, sortBy, includeMetadata, dryRun, verbose }; } async collectDocuments(options) { const documents = []; const language = options.language; const languageDataDir = path.join(this.config.paths.llmContentDir, language); try { await promises.access(languageDataDir); } catch { throw new Error(`Language directory not found: ${languageDataDir}`); } const documentDirs = await promises.readdir(languageDataDir, { withFileTypes: true }); for (const docDir of documentDirs) { if (!docDir.isDirectory()) continue; const documentId = docDir.name; const category = this.extractCategory(documentId); const documentPath = path.join(languageDataDir, documentId); try { const files = await promises.readdir(documentPath); const templateFiles = files.filter((f) => f.endsWith(".md") && f.includes(documentId)); for (const templateFile of templateFiles) { const characterLimit = this.extractCharacterLimit(templateFile); if (characterLimit === null) continue; const filePath = path.join(documentPath, templateFile); const document = await this.parseDocument(filePath, documentId, category, language, characterLimit); if (document && this.isDocumentComplete(document)) documents.push(document); } } catch (error) { console.warn(`⚠️ Warning: Error processing ${documentId}: ${error}`); } } return documents; } async parseDocument(filePath, documentId, category, language, characterLimit) { try { const documentDir = path.dirname(filePath); const priorityPath = path.join(documentDir, "priority.json"); let priorityData = null; try { const priorityContent = await promises.readFile(priorityPath, "utf-8"); priorityData = JSON.parse(priorityContent); } catch { console.warn(`⚠️ Warning: No priority.json found for ${documentId}`); } if (priorityData && this.isPriorityDataValid(priorityData)) { const sourceContent = await this.extractFromSourceDocument(priorityData.document.source_path, characterLimit, priorityData); if (sourceContent) { const cleanedPriorityData = this.cleanPriorityData(priorityData); return { documentId, category, language, characterLimit, title: cleanedPriorityData.document?.title || documentId, content: sourceContent, priority: cleanedPriorityData.priority?.score || 50, filePath, metadata: { completion_status: "generated_from_source", workflow_stage: "auto_generated", quality_score: cleanedPriorityData.quality?.completeness_threshold || .8, content_length: sourceContent.length } }; } } const content = await promises.readFile(filePath, "utf-8"); const parsed = matter(content); const frontmatter = parsed.data; const templateContent = this.extractTemplateContent(parsed.content); if (!templateContent) return null; const priority = frontmatter.priority_score || frontmatter.priority || priorityData?.priority?.score || this.config.categories?.[category]?.priority || 50; const title = frontmatter.title || frontmatter.document_id || priorityData?.document?.title || this.extractTitle(parsed.content) || documentId; return { documentId, category, language, characterLimit, title, content: templateContent, priority, filePath, metadata: { completion_status: frontmatter.completion_status || frontmatter.update_status || "template_based", workflow_stage: frontmatter.workflow_stage || "template_content", quality_score: frontmatter.quality_score || priorityData?.quality?.completeness_threshold, content_length: templateContent.length } }; } catch (error) { console.warn(`⚠️ Warning: Error parsing ${filePath}: ${error}`); return null; } } async extractFromSourceDocument(sourcePath, characterLimit, priorityData) { try { const fullSourcePath = path.join(this.config.paths.docsDir, sourcePath); if (!await this.fileExists(fullSourcePath)) { console.warn(`⚠️ Warning: Source document not found: ${fullSourcePath}`); return null; } const sourceContent = await promises.readFile(fullSourcePath, "utf-8"); const parsed = matter(sourceContent); const strategy = priorityData.extraction?.character_limits?.[characterLimit]; if (strategy) return this.generateContentFromStrategy(parsed.content, strategy, characterLimit); return this.extractContentByCharacterLimit(parsed.content, characterLimit); } catch (error) { console.warn(`⚠️ Warning: Error extracting from source document: ${error}`); return null; } } async fileExists(filePath) { try { await promises.access(filePath); return true; } catch { return false; } } generateContentFromStrategy(content, strategy, characterLimit) { const cleanContent = content.replace(/^---[\s\S]*?---\n/, "").replace(/<!--[\s\S]*?-->/g, "").replace(/^#+\s/gm, "").replace(/\n{3,}/g, "\n\n").trim(); const sentences = cleanContent.split(/[.!?]+/).filter((s) => s.trim().length > 0); let result = ""; let currentLength = 0; for (const sentence of sentences) { const trimmedSentence = sentence.trim(); if (currentLength + trimmedSentence.length + 1 > characterLimit * .9) break; if (result) result += ". "; result += trimmedSentence; currentLength = result.length; } if (result && !result.endsWith(".")) result += "."; return result || cleanContent.substring(0, characterLimit - 10) + "..."; } extractContentByCharacterLimit(content, characterLimit) { const cleanContent = content.replace(/^---[\s\S]*?---\n/, "").replace(/<!--[\s\S]*?-->/g, "").replace(/^#+\s/gm, "").replace(/\n{3,}/g, "\n\n").trim(); if (cleanContent.length <= characterLimit) return cleanContent; const truncated = cleanContent.substring(0, characterLimit - 3); const lastSentenceEnd = Math.max(truncated.lastIndexOf("."), truncated.lastIndexOf("!"), truncated.lastIndexOf("?")); return lastSentenceEnd > characterLimit * .5 ? truncated.substring(0, lastSentenceEnd + 1) : truncated + "..."; } isPriorityDataValid(priorityData) { if (!priorityData.document?.source_path) return false; const templateIndicators = [ "Auto-generated priority assignment", "Provide comprehensive guidance on", "Understanding api ", "Implementing api ", "Framework learning", "framework-users", "beginners", "<!-- Set priority score", "<!-- Set tier:", "<!-- Explain why", "<!-- What is the main purpose", "<!-- Who should read this", "<!-- What to focus on" ]; const rationale = priorityData.priority?.rationale || ""; const primaryGoal = priorityData.purpose?.primary_goal || ""; const score = String(priorityData.priority?.score || ""); const hasTemplateContent = templateIndicators.some((indicator) => rationale.includes(indicator) || primaryGoal.includes(indicator) || score.includes(indicator)); const isPurposeEmpty = Object.keys(priorityData.purpose || {}).length === 0; if (hasTemplateContent || isPurposeEmpty) { console.log(`πŸ“‹ Skipping template priority.json for ${priorityData.document?.id || "unknown"}`); return false; } return true; } cleanPriorityData(priorityData) { const cleaned = JSON.parse(JSON.stringify(priorityData)); if (cleaned.priority?.rationale?.includes("Auto-generated priority assignment")) cleaned.priority.rationale = null; if (cleaned.purpose?.primary_goal?.includes("Provide comprehensive guidance on")) cleaned.purpose.primary_goal = null; if (cleaned.purpose?.use_cases) { cleaned.purpose.use_cases = cleaned.purpose.use_cases.filter((useCase) => !useCase.includes("Understanding ") && !useCase.includes("Implementing ") && !useCase.includes("Framework learning")); if (cleaned.purpose.use_cases.length === 0) cleaned.purpose.use_cases = null; } if (cleaned.purpose?.target_audience) { cleaned.purpose.target_audience = cleaned.purpose.target_audience.filter((audience) => !["framework-users", "beginners"].includes(audience)); if (cleaned.purpose.target_audience.length === 0) cleaned.purpose.target_audience = null; } if (cleaned.keywords?.technical) { const genericKeywords = [ "API", "methods", "interfaces", "framework" ]; cleaned.keywords.technical = cleaned.keywords.technical.filter((keyword) => !genericKeywords.includes(keyword)); if (cleaned.keywords.technical.length === 0) cleaned.keywords.technical = null; } return cleaned; } extractTemplateContent(content) { const directContent = content.trim().replace(/<!--[\s\S]*?-->/g, "").replace(/^#+\s.*$/gm, "").replace(/^\s*---\s*$/gm, "").replace(/\n{3,}/g, "\n\n").trim(); if (directContent && directContent.length > 10 && !directContent.includes("## ν…œν”Œλ¦Ώ λ‚΄μš©") && !directContent.includes("Provide comprehensive guidance on")) return directContent; const codeBlockMatch = content.match(/## ν…œν”Œλ¦Ώ λ‚΄μš©[^]*?```markdown\s*([\s\S]*?)\s*```/); if (codeBlockMatch && codeBlockMatch[1]) return codeBlockMatch[1].trim().replace(/<!--[\s\S]*?-->/g, "").trim(); const sectionMatch = content.match(/## ν…œν”Œλ¦Ώ λ‚΄μš©[^]*?\n\n([\s\S]*?)(?=\n\n|$)/); if (sectionMatch && sectionMatch[1]) return sectionMatch[1].trim().replace(/<!--[\s\S]*?-->/g, "").trim(); return ""; } extractTitle(content) { const titleMatch = content.match(/^# (.+)/m); return titleMatch ? titleMatch[1].replace(/\s*\(\d+자\)/, "") : null; } extractCategory(documentId) { const parts = documentId.split("--"); return parts[0] || "unknown"; } extractCharacterLimit(fileName) { const match = fileName.match(/-(\d+)\.md$/); return match ? parseInt(match[1]) : null; } isDocumentComplete(document) { const content = document.content; if (content.length < 10) return false; const hasPlaceholders = content.includes("여기에") || content.includes("μž‘μ„±ν•˜μ„Έμš”") || content.includes("Provide comprehensive guidance on") || content.includes("Provide comprehensive guida"); return !hasPlaceholders; } filterDocuments(documents, options) { let filtered = documents; if (options.characterLimit) filtered = filtered.filter((doc) => doc.characterLimit === options.characterLimit); if (options.category) filtered = filtered.filter((doc) => doc.category === options.category); return filtered; } sortDocuments(documents, sortBy) { const sorted = [...documents]; switch (sortBy) { case "priority": return sorted.sort((a, b) => { if (a.priority !== b.priority) return b.priority - a.priority; return a.documentId.localeCompare(b.documentId); }); case "category": return sorted.sort((a, b) => { if (a.category !== b.category) return a.category.localeCompare(b.category); return b.priority - a.priority; }); case "alphabetical": return sorted.sort((a, b) => a.documentId.localeCompare(b.documentId)); default: return sorted; } } generateContent(documents, options) { const { pattern, language, characterLimit } = options; let content = ""; if (characterLimit) { content += `# Documentation (${characterLimit} chars)\n\n`; content += `Generated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}\n`; content += `Type: ${pattern === "minimum" ? "Minimum" : pattern === "origin" ? "Origin" : "Standard"}\n`; content += `Language: ${language?.toUpperCase() || "EN"}\n\n`; content += `This document contains ${characterLimit}-character summaries of the documentation.\n\n`; } else content += `# Documentation\n\n`; switch (pattern) { case "minimum": content += this.generateMinimumContent(documents); break; case "origin": content += this.generateOriginContent(documents); break; default: content += this.generateStandardContent(documents); } content += "\n\n---\n\n"; content += `*Generated automatically on ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}*\n`; return content; } generateStandardHeader(language, filters) { let title = "Documentation"; if (filters.category) title += ` - ${filters.category.charAt(0).toUpperCase() + filters.category.slice(1)}`; if (filters.characterLimit) title += ` (${filters.characterLimit} chars)`; return [ `# ${title}`, "", `Generated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`, `Type: Standard`, `Language: ${language.toUpperCase()}`, "", `This document contains ${filters.characterLimit ? `${filters.characterLimit}-character` : "character-limited"} summaries${filters.category ? ` from the ${filters.category} category` : ""} of the documentation.` ].join("\n"); } generateMinimumHeader(language, filters) { return [ "# Document Navigation", "", `Generated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`, "Type: Minimum (Navigation Links)", `Language: ${language.toUpperCase()}`, "", `This document provides quick navigation links to${filters.category ? ` ${filters.category}` : ""} documentation${filters.characterLimit ? ` with ${filters.characterLimit}-character summaries` : ""}, organized by priority tiers.` ].join("\n"); } generateOriginHeader(language, filters) { return [ "# Complete Documentation", "", `Generated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`, "Type: Origin (Full Documents)", `Language: ${language.toUpperCase()}`, "", `This document contains the complete${filters.characterLimit ? ` ${filters.characterLimit}-character` : ""} content${filters.category ? ` from ${filters.category} category` : ""} of documentation, organized by priority.` ].join("\n"); } generateMetadata(documents, filters) { const stats = { totalDocuments: documents.length, categories: [...new Set(documents.map((d) => d.category))], characterLimits: [...new Set(documents.map((d) => d.characterLimit))], averageQuality: documents.filter((d) => d.metadata.quality_score).reduce((sum, d) => sum + d.metadata.quality_score, 0) / documents.filter((d) => d.metadata.quality_score).length || 0, totalCharacters: documents.reduce((sum, d) => sum + d.metadata.content_length, 0) }; return [ "## Document Collection Metadata", "", `**Total Documents**: ${stats.totalDocuments}`, `**Categories**: ${stats.categories.join(", ")}`, `**Character Limits**: ${stats.characterLimits.join(", ")}`, `**Total Characters**: ${stats.totalCharacters.toLocaleString()}`, `**Average Quality Score**: ${stats.averageQuality.toFixed(1)}`, "", "**Filters Applied**:", `- Language: ${filters.language}`, filters.characterLimit ? `- Character Limit: ${filters.characterLimit}` : "", filters.category ? `- Category: ${filters.category}` : "" ].filter(Boolean).join("\n"); } generateStandardContent(documents) { let content = ""; documents.forEach((doc, index) => { const readableTitle = this.formatReadableTitle(doc.title); content += `===================[ DOC: ${this.getRelativeSourcePath(doc)} ]===================\n`; content += `# ${readableTitle}\n\n`; content += `${doc.content}\n\n`; if (index < documents.length - 1) content += `\n`; }); return content.trim(); } formatReadableTitle(title) { if (title.includes("--")) { const parts = title.split("--"); const mainPart = parts[parts.length - 1]; return mainPart.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" "); } return title; } getRelativeSourcePath(doc) { return this.pathManager.getRelativeSourcePath(doc.documentId, doc.language, doc.characterLimit, doc.filePath); } generateMinimumContent(documents) { const categories = [...new Set(documents.map((d) => d.category))]; let content = ""; content += "## Quick Start Path\n\n"; content += "For first-time users, follow this recommended reading order:\n"; const topPriority = documents.sort((a, b) => b.priority - a.priority).slice(0, 4); topPriority.forEach((doc, index) => { const sourcePath = this.getRelativeSourcePath(doc); content += `${index + 1}. [${doc.title}](${sourcePath}) (${doc.category}) - Priority: ${doc.priority}\n`; }); content += "\n## Documents by Category\n\n"; categories.forEach((category) => { const categoryDocs = documents.filter((d) => d.category === category); content += `### ${category.charAt(0).toUpperCase() + category.slice(1)}\n\n`; categoryDocs.forEach((doc) => { const sourcePath = this.getRelativeSourcePath(doc); const preview = doc.content.substring(0, 80); content += `- **[${this.formatReadableTitle(doc.title)}](${sourcePath})** (Priority: ${doc.priority}, ${doc.characterLimit} chars) - ${preview}...\n`; }); content += "\n"; }); return content; } generateOriginContent(documents) { let content = "## Complete Documentation Content\n\n"; documents.forEach((doc, index) => { const readableTitle = this.formatReadableTitle(doc.title); content += `===================[ DOC: ${this.getRelativeSourcePath(doc)} ]===================\n`; content += `## ${readableTitle}\n`; content += `*Source: [${this.getRelativeSourcePath(doc)}](${this.getRelativeSourcePath(doc)}) | Category: ${doc.category} | Priority: ${doc.priority} | Characters: ${doc.metadata.content_length}*\n\n`; content += `${doc.content}\n\n`; if (index < documents.length - 1) content += `\n`; }); return content; } async writeOutput(content, options) { const { language, characterLimit, category, pattern, outputDir } = options; const { outputPath } = this.pathManager.generateOutputPath({ language: language || "en", characterLimit, category, pattern, outputDir }); await promises.mkdir(path.dirname(outputPath), { recursive: true }); await promises.writeFile(outputPath, content, "utf-8"); return outputPath; } displayFilterCriteria(options) { console.log("\nπŸ“‹ Filter Criteria:"); console.log(` Language: ${options.language}`); if (options.characterLimit) console.log(` Character Limit: ${options.characterLimit}`); if (options.category) console.log(` Category: ${options.category}`); console.log(` Pattern: ${options.pattern}`); console.log("\nπŸ’‘ Try adjusting your filters or check if documents exist with these criteria."); } displayDocumentList(documents) { console.log(`πŸ“„ Found ${documents.length} documents:\n`); documents.forEach((doc, index) => { console.log(`${index + 1}. ${doc.documentId}`); console.log(` πŸ“ Category: ${doc.category}`); console.log(` πŸ“ Character Limit: ${doc.characterLimit}`); console.log(` ⭐ Priority: ${doc.priority}`); console.log(` πŸ“Š Content Length: ${doc.metadata.content_length} chars`); console.log(` 🎯 Status: ${doc.metadata.completion_status}`); console.log(); }); } displayDryRunSummary(documents, options) { const totalChars = documents.reduce((sum, doc) => sum + doc.metadata.content_length, 0); console.log("πŸ“Š Dry Run Summary:"); console.log(` Would generate LLMS file with:`); console.log(` β€’ ${documents.length} documents`); console.log(` β€’ ${totalChars.toLocaleString()} total characters`); console.log(` β€’ Pattern: ${options.pattern}`); console.log(` β€’ Language: ${options.language}`); if (options.characterLimit) console.log(` β€’ Character Limit: ${options.characterLimit}`); if (options.category) console.log(` β€’ Category: ${options.category}`); console.log(); } displayResults(result) { console.log("\nβœ… LLMS file generated successfully!\n"); console.log("πŸ“Š Generation Summary:"); console.log(` πŸ“„ Output: ${result.outputPath}`); console.log(` πŸ“ Pattern: ${result.pattern}`); console.log(` πŸ“š Documents: ${result.totalDocuments}`); console.log(` πŸ“ Total Characters: ${result.totalCharacters.toLocaleString()}`); console.log(` πŸ“ˆ Average per Document: ${result.averageCharacters} chars`); console.log("\nπŸ” Filters Applied:"); console.log(` 🌐 Language: ${result.filters.language}`); if (result.filters.characterLimit) console.log(` πŸ“ Character Limit: ${result.filters.characterLimit}`); if (result.filters.category) console.log(` πŸ“ Category: ${result.filters.category}`); console.log(); } }; //#endregion //#region src/cli/commands/GenerateTemplatesCommand.ts var GenerateTemplatesCommand = class { constructor(config) { this.config = config; } async execute(options = {}) { const { language = this.config.generation?.defaultLanguage || "en", characterLimits = this.config.generation?.characterLimits || [ 100, 300, 500, 1e3, 2e3, 5e3 ], overwrite = false, dryRun = false, verbose = false } = options; console.log("πŸš€ Generating template files for all documents...\n"); console.log(`πŸ“‹ Configuration:`); console.log(` Language: ${language}`); console.log(` Character Limits: ${characterLimits.join(", ")}`); console.log(` Overwrite: ${overwrite}`); if (dryRun) console.log(` Mode: DRY RUN (no files will be created)`); console.log(); const priorityFiles = await this.findPriorityFiles(language, options.category); if (priorityFiles.length === 0) { console.log("❌ No priority.json files found"); return; } console.log(`πŸ“ Found ${priorityFiles.length} documents to process\n`); const results = []; for (const priorityFile of priorityFiles) { const result = await this.generateTemplatesForDocument(priorityFile, characterLimits, language, { overwrite, dryRun, verbose }); results.push(result); if (verbose) this.displayDocumentResult(result); } this.displaySummary(results); } async findPriorityFiles(language, category) { const llmsDataDir = path.join(this.config.paths.llmContentDir, language); const priorityFiles = []; try { const entries = await promises.readdir(llmsDataDir, { withFileTypes: true }); for (const entry of entries) { if (!entry.isDirectory()) continue; const documentId = entry.name; if (category) { const docCategory = documentId.split("--")[0]; if (docCategory !== category) continue; } const priorityPath = path.join(llmsDataDir, documentId, "priority.json"); try { await promises.access(priorityPath); priorityFiles.push(priorityPath); } catch {} } } catch (error) { console.error(`❌ Error reading directory ${llmsDataDir}: ${error}`); } return priorityFiles; } async generateTemplatesForDocument(priorityPath, characterLimits, language, options) { const documentDir = path.dirname(priorityPath); const documentId = path.basename(documentDir); const result = { documentId, templatesCreated: 0, templatesSkipped: 0, errors: [] }; try { const priorityContent = await promises.readFile(priorityPath, "utf-8"); const priorityData = JSON.parse(priorityContent); const sourceContent = await this.extractSourceContent(priorityData, language); for (const limit of characterLimits) try { const templatePath = path.join(documentDir, `${documentId}-${limit}.md`); if (!options.overwrite) try { await promises.access(templatePath); result.templatesSkipped++; continue; } catch {} const templateContent = this.generateTemplateContent(priorityData, sourceContent, limit, documentId); if (!options.dryRun) await promises.writeFile(templatePath, templateContent, "utf-8"); result.templatesCreated++; } catch (error) { result.errors.push(`Failed to create ${limit}-char template: ${error}`); } } catch (error) { result.errors.push(`Failed to process document: ${error}`); } return result; } async extractSourceContent(priorityData, _language) { if (!priorityData.document?.source_path) return ""; const sourcePath = path.join(this.config.paths.docsDir, priorityData.document.source_path); try { const content = await promises.readFile(sourcePath, "utf-8"); const parsed = matter(content); return parsed.content.replace(/^#+\s+/gm, "").replace(/```[\s\S]*?```/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[*_~`]/g, "").replace(/\n{3,}/g, "\n\n").trim(); } catch { console.warn(`⚠️ Could not read source document: ${sourcePath}`); return ""; } } generateTemplateContent(priorityData, sourceContent, characterLimit, documentId) { const summary = this.generateSummary(sourceContent, characterLimit, priorityData); const frontmatter = { document_id: documentId, category: priorityData.document?.category || documentId.split("--")[0], source_path: priorityData.document?.source_path || "", character_limit: characterLimit, last_update: (/* @__PURE__ */ new Date()).toISOString(), update_status: "auto_generated", priority_score: priorityData.priority?.score || 50, priority_tier: priorityData.priority?.tier || "standard", completion_status: "completed", workflow_stage: "content_generated" }; const content = summary || `${priorityData.document?.title || documentId} - ${characterLimit} character summary`; return matter.stringify(content, frontmatter); } generateSummary(sourceContent, characterLimit, priorityData) { if (!sourceContent) { const title = priorityData.document?.title || ""; const goal = priorityData.purpose?.primary_goal || ""; const cleanGoal = goal.replace("Provide comprehensive guidance on ", ""); if (title && cleanGoal && !cleanGoal.includes("Auto-generated")) return `${title}: ${cleanGoal}`.substring(0, characterLimit); return ""; } const sentences = sourceContent.split(/[.!?]\s+/).filter((s) => s.trim()); let summary = ""; for (const sentence of sentences) { const trimmedSentence = sentence.trim(); if (!trimmedSentence) continue; const testSummary = summary ? `${summary}. ${trimmedSentence}` : trimmedSentence; if (testSummary.length <= characterLimit - 10) summary = testSummary; else if (!summary) { summary = trimmedSentence.substring(0, characterLimit - 10) + "..."; break; } else break; } if (summary && !summary.match(/[.!?]$/)) summary += "."; return summary; } displayDocumentResult(result) { console.log(`πŸ“„ ${result.documentId}:`); if (result.templatesCreated > 0) console.log(` βœ… Created: ${result.templatesCreated} templates`); if (result.templatesSkipped > 0) console.log(` ⏭️ Skipped: ${result.templatesSkipped} (already exist)`); if (result.errors.length > 0) { console.log(` ❌ Errors: ${result.errors.length}`); result.errors.forEach((err) => console.log(` - ${err}`)); } console.log(); } displaySummary(results) { const totalCreated = results.reduce((sum, r) => sum + r.templatesCreated, 0); const totalSkipped = results.reduce((sum, r) => sum + r.templatesSkipped, 0); const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0); const successfulDocs = results.filter((r) => r.templatesCreated > 0).length; console.log("\n" + "=".repeat(60)); console.log("πŸ“Š GENERATION SUMMARY"); console.log("=".repeat(60)); console.log(`πŸ“ Documents Processed: ${results.length}`); console.log(`βœ… Templates Created: ${totalCreated}`); console.log(`⏭️ Templates Skipped: ${totalSkipped}`); console.log(`πŸ“„ Successful Documents: ${successfulDocs}`); if (totalErrors > 0) console.log(`❌ Total Errors: ${totalErrors}`); console.log("=".repeat(60)); } }; //#endregion //#region src/cli/commands/SimpleLLMSCommand.ts var SimpleLLMSCommand = class { pathManager; constructor(config) { this.config = config; const cliConfig = { paths: config.paths, generation: { supportedLanguages: config.generation.supportedLanguages, characterLimits: config.generation.characterLimits, defaultCharacterLimits: { summary: 300, detailed: 1e3, comprehensive: 2e3 }, defaultLanguage: config.generation.defaultLanguage, outputFormat: "txt" }, quality: config.quality, categories: config.categories }; this.pathManager = new LLMSOutputPathManager(cliConfig); } async execute(options) { const { characterLimit, category, language = this.config.generation?.defaultLanguage || "ko", pattern = "clean", patterns, generateAll = false, outputDir = this.config.paths?.outputDir || "./docs/llms", dryRun = false, verbose = false } = options; if (generateAll || !pattern && !patterns && !characterLimit) return this.executeMultipleGeneration({ ...options, language, outputDir, dryRun, verbose }); if (verbose) { console.log("πŸš€ Generating clean LLMS-TXT file..."); if (dryRun) console.log("πŸ” DRY RUN - No files will be created"); } const documents = await this.findDocuments(language, characterLimit, category); if (documents.length === 0) { console.log("❌ No completed documents found matching the specified criteria"); console.log("πŸ“‹ Filter Criteria:"); console.log(` Language: ${language}`); if (characterLimit) console.log(` Character Limit: ${characterLimit}`); if (category) console.log(` Category: ${category}`); console.log("πŸ’‘ Try running `work-next --show-completed` to see available documents."); return; } if (verbose) { console.log(`πŸ“„ Found ${documents.length} completed documents:`); documents.forEach((doc, i) => { console.log(`${i + 1}. ${doc.title} (${doc.category}, ${doc.characterLimit} chars)`); }); console.log(); } const content = this.generateCleanContent(documents, pattern); const { outputPath, filename } = this.pathManager.generateOutputPath({ language, characterLimit, category, pattern, outputDir }); if (dryRun) { console.log("πŸ“Š Dry Run Summary:"); console.log(` Would generate: ${filename}`); console.log(` Documents: ${documents.length}`); console.log(` Total characters: ${content.length}`); console.log(` Pattern: ${pattern}`); console.log(` Language: ${language}`); if (characterLimit) console.log(` Character Limit: ${characterLimit}`); if (category) console.log(` Category: ${category}`); return; } await promises.mkdir(path.dirname(outputPath), { recursive: true }); await promises.writeFile(outputPath, content, "utf-8"); console.log("βœ… Clean LLMS file generated successfully!"); console.log(`πŸ“„ Output: ${outputPath}`); console.log(`πŸ“ Pattern: ${pattern}`); console.log(`πŸ“š Documents: ${documents.length}`); console.log(`πŸ“ Total Characters: ${content.length}`); if (documents.length > 0) { const avgChars = Math.round(content.length / documents.length); console.log(`πŸ“ˆ Average per Document: ${avgChars} chars`); } } async findDocuments(language, characterLimit, category) { const dataDir = path.resolve(this.config.paths?.llmContentDir || "./data"); const languageDir = path.join(dataDir, language); if (!await this.exists(languageDir)) { console.log(`❌ Language directory does not exi