@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.
364 lines • 13.4 kB
TypeScript
/**
* Core shared types for goat-flow.
*/
/** Canonical supported AI coding agent identifiers in stable display order. */
export declare const KNOWN_AGENT_IDS: readonly ["claude", "codex", "antigravity", "copilot"];
/** Supported AI coding agent identifiers derived from the canonical tuple. */
export type AgentId = (typeof KNOWN_AGENT_IDS)[number];
/** Prompt invocation syntax an agent expects for goat-flow skills. */
type PromptInvocationStyle = "slash" | "dollar";
/** Skill mirror/source classification used by quality inventory. */
type SkillSource = "installed" | "agent-mirror" | "github-mirror";
/**
* Describes an agent's file layout and enforcement mechanisms.
* One profile per supported agent (Claude, Codex, Antigravity, Copilot).
*
* `denyMechanism` and `hookEvents` are nullable to model agents whose upstream
* runtime has no project-local hook wiring for a given capability; consumers
* MUST guard for the null case.
*/
export interface AgentProfile {
id: AgentId;
name: string;
instructionFile: string;
terminalBinary: string;
setupSurfaces: string[];
promptInvocationStyle: PromptInvocationStyle;
skillSource: SkillSource;
supportsPostTurnHook: boolean;
settingsFile: string | null;
hookConfigFile: string | null;
skillsDir: string;
hooksDir: string | null;
denyMechanism: DenyMechanism | null;
denyHookFile: string | null;
localPattern: string;
hookEvents: HookEvents | null;
}
/**
* Discriminated union for how an agent enforces command denials.
* Agents may use settings-based deny, a deny script, or both.
*/
export type DenyMechanism = {
type: "settings-deny";
path: string;
} | {
type: "deny-script";
path: string;
} | {
type: "both";
settingsPath: string;
scriptPath: string;
};
/** Hook event file names specific to each agent runtime */
interface HookEvents {
preTool: string;
postTurn: string | null;
}
/** Top-level fact container gathered by the fact extractors */
export interface ProjectFacts {
root: string;
stack: StackInfo;
agents: AgentFacts[];
shared: SharedFacts;
}
/** Detected build toolchain for the target project */
export interface StackInfo {
languages: string[];
buildCommand: string | null;
testCommand: string | null;
lintCommand: string | null;
formatCommand: string | null;
/** Approximate count of source files (excludes node_modules, vendor, dist, .git) */
sourceFileCount: number;
/** Extended project signals detected during setup-time analysis */
signals: ProjectSignals;
}
/** Extended detection signals for richer setup prompts. */
export interface ProjectSignals {
/** Code generation tools found (sqlc, Hygen, protobuf, openapi-generator) */
codeGenTools: string[];
/** Deployment/infrastructure platforms found (amplify, terraform, docker, fly, vercel) */
deployPlatforms: string[];
/** LLM integration signals (model provider env vars, SDK imports) */
llmIntegration: boolean;
/** Static analysis tools with detected strictness level */
staticAnalysis: Array<{
tool: string;
level: string | null;
}>;
/** PHI/compliance keywords detected in docs or instructions */
complianceSignals: boolean;
/** Formatter coverage: languages with detected formatters vs languages without */
formatterGaps: string[];
}
/** Per-bucket learning-loop freshness + health record used by `goat-flow stats`. */
export interface BucketFreshness {
/** Relative path of the bucket file */
path: string;
/** `last_reviewed` date from frontmatter in YYYY-MM-DD form, or null if missing/invalid */
lastReviewed: string | null;
/** Whole days between last_reviewed and "now"; null when lastReviewed is unknown */
freshnessDays: number | null;
/** Freshness band: <=30d fresh, 31-90d aging, >90d stale, unknown if no valid date */
freshnessBand: "fresh" | "aging" | "stale" | "unknown";
/** Entries counted live in this bucket (## Footgun/Lesson/Pattern headings) */
entryCount: number;
/** Stale file refs found in this bucket */
staleRefs: string[];
/** Invalid line refs (line out of bounds or missing semantic anchor) found in this bucket */
invalidLineRefs: string[];
/** Most recent `**Created:**` or `**Updated:**` date in the body, YYYY-MM-DD or null.
* Used to detect frontmatter `last_reviewed` that is stale relative to entry dates. */
maxEntryDate: string | null;
/** File content size in bytes. Used by `goat-flow stats --check` for bucket-size warnings. */
sizeBytes: number;
/** Total line count of the bucket file. */
lineCount: number;
}
/** Learning-loop artifact kinds in the order the retrieval and stats pipelines understand. */
export type LearningLoopEntryKind = "footgun" | "lesson" | "pattern" | "decision";
/** Compact parsed learning-loop entry used by bounded prompt retrieval. */
export interface LearningLoopEntryFact {
sourcePath: string;
kind: LearningLoopEntryKind;
title: string;
status: "active" | "resolved" | null;
created: string | null;
updated: string | null;
resolved: string | null;
excerpt: string;
staleRefs: string[];
invalidLineRefs: string[];
hasValidAnchor: boolean;
bucketSizeBytes: number;
order: number;
}
/** Stable project-wide fact schema shared by audits, stats, setup prompts, and dashboard APIs. */
export interface SharedFacts {
footguns: {
exists: boolean;
hasEvidence: boolean;
entryCount: number;
labelCount: number;
hasEvidenceLabels: boolean;
dirMentions: Map<string, number>;
staleRefs: string[];
invalidLineRefs: string[];
duplicateSurfacePaths: string[];
totalRefs: number;
validRefs: number;
formatDiagnostic: string | null;
path: string;
/** Per-bucket freshness records; empty when the directory is missing. */
buckets: BucketFreshness[];
};
lessons: {
exists: boolean;
hasEntries: boolean;
entryCount: number;
staleRefs: string[];
invalidLineRefs: string[];
duplicateSurfacePaths: string[];
formatDiagnostic: string | null;
path: string;
/** Per-bucket freshness records; empty when the directory is missing. */
buckets: BucketFreshness[];
};
decisions: {
dirExists: boolean;
fileCount: number;
path: string;
hasRealContent: boolean;
};
config: {
exists: boolean;
valid: boolean;
warningCount: number;
errorCount: number;
parseError: string | null;
lineLimits: {
target: number;
limit: number;
};
userRole: "developer" | "investigator" | "tester";
};
architecture: {
exists: boolean;
lineCount: number;
};
ignoreFiles: {
copilotignore: boolean;
cursorignore: boolean;
};
gitignore: {
exists: boolean;
hasRequiredEntries: boolean;
};
preflightScript: {
exists: boolean;
};
skillConventions: {
exists: boolean;
};
localInstructions: {
dirExists: boolean;
location: "ai" | "github" | null;
aiDirExists: boolean;
githubDirExists: boolean;
duplicateSurfacePaths: string[];
fileCount: number;
hasRouter: boolean;
hasValidRouter: boolean;
routerNeedsFix: string | null;
hasConventions: boolean;
conventionsHasContent: boolean;
hasFrontend: boolean;
hasBackend: boolean;
hasCodeReview: boolean;
hasGitCommit: boolean;
conventionsContent: string | null;
localFileSizes: Array<{
path: string;
lines: number;
}>;
path: string;
};
gitCommitInstructions: {
exists: boolean;
path: string | null;
requiredPath: string;
misplacedPaths: string[];
};
/** Total line count across canonical local-instruction files. */
localInstructionsLineCount: number;
/** Parsed entries for deterministic, bounded prompt retrieval. */
learningLoopEntries: LearningLoopEntryFact[];
}
/** Per-agent facts gathered from instruction files, settings, skills, and hooks */
export interface AgentFacts {
agent: AgentProfile;
instruction: {
exists: boolean;
content: string | null;
lineCount: number;
sections: Map<string, string>;
};
settings: {
exists: boolean;
valid: boolean;
parsed: unknown;
hasDenyPatterns: boolean;
};
skills: {
/** All skill directories present under the agent's skills dir that contain a SKILL.md file */
installedDirs: string[];
found: string[];
missing: string[];
allPresent: boolean;
/** Map from skill name to its embedded goat-flow-skill-version (null if missing) */
versions: Record<string, string | null>;
/** Number of skills with a version older than the current SKILL_VERSION */
outdatedCount: number;
/** Whether the goat dispatcher skill is installed */
hasDispatcher: boolean;
quality: {
withStep0: number;
withHumanGate: number;
withConstraints: number;
withPhases: number;
withConversational: number;
withChoices: number;
withOutputFormat: number;
withSharedConventions: number;
/** Number of malformed (unclosed) markdown fence blocks across all skill files */
malformedFenceCount: number;
/** Skills where Step 0 Jaccard similarity to template > 0.9 (unadapted) */
unadaptedCount: number;
/** Total remaining <!-- ADAPT: --> comments across all skill files */
adaptCommentCount: number;
total: number;
};
};
hooks: {
denyExists: boolean;
denyHasBlocks: boolean;
/** True when deny is via settings.json patterns (not a shell script). jq/chaining checks are N/A for config-based deny. */
denyIsConfigBased: boolean;
denyUsesJq: boolean;
denyHandlesChaining: boolean;
denyBlocksRmRf: boolean;
denyBlocksGitPush: boolean;
denyBlocksChmod: boolean;
denyBlocksPipeToShell: boolean;
denyBlocksCloudDestructive: boolean;
/** True when the deny hook is registered as a pre-tool-use hook in agent settings */
denyIsRegistered: boolean;
denyRegisteredPath: string | null;
postTurnExists: boolean;
postTurnRegistered: boolean;
postTurnRegisteredPath: string | null;
postTurnExecutable: boolean;
postTurnExitsZero: boolean;
postTurnHasValidation: boolean;
postTurnSwallowsFailures: boolean;
/** Hook scripts containing hardcoded absolute paths (not wrapped in $(git rev-parse)) */
absolutePathHooks: string[];
readDenyCoversSecrets: boolean;
/** True when the Bash deny hook blocks direct literal secret-bearing paths
* (.env, /.ssh/, /.aws/, .pem/.key/.pfx). Settings/Codex permission
* file-read denies do not cover Bash commands, so this is direct-path
* defence in depth. */
bashDenyCoversSecrets: boolean;
};
deny: {
gitCommitBlocked: boolean;
gitPushBlocked: boolean;
};
router: {
exists: boolean;
paths: string[];
resolved: number;
unresolved: string[];
};
localContext: {
files: string[];
warranted: string[];
missing: string[];
};
}
/**
* Stable read-only filesystem schema for the scan engine.
* Allows swapping real FS for in-memory FS during testing while preserving non-throwing read semantics.
*/
export interface ReadonlyFS {
/** Return path existence; implementations should report inaccessible paths as false. */
exists(path: string): boolean;
/** Read UTF-8 text or return null when the file is missing or unreadable. */
readFile(path: string): string | null;
/** Count text lines, returning 0 when the file cannot be read. */
lineCount(path: string): number;
/** Parse JSON defensively, returning null for missing, unreadable, or malformed files. */
readJson(path: string): unknown;
/** List child names; missing and unreadable directories intentionally return an empty list. */
listDir(path: string): string[];
/** Report whether a file can be executed by the current platform. */
isExecutable(path: string): boolean;
/** Expand goat-flow's small relative glob syntax into matching POSIX-shaped paths. */
glob(pattern: string): string[];
/** Check whether a glob has any match without requiring callers to materialize every path. */
existsGlob(pattern: string): boolean;
}
/** Parsed command-line arguments for the goat-flow CLI */
export interface CLIOptions {
projectPath: string;
format: "json" | "text" | "markdown" | "sarif";
agent: AgentId | null;
isVerbose: boolean;
output: string | null;
isDevMode: boolean;
showHelp: boolean;
showVersion: boolean;
}
export {};
//# sourceMappingURL=types.d.ts.map