framework
Version:
The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.
61 lines • 2.8 kB
JavaScript
import { readFile, readdir } from 'node:fs/promises';
import { join } from 'node:path';
import { readDataFile } from '../data-branch.js';
import { FLAT_TODO_FILE } from '../tickets.js';
/**
* The plan/backlog document categories the dashboard surfaces in its sidebar
* (#319, part of the MVP UI #309), so the human can read them beside the agent.
*
* The Framework's system prompt writes these per session (#323/#326):
* `PLAN_<SESSION>.agent.md` (the plan for now) and `TODO_<SESSION>.agent.md` (the
* backlog), where SESSION is a git-branch slug. The flat fallbacks are `PLAN.md`
* (root) and the flat backlog (`backlog: true` reads `TODO_AGENTS.md` off the data
* branch, its one location since #1582). Scoped and flat-root names are matched
* against a flat readdir of the root, never taken from user input, so there is no
* path traversal to guard against.
*/
export const DOC_CATEGORIES = [
{ flat: 'PLAN.md', scoped: /^PLAN_[a-z0-9-]+\.agent\.md$/ },
{ flat: 'TODO_AGENTS.md', backlog: true, scoped: /^TODO_[a-z0-9-]+\.agent\.md$/ },
];
/** Cap a single doc so a runaway file can't bloat the docs payload. */
const MAX_DOC_BYTES = 200_000;
/**
* The workspace-root filenames to surface, in sidebar order: per category the flat
* file (if present) then its session-scoped files (sorted). Every name is a bare
* readdir entry matched against a fixed pattern, so none can traverse. Returns
* empty when the workspace is missing or unreadable.
*/
/**
* Read the surfaced plan/backlog docs, in sidebar order: per category the flat file (if present)
* then its session-scoped files (sorted). The flat backlog comes off the data branch (#1582);
* everything else is a workspace-root file. Missing or blank files are skipped; a file over the
* size cap is truncated. Never throws — a read error just omits that doc.
*/
export async function readDocs(cwd) {
let entries;
try {
entries = await readdir(cwd);
}
catch {
return [];
}
const present = new Set(entries);
const docs = [];
const push = (name, content) => {
if (!content?.trim())
return;
docs.push({ name, content: content.length > MAX_DOC_BYTES ? content.slice(0, MAX_DOC_BYTES) + '\n\n… (truncated)' : content });
};
for (const cat of DOC_CATEGORIES) {
if ('backlog' in cat)
push(FLAT_TODO_FILE, await readDataFile(cwd, FLAT_TODO_FILE));
else if (present.has(cat.flat))
push(cat.flat, await readFile(join(cwd, cat.flat), 'utf8').catch(() => undefined));
for (const name of entries.filter(e => cat.scoped.test(e)).sort()) {
push(name, await readFile(join(cwd, name), 'utf8').catch(() => undefined));
}
}
return docs;
}
//# sourceMappingURL=docs.js.map