UNPKG

@blundergoat/goat-flow

Version:

AI coding agent harness and local dashboard for Claude Code, OpenAI Codex, Google Antigravity, and GitHub Copilot - setup audits, guardrails, structured skills, deny hooks, and persistent learning loops.

191 lines 7.59 kB
const GITHUB_INSTRUCTIONS_DIR = ".github/instructions"; /** Resolve the active local instruction directory. */ function resolveLocalInstructionDir(githubDirExists) { if (githubDirExists) return { location: "github", dir: GITHUB_INSTRUCTIONS_DIR }; return null; } /** Create the empty local-instructions fact payload. */ function createEmptyLocalInstructions() { return { dirExists: false, location: null, aiDirExists: false, githubDirExists: false, duplicateSurfacePaths: [], fileCount: 0, hasRouter: false, hasValidRouter: false, routerNeedsFix: null, hasConventions: false, conventionsHasContent: false, hasFrontend: false, hasBackend: false, hasCodeReview: false, hasGitCommit: false, conventionsContent: null, localFileSizes: [], path: GITHUB_INSTRUCTIONS_DIR, }; } /** Check whether a basename exists in either supported instruction filename form. */ function hasInstructionFile(files, baseName) { return files.some((file) => file === `${baseName}.md` || file === `${baseName}.instructions.md`); } /** Collect local-instruction feature flags from the discovered files. */ function collectLocalInstructionFlags(files) { return { hasConventions: hasInstructionFile(files, "conventions"), hasFrontend: hasInstructionFile(files, "frontend"), hasBackend: hasInstructionFile(files, "backend"), hasCodeReview: hasInstructionFile(files, "code-review"), hasGitCommit: hasInstructionFile(files, "git-commit"), }; } /** Collect line counts for the discovered local instruction files. */ function collectLocalFileSizes(fs, dir, files) { return files.map((file) => ({ path: `${dir}/${file}`, lines: fs.lineCount(`${dir}/${file}`), })); } /** Check whether a conventions file contains substantial project guidance. */ function hasConventionsContent(content) { const hasCommands = /##.*command|```bash|```sh/i.test(content); const hasConventionRules = /##.*convention|do.*don't|do:.*don't:|good.*bad/i.test(content); const lineCount = content.split("\n").length; return hasCommands && hasConventionRules && lineCount > 15; } /** Check whether a README reference looks like a real repo path. */ function isReadableRouterRef(rawRef) { const ref = rawRef.trim(); if (!ref) return false; if (ref.startsWith("http://") || ref.startsWith("https://")) return false; if (ref.startsWith("$")) return false; if (!ref.includes("/") && /\b(README|docs|command|format|lint)\b/i.test(ref)) return false; if (ref.includes(" ")) return false; return /(?:^\.\/|^\.\.\/|^[\w-]+\/|^[a-zA-Z0-9._-]+\.[a-zA-Z0-9]+$)/.test(ref); } /** Remove any heading anchor from a router reference. */ function stripRouterAnchor(ref) { const anchorIndex = ref.indexOf("#"); if (anchorIndex === -1) return ref.trim(); return ref.slice(0, anchorIndex).trim(); } /** Extract repo-local file references from markdown links and code spans. */ function extractRouterRefsFromMarkdown(content) { const refs = new Set(); for (const match of content.matchAll(/\[[^\]]+\]\(([^)]+)\)/g)) { const raw = match[1]; if (!raw) continue; const ref = stripRouterAnchor(raw); if (isReadableRouterRef(ref)) refs.add(ref); } for (const match of content.matchAll(/`([^`]+)`/g)) { const raw = match[1]; if (!raw) continue; const ref = stripRouterAnchor(raw); if (isReadableRouterRef(ref)) refs.add(ref); } return Array.from(refs); } /** Validate router references against files that actually exist. */ function validateRouterLinks(fs, aiReadmeContent) { if (aiReadmeContent === null) { return { hasValidRouter: false, invalidRefs: [], routerNeedsFix: ".goat-flow/README.md missing - create it and reference existing project guidance if you use this surface", }; } const refs = extractRouterRefsFromMarkdown(aiReadmeContent); if (refs.length === 0) { return { hasValidRouter: false, invalidRefs: [], routerNeedsFix: ".goat-flow/README.md should reference at least one real project guidance file.", }; } const invalidRefs = refs.filter((ref) => !fs.exists(ref)); if (invalidRefs.length > 0) { return { hasValidRouter: false, invalidRefs, routerNeedsFix: `.goat-flow/README.md references missing paths: ${invalidRefs.join(", ")}`, }; } return { hasValidRouter: true, invalidRefs: [], routerNeedsFix: null, }; } /** Read the conventions file and score whether it has real content. */ function analyzeConventionsContent(fs, _location, hasConventions) { if (!hasConventions) { return { conventionsContent: null, conventionsHasContent: false }; } const conventionsPath = `${GITHUB_INSTRUCTIONS_DIR}/conventions.instructions.md`; const conventionsContent = fs.readFile(conventionsPath); return { conventionsContent, conventionsHasContent: conventionsContent !== null && hasConventionsContent(conventionsContent), }; } /** Resolve router-validation facts for the active instruction surface. */ function resolveRouterValidation(fs, location) { const hasRouter = location === "ai" && fs.exists(".goat-flow/README.md"); const routerValidation = location === "ai" ? validateRouterLinks(fs, fs.readFile(".goat-flow/README.md")) : { hasValidRouter: true, routerNeedsFix: null, invalidRefs: [] }; return { hasRouter, routerValidation }; } /** * Extract local-instruction facts from the project instruction surface. * * @param fs - project filesystem adapter used to inspect local instruction files * @returns local instruction presence, flags, router status, and validation details */ export function extractLocalInstructions(fs) { const githubDirExists = fs.exists(GITHUB_INSTRUCTIONS_DIR); const localInstructionDir = resolveLocalInstructionDir(githubDirExists); if (localInstructionDir === null) return createEmptyLocalInstructions(); const files = fs .listDir(localInstructionDir.dir) .filter((file) => file.endsWith(".md")); const flags = collectLocalInstructionFlags(files); const conventions = analyzeConventionsContent(fs, localInstructionDir.location, flags.hasConventions); const { hasRouter, routerValidation } = resolveRouterValidation(fs, localInstructionDir.location); return { dirExists: true, location: localInstructionDir.location, aiDirExists: false, githubDirExists, duplicateSurfacePaths: [], fileCount: files.length, hasRouter, hasValidRouter: routerValidation.hasValidRouter, routerNeedsFix: routerValidation.routerNeedsFix, hasConventions: flags.hasConventions, conventionsHasContent: conventions.conventionsHasContent, hasFrontend: flags.hasFrontend, hasBackend: flags.hasBackend, hasCodeReview: flags.hasCodeReview, hasGitCommit: flags.hasGitCommit, conventionsContent: conventions.conventionsContent, localFileSizes: collectLocalFileSizes(fs, localInstructionDir.dir, files), path: localInstructionDir.dir, }; } //# sourceMappingURL=local-instructions.js.map