An agent harness for spec-driven development.
0

Configure Feed

Select the types of activity you want to include in your feed.

charter / src / spec-files.ts
2.3 kB 75 lines
1import fs from "node:fs/promises"; 2import path from "node:path"; 3import fg from "fast-glob"; 4import { parseSpecMarkdown } from "./markdown.js"; 5import type { SpecDocumentRow, SpecImportPlan, SpecMarkdownDocument } from "./types.js"; 6 7export interface ScanSpecOptions { 8 projectRoot?: string; 9 specDir?: string; 10} 11 12export async function scanSpecDocuments(options: ScanSpecOptions = {}): Promise<SpecMarkdownDocument[]> { 13 const projectRoot = path.resolve(options.projectRoot ?? process.cwd()); 14 const specDir = options.specDir ?? "spec"; 15 const specRoot = path.resolve(projectRoot, specDir); 16 17 const entries = await fg("**/*.md", { 18 cwd: specRoot, 19 dot: false, 20 onlyFiles: true, 21 unique: true, 22 }); 23 24 const documents = await Promise.all( 25 entries.sort().map(async (relativePath) => { 26 const absolutePath = path.join(specRoot, relativePath); 27 const [raw, stat] = await Promise.all([fs.readFile(absolutePath, "utf8"), fs.stat(absolutePath)]); 28 return parseSpecMarkdown({ 29 absolutePath, 30 relativePath, 31 raw, 32 mtimeMs: stat.mtimeMs, 33 }); 34 }), 35 ); 36 37 return documents.sort((left, right) => left.path.localeCompare(right.path)); 38} 39 40export async function buildImportPlan(options: ScanSpecOptions = {}): Promise<SpecImportPlan> { 41 const projectRoot = path.resolve(options.projectRoot ?? process.cwd()); 42 const specRoot = path.resolve(projectRoot, options.specDir ?? "spec"); 43 const generatedAt = new Date().toISOString(); 44 const documents = await scanSpecDocuments({ 45 projectRoot, 46 ...(options.specDir === undefined ? {} : { specDir: options.specDir }), 47 }); 48 const rows = documents.map((doc) => documentToRow(doc, generatedAt)); 49 50 return { 51 projectRoot, 52 specRoot, 53 generatedAt, 54 documents, 55 rows, 56 events: [], 57 }; 58} 59 60export function documentToRow(document: SpecMarkdownDocument, importedAt = new Date().toISOString()): SpecDocumentRow { 61 return { 62 id: document.id, 63 path: document.path, 64 title: document.title, 65 kind: document.kind, 66 status: document.status ?? "draft", 67 body: document.body, 68 frontmatterJson: JSON.stringify(document.frontmatter), 69 headingsJson: JSON.stringify(document.headings), 70 sha256: document.sha256, 71 sizeBytes: document.sizeBytes, 72 mtimeMs: document.mtimeMs, 73 importedAt, 74 }; 75}