@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.
111 lines • 5.1 kB
JavaScript
/**
* Provenance helpers for audit checks. Two jobs live here: classifying a check's evidence paths into
* framework-truth versus target-project buckets (so renderers can label where evidence came from),
* and validating, once per process, that every registered check's provenance still resolves on disk.
* The framework/target split exists because goat-flow audits a target project but its own docs are
* the canonical truth for some checks; the two must not be conflated in evidence output.
*/
import { existsSync } from "node:fs";
import { getTemplatePath, isPackagedInstall } from "../paths.js";
import { AGENT_CHECKS } from "./check-agent-setup.js";
import { SETUP_CHECKS } from "./check-goat-flow.js";
import { HARNESS_CHECKS } from "./harness/index.js";
import { validateProvenance } from "./provenance-types.js";
const FRAMEWORK_EVIDENCE_PREFIXES = [
"workflow/",
"docs/",
".goat-flow/learning-loop/footguns/",
".goat-flow/learning-loop/lessons/",
".goat-flow/learning-loop/decisions/",
".goat-flow/skill-docs/",
".goat-flow/skill-docs/playbooks/",
];
const FRAMEWORK_EVIDENCE_PATHS = new Set([
"README.md",
".goat-flow/architecture.md",
".goat-flow/code-map.md",
".goat-flow/glossary.md",
]);
/** Classify evidence paths that describe goat-flow framework truth rather than target-project files. */
function isFrameworkEvidencePath(path) {
return (FRAMEWORK_EVIDENCE_PATHS.has(path) ||
FRAMEWORK_EVIDENCE_PREFIXES.some((prefix) => path.startsWith(prefix)));
}
/** Deduplicate strings while preserving order for stable evidence output. */
function unique(values) {
return Array.from(new Set(values));
}
/**
* Add explicit framework/target path-base labels to a check's evidence while preserving the legacy
* flat `evidence_paths`. Each path is classified by prefix and folded into `framework_evidence_paths`
* or `target_evidence_paths`; duplicates are dropped but original order is preserved. Returns the
* input untouched when there are no evidence paths, so callers can apply it unconditionally.
*
* @param provenance - a check's evidence record; its `evidence_paths` are split, existing labelled
* arrays are merged with the newly classified paths rather than overwritten
* @returns the same evidence with framework/target arrays added; the original object when no paths exist
*/
export function labelEvidencePathBases(provenance) {
const paths = provenance.evidence_paths ?? [];
if (paths.length === 0)
return provenance;
const frameworkPaths = paths.filter(isFrameworkEvidencePath);
const targetPaths = paths.filter((path) => !isFrameworkEvidencePath(path));
return {
...provenance,
...(frameworkPaths.length > 0
? {
framework_evidence_paths: unique([
...(provenance.framework_evidence_paths ?? []),
...frameworkPaths,
]),
}
: {}),
...(targetPaths.length > 0
? {
target_evidence_paths: unique([
...(provenance.target_evidence_paths ?? []),
...targetPaths,
]),
}
: {}),
};
}
/** Process-level guard so the cross-check validation below runs at most once per process. */
let provenanceValidated = false;
/**
* Validate provenance on every registered check against the target project or package root.
*
* In packaged installs, `evidence_paths` pointing at framework-repo docs
* (`.goat-flow/learning-loop/footguns/*`, `.goat-flow/learning-loop/lessons/*`, `docs/*`) can't be
* resolved because those files aren't in `package.json` `files`. Skip the
* existence check there - the paths are human-readable pointers for future
* maintainers, not runtime contracts. In dev mode we keep the check so
* stale provenance surfaces in preflight.
*
* Throws an Error listing every offending `checkId: reason` when any registered check's provenance
* is invalid (in dev, that includes evidence paths that resolve neither in the target FS nor in the
* template root). Runs at most once per process and is a no-op on subsequent calls.
*
* @param fs - readonly FS for the target project; used in dev mode to existence-test evidence paths
* before falling back to the packaged template root, and ignored entirely in packaged installs
*/
export function validateRegisteredCheckProvenance(fs) {
if (provenanceValidated)
return;
const checks = [...SETUP_CHECKS, ...AGENT_CHECKS, ...HARNESS_CHECKS];
const errors = [];
const pathExists = isPackagedInstall()
? undefined
: (p) => fs.exists(p) || existsSync(getTemplatePath(p));
for (const check of checks) {
for (const error of validateProvenance(check.provenance, pathExists)) {
errors.push(`${check.id}: ${error}`);
}
}
if (errors.length > 0) {
throw new Error(`Invalid audit check provenance:\n- ${errors.join("\n- ")}`);
}
provenanceValidated = true;
}
//# sourceMappingURL=audit-provenance.js.map