@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.
86 lines • 3.5 kB
JavaScript
import { extractSection } from "./instruction.js";
/** Return true if a string contains '/' or '.', suggesting a file path. */
function looksLikePath(value) {
return value.includes("/") || value.includes(".");
}
/** Return true if a string contains glob or template characters. */
function hasGlobChars(value) {
return value.includes("*") || value.includes("{");
}
/**
* Add a discovered router path once without duplicating earlier matches.
*
* @param paths - mutable path accumulator for one instruction file
* @param path - router path candidate to append when not already present
*/
export function pushUniquePath(paths, path) {
if (paths.includes(path) === false) {
paths.push(path);
}
}
/** Collect paths matching a regex pattern, filtered by a validation predicate. */
function collectMatchedPaths(content, pattern, isValid, paths) {
for (const match of content.matchAll(pattern)) {
const path = match[1];
if (path === undefined)
continue;
if (!isValid(path))
continue;
pushUniquePath(paths, path);
}
}
/** Treat backtick-wrapped router references as paths only when they look local and literal. */
function isRouterBacktickPath(path) {
return !hasGlobChars(path) && looksLikePath(path);
}
/** Treat markdown link targets as router paths only when they look local and literal. */
function isRouterLinkPath(path) {
return !hasGlobChars(path) && !path.startsWith("http") && looksLikePath(path);
}
/** Extract all file/directory paths referenced in the Router Table section. */
function extractRouterPaths(content) {
/** Accumulated list of discovered router paths */
const paths = [];
/** Content of the router section extracted from the instruction file */
const routerSection = extractSection(content, "router");
if (routerSection == null)
return paths;
collectMatchedPaths(routerSection, /`([^`]+)`/g, isRouterBacktickPath, paths);
collectMatchedPaths(routerSection, /\]\(([^)]+)\)/g, isRouterLinkPath, paths);
return paths;
}
/** Resolve referenced paths against the filesystem, counting hits and misses. */
function resolveReferencedPaths(fs, paths) {
let resolved = 0;
const unresolved = [];
for (const path of paths) {
if (fs.exists(path)) {
resolved++;
}
else {
unresolved.push(path);
}
}
return { resolved, unresolved };
}
/**
* Extract router table facts: paths found and their resolution status.
*
* @param fs - project filesystem adapter used to verify referenced paths
* @param content - instruction file content, or null when the file is absent
* @returns router table presence plus resolved/unresolved path counts
*/
export function extractRouterFacts(fs, content) {
const paths = content !== null ? extractRouterPaths(content) : [];
const resolution = resolveReferencedPaths(fs, paths);
return {
exists: paths.length > 0,
paths,
resolved: resolution.resolved,
unresolved: resolution.unresolved,
};
}
// settingsLocal extraction removed - personal preference file, not a project quality signal.
// askFirst extraction removed - ask_first config field removed from CLI (ADR-014).
// ─── Composer ────────────────────────────────────────────────────────
//# sourceMappingURL=routing.js.map