/** * Unified, detectTemplate-driven discovery (prototype for #391). * * Today `commands/audit.ts` ships six bespoke discoverers — `discoverManifests`, * `discoverGcp`, `discoverCloudFormation`, `discoverArm`, `discoverDocker`, * `discoverHelm` — each walking the tree and re-implementing a `looksLike*` * content heuristic that mostly duplicates the lexicon plugin's own * `detectTemplate(parsed)` (already used by `chant import`). * * This module collapses that into ONE walk plus a single content-detection * pass that delegates to each plugin's `detectTemplate`. A new content-detected * lexicon then needs only a `detectTemplate` and a precedence entry — no new * walk, no new heuristic. * * It is deliberately a HYBRID, because some classification can't be expressed * by content shape alone: * - CI (github/forgejo/gitlab) stays a PATH fast-path. github and forgejo * workflows are byte-for-byte the same shape (`on:` + `jobs:`), so only the * directory (`.github` vs `.forgejo`) disambiguates them. * - Dockerfiles are matched by FILENAME — they aren't YAML/JSON, so * `detectTemplate` (which only knows Compose's `services:`) can't see them. * - Helm charts are a directory BUNDLE keyed by `Chart.yaml`; the helm checks * read `output.files`, so the whole chart is one AuditInput. * - k8s `detectTemplate` matches any `apiVersion`+`kind`, including GCP Config * Connector (`cnrm.cloud.google.com`) resources, so gcp must be tried first. * - aws/azure checks `JSON.parse` their input, so YAML/templated content is * normalized to a JSON string here. */ import type { LexiconPlugin } from "../lexicon.js"; import type { AuditInput, AuditLexicon } from "./core.js"; /** Lexicons the auditor knows how to detect and run checks for. */ export declare const AUDIT_LEXICONS: readonly ["github", "gitlab", "forgejo", "k8s", "docker", "aws", "azure", "gcp", "helm"]; /** * Load the plugins used for detection. Each is loaded in isolation (no * cross-lexicon conflict aggregation — detection only reads `detectTemplate`), * and a lexicon that isn't installed is skipped rather than failing the audit. * `init()` is intentionally not called: `detectTemplate` is a pure function of * its input and needs no plugin setup. */ export declare function loadAuditPlugins(names?: readonly string[]): Promise; /** A repo file (relative POSIX path + content) — the unit both local and remote feed the classifier. */ export interface RepoFile { path: string; content: string; } /** * The minimal shape `classifyFiles` needs from a lexicon: its name and (for * content-detected lexicons) its `detectTemplate`. Narrower than `LexiconPlugin` * so an edge caller can build detectors from static `@…/detect` imports without * loading the full plugin (which drags the TypeScript compiler — see #426). */ export interface DetectPlugin { name: string; detectTemplate?: (data: unknown) => boolean; } /** * Whether a path is worth reading/fetching at all. Bounds the local walk and, * crucially, the remote fetch (so we never pull a whole repo's contents). */ export declare function isCandidatePath(path: string): boolean; /** * What a candidate file looks like it wants, judged cheaply by core alone * (path, filename, a few content markers) with no lexicon plugin involved. * Names the lexicon that would have claimed a file when that lexicon isn't * installed (#1623). Deliberately coarse: precision comes from the plugin's * `detectTemplate`, which is exactly what's missing in that case. * `terraform` is not an audit lexicon; `*.tf` is surfaced so the user learns * the audit never reads it (see `chant carve`). */ export type LexiconHint = AuditLexicon | "terraform"; export declare function hintLexiconForFile(path: string, content: string): LexiconHint | undefined; /** A candidate file that looked like it belonged to a lexicon the audit did not have. */ export interface UnclaimedFile { path: string; lexicon: LexiconHint; } /** * Files no loaded lexicon claimed, paired with the lexicon core guesses they * wanted. Only files whose guessed lexicon is absent are reported; a file an * installed lexicon declined is that plugin's call, not a coverage gap. */ export declare function unclaimedFiles(files: RepoFile[], inputs: AuditInput[], plugins: DetectPlugin[]): UnclaimedFile[]; /** * Classify an in-memory set of repo files into per-lexicon audit inputs. Pure * (no fs, no network) so the local walk and the remote tree-fetch share exactly * one detection path. Each file maps to at most one lexicon: CI by path, * Dockerfiles by name, Helm charts as a bundle, everything else by the * precedence-ordered content detectors (delegating to each plugin's * `detectTemplate`). Lexicons whose plugin isn't provided are skipped. */ export declare function classifyFiles(files: RepoFile[], plugins: DetectPlugin[]): AuditInput[]; /** * Local discovery: one filesystem walk, candidate-filtered, read into memory, * then handed to the shared `classifyFiles`. Detection is skipped for any * lexicon whose plugin isn't provided, so a caller can scope discovery. */ export declare function discoverByDetection(root: string, plugins: DetectPlugin[]): AuditInput[]; /** The walk half of `discoverByDetection`: candidate files read into memory, paths relative to the root. */ export declare function collectCandidates(root: string): RepoFile[]; //# sourceMappingURL=discover.d.ts.map