@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.
165 lines • 8.48 kB
JavaScript
/**
* Loads and schema-validates the shipped detector tables in
* workflow/project-stack-data.json, then re-exports each table as a typed
* constant for the project-stack detector and setup signals.
*
* Loaded eagerly at module import: a malformed shipped table throws during
* startup rather than producing silently wrong detection. The exported
* PROJECT_STACK_* constants are the only supported way to read these rows;
* consumers must not re-parse the JSON.
*/
import { readFileSync } from "node:fs";
import { getTemplatePath } from "../paths.js";
/** Relative path to the shipped project-stack data tables. */
const PROJECT_STACK_DATA_PATH = "workflow/project-stack-data.json";
/** Treat arrays as invalid records because every shipped data row uses named fields. */
function isRecord(candidate) {
return (typeof candidate === "object" &&
candidate !== null &&
!Array.isArray(candidate));
}
/** Read a string array from shipped data; throws with the table label on schema drift. */
function readStringArray(rawValue, label) {
if (!Array.isArray(rawValue) ||
rawValue.some((entry) => typeof entry !== "string")) {
throw new Error(`${PROJECT_STACK_DATA_PATH} has an invalid ${label} array`);
}
return [...rawValue];
}
/** Read language/path/glob rows; throws with row indexes so bad shipped data is fixable. */
function readLanguageSignals(value, label) {
if (!Array.isArray(value)) {
throw new Error(`${PROJECT_STACK_DATA_PATH} has an invalid ${label} list`);
}
return value.map((entry, index) => {
if (!isRecord(entry) || typeof entry.language !== "string") {
throw new Error(`${PROJECT_STACK_DATA_PATH} has an invalid ${label}[${index}] entry`);
}
return {
language: entry.language,
paths: readStringArray(entry.paths, `${label}[${index}].paths`),
globs: readStringArray(entry.globs, `${label}[${index}].globs`),
};
});
}
/** Read tool/path/glob rows; throws before detector startup can use malformed data. */
function readToolSignals(rawValue, label) {
if (!Array.isArray(rawValue)) {
throw new Error(`${PROJECT_STACK_DATA_PATH} has an invalid ${label} list`);
}
return rawValue.map((entry, index) => {
if (!isRecord(entry) || typeof entry.tool !== "string") {
throw new Error(`${PROJECT_STACK_DATA_PATH} has an invalid ${label}[${index}] entry`);
}
return {
tool: entry.tool,
paths: readStringArray(entry.paths, `${label}[${index}].paths`),
globs: readStringArray(entry.globs, `${label}[${index}].globs`),
};
});
}
/** Read setup-framework marker rows from the project-stack data JSON. */
function readSetupFrameworkMarkers(value, label) {
if (!Array.isArray(value)) {
throw new Error(`${PROJECT_STACK_DATA_PATH} has an invalid ${label} list`);
}
return value.map((entry, index) => {
if (!isRecord(entry) || typeof entry.name !== "string") {
throw new Error(`${PROJECT_STACK_DATA_PATH} has an invalid ${label}[${index}] entry`);
}
return {
name: entry.name,
files: readStringArray(entry.files, `${label}[${index}].files`),
markers: readStringArray(entry.markers, `${label}[${index}].markers`),
};
});
}
/** Read Node framework rows from the project-stack data JSON. */
function readNodeFrameworkSignals(value, label) {
if (!Array.isArray(value)) {
throw new Error(`${PROJECT_STACK_DATA_PATH} has an invalid ${label} list`);
}
return value.map((entry, index) => {
if (!isRecord(entry) || typeof entry.language !== "string") {
throw new Error(`${PROJECT_STACK_DATA_PATH} has an invalid ${label}[${index}] entry`);
}
return {
language: entry.language,
packages: readStringArray(entry.packages, `${label}[${index}].packages`),
};
});
}
/** Read Node test framework rows from the project-stack data JSON. */
function readNodeTestFrameworkSignals(value, label) {
if (!Array.isArray(value)) {
throw new Error(`${PROJECT_STACK_DATA_PATH} has an invalid ${label} list`);
}
return value.map((entry, index) => {
if (!isRecord(entry) || typeof entry.name !== "string") {
throw new Error(`${PROJECT_STACK_DATA_PATH} has an invalid ${label}[${index}] entry`);
}
return {
name: entry.name,
packages: readStringArray(entry.packages, `${label}[${index}].packages`),
};
});
}
/** Read formatter mappings; throws if a language maps to anything other than strings. */
function readFormatterMap(rawValue) {
if (!isRecord(rawValue)) {
throw new Error(`${PROJECT_STACK_DATA_PATH} has an invalid formatterMap`);
}
return Object.fromEntries(Object.entries(rawValue).map(([language, formatters]) => [
language,
readStringArray(formatters, `formatterMap.${language}`),
]));
}
/** Load shipped detector tables once; throws during startup when the JSON schema drifts. */
function loadProjectStackData() {
const path = getTemplatePath(PROJECT_STACK_DATA_PATH);
const raw = JSON.parse(readFileSync(path, "utf-8"));
if (!isRecord(raw)) {
throw new Error(`${PROJECT_STACK_DATA_PATH} must contain a JSON object`);
}
return {
nodeFrameworks: readNodeFrameworkSignals(raw.nodeFrameworks, "nodeFrameworks"),
nodeTestFrameworks: readNodeTestFrameworkSignals(raw.nodeTestFrameworks, "nodeTestFrameworks"),
extraLanguageSignals: readLanguageSignals(raw.extraLanguageSignals, "extraLanguageSignals"),
codeGenSignals: readToolSignals(raw.codeGenSignals, "codeGenSignals"),
deploySignals: readToolSignals(raw.deploySignals, "deploySignals"),
setupFrameworkMarkers: readSetupFrameworkMarkers(raw.setupFrameworkMarkers, "setupFrameworkMarkers"),
rootPythonFiles: readStringArray(raw.rootPythonFiles, "rootPythonFiles"),
subdirPythonGlobs: readStringArray(raw.subdirPythonGlobs, "subdirPythonGlobs"),
javaManifestPaths: readStringArray(raw.javaManifestPaths, "javaManifestPaths"),
llmEnvFiles: readStringArray(raw.llmEnvFiles, "llmEnvFiles"),
llmDepFiles: readStringArray(raw.llmDepFiles, "llmDepFiles"),
complianceDocs: readStringArray(raw.complianceDocs, "complianceDocs"),
formatterMap: readFormatterMap(raw.formatterMap),
};
}
const PROJECT_STACK_DATA = loadProjectStackData();
/** Node.js framework indicators matched against package dependencies. */
export const PROJECT_STACK_NODE_FRAMEWORKS = PROJECT_STACK_DATA.nodeFrameworks;
/** Additional language/template indicators beyond primary manifest detection. */
export const PROJECT_STACK_EXTRA_LANGUAGE_SIGNALS = PROJECT_STACK_DATA.extraLanguageSignals;
/** Code generation tool indicators detected from config files. */
export const PROJECT_STACK_CODE_GENERATION_SIGNALS = PROJECT_STACK_DATA.codeGenSignals;
/** Deployment platform indicators detected from config files. */
export const PROJECT_STACK_DEPLOYMENT_SIGNALS = PROJECT_STACK_DATA.deploySignals;
/** Extra framework markers used only for setup-view framework display names. */
export const PROJECT_STACK_SETUP_FRAMEWORK_MARKERS = PROJECT_STACK_DATA.setupFrameworkMarkers;
/** Root-level files that indicate a Python project. */
export const PROJECT_STACK_ROOT_PYTHON_FILES = PROJECT_STACK_DATA.rootPythonFiles;
/** Glob patterns for detecting Python projects in subdirectories. */
export const PROJECT_STACK_SUBDIRECTORY_PYTHON_GLOBS = PROJECT_STACK_DATA.subdirPythonGlobs;
/** Build manifest paths read to detect Java framework dependencies. */
export const PROJECT_STACK_JAVA_MANIFEST_PATHS = PROJECT_STACK_DATA.javaManifestPaths;
/** Environment files checked for LLM provider API key variables. */
export const PROJECT_STACK_LLM_ENV_FILES = PROJECT_STACK_DATA.llmEnvFiles;
/** Dependency files checked for LLM SDK references. */
export const PROJECT_STACK_LLM_DEPENDENCY_FILES = PROJECT_STACK_DATA.llmDepFiles;
/** Files checked for compliance-related keywords (HIPAA, GDPR, etc.). */
export const PROJECT_STACK_COMPLIANCE_DOCS = PROJECT_STACK_DATA.complianceDocs;
/** Maps languages to their known formatter tool names for gap detection. */
export const PROJECT_STACK_FORMATTER_MAP = PROJECT_STACK_DATA.formatterMap;
//# sourceMappingURL=project-stack-data.js.map