Project Files
src / toolsProvider.ts
import { tool, type Tool, type ToolsProviderController } from "@lmstudio/sdk";
import { z } from "zod";
import { existsSync, mkdirSync } from "fs";
import { writeFile } from "fs/promises";
import { dirname, join } from "path";
import {
TemplateTypeEnum,
FrontmatterSchema,
} from "./schemas";
import { TEMPLATES } from "./templates";
function generateFrontmatter(frontmatter?: {
id?: string;
module?: string;
version?: string;
strict_mode?: boolean;
dependencies?: string[];
ai_behavior?: "critical_honesty" | "creative" | "assistive";
}): string {
const fm = frontmatter || {};
const lines = ["---"];
if (fm.id) lines.push(`id: "${fm.id}"`);
if (fm.module) lines.push(`module: "${fm.module}"`);
if (fm.version) lines.push(`version: "${fm.version}"`);
if (fm.strict_mode !== undefined) lines.push(`strict_mode: ${fm.strict_mode}`);
if (fm.dependencies?.length) lines.push(`dependencies: [${fm.dependencies.map(d => `"${d}"`).join(", ")}]`);
if (fm.ai_behavior) lines.push(`ai_behavior: "${fm.ai_behavior}"`);
lines.push("---");
return lines.length > 2 ? lines.join("\n") : "";
}
function validateMarkdownStructure(content: string): {
valid: boolean;
hasFrontmatter: boolean;
hasInlineMetadata: boolean;
hasSectionIds: boolean;
errors: string[];
warnings: string[];
} {
const errors: string[] = [];
const warnings: string[] = [];
const frontmatterRegex = /^---\n[\s\S]*?\n---/;
const hasFrontmatter = frontmatterRegex.test(content);
const inlineMetadataRegex = /^[A-Za-z]+::\s*.+$/m;
const hasInlineMetadata = inlineMetadataRegex.test(content);
const sectionIdRegex = /(§\d+(?:\.\d+)?|@@ID:[a-zA-Z0-9_-]+)/;
const hasSectionIds = sectionIdRegex.test(content);
if (!hasFrontmatter) {
warnings.push("Brak YAML frontmatter - zalecane dla zgodności ze standardami");
}
if (!hasInlineMetadata) {
warnings.push("Brak metadanych inline (Klucz:: Wartość) - zalecane dla adresowalności");
}
if (!hasSectionIds) {
warnings.push("Brak unikalnych ID sekcji (§ lub @@ID:) - zalecane dla systemów RAG");
}
const valid = errors.length === 0;
return {
valid,
hasFrontmatter,
hasInlineMetadata,
hasSectionIds,
errors,
warnings,
};
}
function fillTemplateWithData(template: string, data: Record<string, unknown>): string {
let result = template;
const placeholderRegex = /\{\{([^}]+)\}\}/g;
result = result.replace(placeholderRegex, (match, key) => {
const trimmedKey = key.trim();
const value = data[trimmedKey];
if (value === undefined || value === null) {
return match;
}
if (Array.isArray(value)) {
return value.join("\n");
}
if (typeof value === "object") {
return JSON.stringify(value);
}
return String(value);
});
result = result.replace(/\{\{#each (\w+)\}\}[\s\S]*?\{\{\/each\}\}/g, (match, arrayKey) => {
const array = data[arrayKey];
if (!Array.isArray(array) || array.length === 0) {
return "";
}
const blockContent = match.match(/\{\{#each \w+\}\}([\s\S]*?)\{\{\/each\}\}/)?.[1] || "";
return array.map((item: unknown, index: number) => {
let block = blockContent;
if (typeof item === "object" && item !== null) {
Object.entries(item).forEach(([k, v]) => {
block = block.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), String(v));
});
} else {
block = block.replace(/\{\{this\}\}/g, String(item));
block = block.replace(/\{\{@index\}\}/g, String(index + 1));
}
return block;
}).join("");
});
result = result.replace(/\{\{#if (\w+)\}\}([\s\S]*?)\{\{else\}\}([\s\S]*?)\{\{\/if\}\}/g, (_match, key, ifBlock, elseBlock) => {
const value = data[key];
return value ? ifBlock : (elseBlock || "");
});
result = result.replace(/\{\{#if (\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_match, key, block) => {
const value = data[key];
return value ? block : "";
});
return result;
}
export async function toolsProvider(ctl: ToolsProviderController): Promise<Tool[]> {
const createMarkdownFile = tool({
name: "create_markdown_file",
description: `Tworzy plik Markdown zgodny ze standardami dokumentacyjnymi (file_construction_standards).
WYMAGANIA FORMATOWANIA:
- YAML Frontmatter z polami: id, module, version, strict_mode, dependencies, ai_behavior
- Metadane inline: Klucz:: Wartość
- Sekcje oznaczone: § (np. §1.1) lub @@ID:nazwa
- Emoji w tytułach sekcji (🏗️, 📋, 🚀, etc.)
- Separator sekcji: ---
- Placeholdery: {{nazwa}} lub [Nazwa]
- Tabele dla danych porównawczych
- Bloki Mermaid dla diagramów
- Callouts: > [!NOTE], > [!TIP], > [!WARNING]
OBSŁUGIWANE SZABLONY:
- agent_rules: zasady agenta z sekcją filozofii
- rosetta_stone: transformacja dokumentacji
- script_vision: wizja skryptu
- script_spec: specyfikacja skryptu
- custom: własny format`,
parameters: {
file_name: z.string(),
content: z.string(),
template_type: TemplateTypeEnum.optional(),
frontmatter: FrontmatterSchema.optional(),
force: z.boolean().optional(),
},
implementation: async ({ file_name, content, template_type, frontmatter, force }) => {
try {
const workingDir = ctl.getWorkingDirectory();
const filePath = join(workingDir, file_name);
if (!file_name.endsWith(".md")) {
return "Błąd: Plik musi mieć rozszerzenie .md";
}
if (existsSync(filePath) && !force) {
return `Błąd: Plik ${file_name} już istnieje. Użyj force=true aby nadpisać.`;
}
let finalContent = content;
const fm = generateFrontmatter(frontmatter);
if (fm) {
finalContent = fm + "\n\n" + content;
}
const dir = dirname(filePath);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
await writeFile(filePath, finalContent, "utf-8");
const validation = validateMarkdownStructure(finalContent);
return `✅ Plik ${file_name} utworzony pomyślnie.
Lokalizacja: ${filePath}
Struktura:
- YAML Frontmatter: ${validation.hasFrontmatter ? "✅" : "❌ (brak)"}
- Metadane inline: ${validation.hasInlineMetadata ? "✅" : "❌ (brak)"}
- ID sekcji: ${validation.hasSectionIds ? "✅" : "❌ (brak)"}
${validation.warnings.length > 0 ? "\nOstrzeżenia:\n" + validation.warnings.map(w => "- " + w).join("\n") : ""}`;
} catch (error) {
return `Błąd podczas tworzenia pliku: ${error instanceof Error ? error.message : "Nieznany błąd"}`;
}
},
});
const validateMarkdownStructureTool = tool({
name: "validate_markdown_structure",
description: `Waliduje strukturę pliku Markdown pod kątem zgodności ze standardami dokumentacyjnymi.
SPRAWDZA:
- YAML Frontmatter (id, module, version, strict_mode, dependencies, ai_behavior)
- Metadane inline w formacie: Klucz:: Wartość
- Unikalne ID sekcji: § (np. §1.1) lub @@ID:nazwa
- Poprawność składni Markdown
ZWRACA:
- Lista błędów krytycznych
- Lista ostrzeżeń (brak frontmatter, brak metadanych)
- Ocena ogólna (valid/invalid)`,
parameters: {
content: z.string(),
},
implementation: async ({ content }) => {
const result = validateMarkdownStructure(content);
const status = result.valid ? "✅ POPRAWNY" : "❌ NIEPOPRAWNY";
let output = `📊 Wynik walidacji: ${status}\n\n`;
output += "**Struktura wykryta:**\n";
output += `- YAML Frontmatter: ${result.hasFrontmatter ? "✅" : "❌"}\n`;
output += `- Metadane inline: ${result.hasInlineMetadata ? "✅" : "❌"}\n`;
output += `- ID sekcji: ${result.hasSectionIds ? "✅" : "❌"}\n\n`;
if (result.errors.length > 0) {
output += "**Błędy:**\n" + result.errors.map(e => `- ❌ ${e}`).join("\n") + "\n\n";
}
if (result.warnings.length > 0) {
output += "**Ostrzeżenia:**\n" + result.warnings.map(w => `- ⚠️ ${w}`).join("\n");
}
return output;
},
});
const fillTemplateTool = tool({
name: "fill_template",
description: `Wypełnia szablon Markdown danymi i zwraca gotowy do zapisu dokument.
DOSTĘPNE SZABLONY:
- script_vision: Wizja skryptu - definiuje cel, problem i koncepcję skryptu
- script_spec: Specyfikacja skryptu - szczegółowa dokumentacja techniczna
- rosetta_stone: Transformacja istniejącej dokumentacji do formatu adresowalnego
- agent_rules: Zasady agenta - standardy kodowania i architektury
WYMAGANIA DLA WYNIKU:
- YAML Frontmatter z id, module, version, strict_mode, ai_behavior
- Metadane inline: Klucz:: Wartość
- Sekcje oznaczone: § (np. §1.1) lub @@ID:nazwa
- Emoji w tytułach sekcji (🏗️, 📋, 🚀, 💡, 📜)
- Separator sekcji: ---
- Placeholdery wypełnione konkretnymi wartościami (nie zostawiaj {{placeholder}})`,
parameters: {
template_type: z.enum(["script_vision", "script_spec", "rosetta_stone", "agent_rules"]),
data: z.record(z.any()),
frontmatter: FrontmatterSchema.optional(),
},
implementation: async ({ template_type, data, frontmatter }) => {
try {
const template = TEMPLATES[template_type as keyof typeof TEMPLATES];
if (!template) {
return `Błąd: Nieznany typ szablonu "${template_type}". Dostępne: script_vision, script_spec, rosetta_stone, agent_rules`;
}
const fm = generateFrontmatter(frontmatter);
const filledTemplate = fillTemplateWithData(template, data);
const finalContent = fm ? fm + "\n\n" + filledTemplate : filledTemplate;
const validation = validateMarkdownStructure(finalContent);
const hasUnfilledPlaceholders = /\{\{[^}]+\}\}/.test(finalContent);
if (hasUnfilledPlaceholders) {
return `⚠️ Uwaga: Szablon może zawierać niewypełnione placeholdery.\n\n` + finalContent + `\n\n--- WALIDACJA ---\n` +
`- YAML Frontmatter: ${validation.hasFrontmatter ? "✅" : "❌"}\n` +
`- Metadane inline: ${validation.hasInlineMetadata ? "✅" : "❌"}\n` +
`- ID sekcji: ${validation.hasSectionIds ? "✅" : "❌"}`;
}
return `✅ Szablon "${template_type}" wypełniony pomyślnie.\n\n--- WYNIK ---\n\n` + finalContent + `\n\n--- WALIDACJA ---\n` +
`- YAML Frontmatter: ${validation.hasFrontmatter ? "✅" : "❌"}\n` +
`- Metadane inline: ${validation.hasInlineMetadata ? "✅" : "❌"}\n` +
`- ID sekcji: ${validation.hasSectionIds ? "✅" : "❌"}`;
} catch (error) {
return `Błąd podczas wypełniania szablonu: ${error instanceof Error ? error.message : "Nieznany błąd"}`;
}
},
});
return [createMarkdownFile, validateMarkdownStructureTool, fillTemplateTool];
}