mirror of
https://github.com/plebbit/seedit.git
synced 2026-06-12 18:15:57 -04:00
docs(agents): add generated LLM context workflow
This commit is contained in:
241
scripts/generate-llms-files.mjs
Normal file
241
scripts/generate-llms-files.mjs
Normal file
@@ -0,0 +1,241 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
|
||||
const config = JSON.parse(await readText("scripts/llms-config.json"));
|
||||
|
||||
for (const key of ["title", "summary", "repoUrl", "llmsFullUrl"]) {
|
||||
if (!config[key]) throw new Error(`Missing llms config key: ${key}`);
|
||||
}
|
||||
|
||||
const docs = await loadDocs(config.docs);
|
||||
const optionalDocs = await loadDocs(config.optionalDocs || []);
|
||||
|
||||
if (docs.length === 0) {
|
||||
throw new Error("No source docs found for llms generation");
|
||||
}
|
||||
|
||||
await writeOutputs(config.shortOutputs, buildShortFile(config, docs, optionalDocs));
|
||||
await writeOutputs(config.fullOutputs, buildFullFile(config, docs, optionalDocs));
|
||||
|
||||
function buildShortFile(config, docs, optionalDocs) {
|
||||
const lines = [
|
||||
`# ${config.title}`,
|
||||
"",
|
||||
`> ${config.summary}`,
|
||||
"",
|
||||
"This file is generated by `scripts/generate-llms-files.mjs`. Do not hand-edit it; update the source docs or generator config, then run `yarn llms:generate`.",
|
||||
"",
|
||||
"## Canonical Links",
|
||||
"",
|
||||
...config.links.map(([label, url]) => `- [${label}](${url})`),
|
||||
`- [llms-full.txt](${config.llmsFullUrl}): Expanded inline corpus generated from the curated source docs.`,
|
||||
"",
|
||||
"## Critical Context",
|
||||
"",
|
||||
...config.notes.map((note) => `- ${note}`),
|
||||
"",
|
||||
"## Source Of Truth",
|
||||
"",
|
||||
"- Code, tests, package manifests, source docs, and live/runtime evidence when relevant are source of truth.",
|
||||
"- This generated file is compiled context for orientation. Verify behavioral claims against source files before editing or concluding.",
|
||||
"- Repo-managed AI instructions live in `AGENTS.md` and any directory-specific `AGENTS.md` files.",
|
||||
"",
|
||||
"## Core Documents",
|
||||
"",
|
||||
...docs.map(formatDocLink)
|
||||
];
|
||||
|
||||
if (optionalDocs.length > 0) {
|
||||
lines.push("", "## Optional", "", ...optionalDocs.map(formatDocLink));
|
||||
}
|
||||
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
function buildFullFile(config, docs, optionalDocs) {
|
||||
const allDocs = [...docs, ...optionalDocs];
|
||||
const lines = [
|
||||
`# ${config.title} Full LLM Context`,
|
||||
"",
|
||||
`> ${config.summary}`,
|
||||
"",
|
||||
"This file is generated by `scripts/generate-llms-files.mjs`. Do not hand-edit it; update the source docs or generator config, then run `yarn llms:generate`.",
|
||||
"",
|
||||
"Use this as compiled context only. Source files, tests, manifests, and live/runtime evidence remain authoritative.",
|
||||
"",
|
||||
"## Index",
|
||||
"",
|
||||
...allDocs.map((doc) => `- [${doc.relativePath}](#${anchorFor(doc.relativePath)})`),
|
||||
"",
|
||||
"---"
|
||||
];
|
||||
|
||||
for (const doc of allDocs) {
|
||||
lines.push(
|
||||
"",
|
||||
`## ${doc.relativePath}`,
|
||||
"",
|
||||
`Source: ${doc.url}`,
|
||||
"",
|
||||
"```markdown",
|
||||
doc.content.trim(),
|
||||
"```",
|
||||
"",
|
||||
"---"
|
||||
);
|
||||
}
|
||||
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
function formatDocLink(doc) {
|
||||
return `- [${doc.title}](${doc.url}): ${doc.summary}`;
|
||||
}
|
||||
|
||||
async function loadDocs(relativePaths) {
|
||||
const docs = [];
|
||||
|
||||
for (const relativePath of relativePaths) {
|
||||
const content = await readOptional(relativePath);
|
||||
if (!content) continue;
|
||||
|
||||
docs.push({
|
||||
relativePath,
|
||||
content,
|
||||
title: titleFromMarkdown(content, relativePath),
|
||||
summary: summaryFromMarkdown(content),
|
||||
url: `${config.repoUrl}/blob/master/${encodeURI(relativePath).replace(/%2F/g, "/")}`
|
||||
});
|
||||
}
|
||||
|
||||
return docs;
|
||||
}
|
||||
|
||||
function titleFromMarkdown(content, fallback) {
|
||||
if (fallback.endsWith("CHANGELOG.md")) return "Changelog";
|
||||
if (fallback.endsWith("TODO.md")) return "TODO";
|
||||
|
||||
let inFence = false;
|
||||
let inFrontmatter = false;
|
||||
let fallbackHeading = "";
|
||||
const lines = content.split(/\r?\n/);
|
||||
|
||||
for (let index = 0; index < lines.length; index += 1) {
|
||||
const trimmed = lines[index].trim();
|
||||
|
||||
if (index === 0 && trimmed === "---") {
|
||||
inFrontmatter = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inFrontmatter) {
|
||||
if (trimmed === "---") inFrontmatter = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmed.startsWith("```")) {
|
||||
inFence = !inFence;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inFence) continue;
|
||||
|
||||
const h1 = trimmed.match(/^#\s+(.+)$/);
|
||||
if (h1) return h1[1].trim();
|
||||
|
||||
const h2 = trimmed.match(/^##\s+(.+)$/);
|
||||
if (h2 && !fallbackHeading) fallbackHeading = h2[1].trim();
|
||||
}
|
||||
|
||||
return fallbackHeading || fallback;
|
||||
}
|
||||
|
||||
function summaryFromMarkdown(content) {
|
||||
const lines = content.split(/\r?\n/);
|
||||
const paragraphs = [];
|
||||
let current = [];
|
||||
let inFence = false;
|
||||
let inFrontmatter = false;
|
||||
|
||||
for (let index = 0; index < lines.length; index += 1) {
|
||||
const line = lines[index];
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (index === 0 && trimmed === "---") {
|
||||
inFrontmatter = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inFrontmatter) {
|
||||
if (trimmed === "---") inFrontmatter = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmed.startsWith("```")) {
|
||||
inFence = !inFence;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inFence || !trimmed || trimmed.startsWith("#") || trimmed.startsWith("|")) {
|
||||
if (current.length > 0) {
|
||||
paragraphs.push(current.join(" "));
|
||||
current = [];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
trimmed.startsWith("[![") ||
|
||||
trimmed.startsWith("_Telegram") ||
|
||||
trimmed.startsWith("<img") ||
|
||||
trimmed.startsWith("<p") ||
|
||||
trimmed.startsWith("<br") ||
|
||||
trimmed.startsWith("</") ||
|
||||
trimmed.startsWith("<!--")
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
current.push(trimmed.replace(/\s+/g, " "));
|
||||
}
|
||||
|
||||
if (current.length > 0) paragraphs.push(current.join(" "));
|
||||
|
||||
const summary = paragraphs.find(Boolean) || "Repository documentation.";
|
||||
return summary.length > 220 ? `${summary.slice(0, 217).trimEnd()}...` : summary;
|
||||
}
|
||||
|
||||
function anchorFor(value) {
|
||||
return value
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/g, "")
|
||||
.trim()
|
||||
.replace(/\s+/g, "-");
|
||||
}
|
||||
|
||||
async function writeOutputs(relativePaths, content) {
|
||||
for (const relativePath of relativePaths) {
|
||||
const absolutePath = path.join(rootDir, relativePath);
|
||||
await mkdir(path.dirname(absolutePath), { recursive: true });
|
||||
await writeFile(absolutePath, content);
|
||||
console.log(`[llms] wrote ${relativePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function readOptional(relativePath) {
|
||||
try {
|
||||
return await readText(relativePath);
|
||||
} catch (error) {
|
||||
if (error && error.code === "ENOENT") return "";
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function readText(relativePath) {
|
||||
return readFile(path.join(rootDir, relativePath), "utf8");
|
||||
}
|
||||
33
scripts/llms-config.json
Normal file
33
scripts/llms-config.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"title": "Seedit",
|
||||
"summary": "Seedit is a static, serverless, adminless, decentralized old.reddit-style client built on the Bitsocial protocol.",
|
||||
"repoUrl": "https://github.com/bitsocialnet/seedit",
|
||||
"llmsUrl": "https://seedit.app/llms.txt",
|
||||
"llmsFullUrl": "https://seedit.app/llms-full.txt",
|
||||
"shortOutputs": ["public/llms.txt"],
|
||||
"fullOutputs": ["public/llms-full.txt"],
|
||||
"links": [
|
||||
["App", "https://seedit.app"],
|
||||
["Repository", "https://github.com/bitsocialnet/seedit"],
|
||||
["Releases", "https://github.com/bitsocialnet/seedit/releases/latest"],
|
||||
["Bitsocial protocol", "https://bitsocial.net"]
|
||||
],
|
||||
"notes": [
|
||||
"Seedit is a client for Bitsocial communities, not a centralized forum backend.",
|
||||
"Community owners create, host, and moderate their own communities.",
|
||||
"The web app uses hash routing and a static app-shell deployment model.",
|
||||
"For code contributions, AGENTS.md and directory-specific AGENTS.md files are the source of truth."
|
||||
],
|
||||
"docs": [
|
||||
"README.md",
|
||||
"AGENTS.md",
|
||||
"src/AGENTS.md",
|
||||
"scripts/AGENTS.md",
|
||||
"docs/agent-playbooks/known-surprises.md",
|
||||
"docs/agent-playbooks/long-running-agent-workflow.md",
|
||||
"docs/agent-playbooks/bug-investigation.md",
|
||||
"docs/agent-playbooks/translations.md",
|
||||
"docs/agent-playbooks/skills-and-tools.md"
|
||||
],
|
||||
"optionalDocs": ["CHANGELOG.md"]
|
||||
}
|
||||
Reference in New Issue
Block a user