@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
JavaScript
#!/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