/**
 * AUTOGENERATED FILE. DO NOT EDIT.
 *
 * Generated from `docs/output-schema.json` by
 * `editors/vscode/scripts/codegen-types.mjs`. The same file is written to
 * `editors/vscode/src/generated/output-contract.d.ts` (extension internal) and
 * `npm/fallow/types/output-contract.d.ts` (published as `fallow/types`).
 *
 * To change a shape:
 *   1. Edit the Rust struct in `crates/types/src/results.rs` (or the
 *      duplicates crate at `crates/core/src/duplicates/types.rs`). The Rust
 *      side is the runtime source of truth for the JSON output.
 *   2. Update `docs/output-schema.json` to match. The schema is
 *      hand-maintained against the Rust structs; the drift-guard test in
 *      `crates/cli/src/report/json.rs` enforces only a subset of the
 *      contract (the `HealthFindingAction` enum). Field-level drift between
 *      Rust and the schema is currently caught by code review.
 *   3. Run `pnpm --filter fallow-vscode codegen:types` from anywhere.
 *   4. Commit both regenerated files alongside the schema edit.
 *
 * CI runs `pnpm --filter fallow-vscode check:codegen` which fails when
 * either committed file disagrees with what regen would produce.
 */
/* eslint-disable */


/**
 * Schemas for the JSON output of fallow commands. Object-shaped envelopes covered by the `FallowOutput` contract carry a top-level `kind` discriminator (for example `dead-code`, `dead-code-grouped`, `health`, `dupes`, `combined`, `audit`, `explain`, `impact`, `security`, `coverage-setup`, `coverage-analyze`, `list-boundaries`, `review-envelope`, and `review-reconcile`). Consumers should branch on `kind` instead of probing for unique field presence. `--legacy-envelope` removes only the document-root `kind` for one compatibility cycle. `CodeClimateOutput` is a bare JSON array (per the Code Climate / GitLab Code Quality spec) and stays a sibling root branch discriminated by checking whether the document root is an array.
 */
export type FallowJsonOutput = (FallowOutput | CodeClimateOutput)
/**
 * Typed root of every fallow JSON envelope shape that serializes as a JSON
 * object and participates in the documented `FallowOutput` contract. The
 * schema derived from this enum drives the document-root `oneOf` in
 * `docs/output-schema.json`.
 *
 * The default wire shape now carries a top-level `kind` discriminator so
 * agents and schema-validating clients can select the variant in O(1) instead
 * of probing for unique field presence. `--legacy-envelope` is a one-cycle
 * compatibility flag that removes only this document-root `kind` field from
 * CLI JSON output; nested report objects are not rewritten.
 *
 * One envelope is intentionally NOT in this enum:
 * - `CodeClimateOutput` serializes as a bare JSON array
 *   (`#[serde(transparent)]`) per the Code Climate / GitLab Code Quality
 *   spec; `#[serde(tag = ...)]` cannot internally tag a non-object
 *   variant and wrapping the array would break the spec. The root schema
 *   carries it as a sibling `oneOf` branch alongside `FallowOutput`.
 */
export type FallowOutput = ((AuditOutput & {
kind: "audit"
}) | (ExplainOutput & {
kind: "explain"
}) | (ReviewEnvelopeOutput & {
kind: "review-envelope"
}) | (ReviewReconcileOutput & {
kind: "review-reconcile"
}) | (CoverageSetupOutput & {
kind: "coverage-setup"
}) | (CoverageAnalyzeOutput & {
kind: "coverage-analyze"
}) | (ListBoundariesOutput & {
kind: "list-boundaries"
}) | (WorkspacesOutput & {
kind: "list-workspaces"
}) | (HealthOutput & {
kind: "health"
}) | (DupesOutput & {
kind: "dupes"
}) | (CheckGroupedOutput & {
kind: "dead-code-grouped"
}) | (ImpactReport & {
kind: "impact"
}) | (CrossRepoImpactReport & {
kind: "impact-cross-repo"
}) | ((SecurityOutput | SecuritySummaryOutput) & {
kind: "security"
}) | (CheckOutput & {
kind: "dead-code"
}) | (CombinedOutput & {
kind: "combined"
}))
/**
 * Schema version for this output format (independent of tool version). Bump
 * policy: ADDITIVE changes (new optional top-level fields, new optional struct
 * fields, new array entries, new MCP tools, new CLI flags that map to new
 * optional fields) do NOT bump the version; consumers receive new fields
 * without breaking. BREAKING changes (renamed fields, removed fields, type
 * changes, enum-variant removals, semantic changes to existing fields) DO
 * bump. To detect newly-added fields without a bump, check field presence via
 * JSON-key existence rather than gating on the version. v4 was introduced
 * alongside fallow-cov-protocol 0.2 (per-finding verdict, stable IDs, evidence
 * block, renamed summary fields); v5 introduced health_score formula_version 2
 * with scale-invariant scoring semantics; v6 widened `AddToConfigAction.value`
 * from a scalar string to `oneOf: [string, array]` so the new `ignoreExports`
 * action can carry a paste-ready array of `{ file, exports }` rule objects
 * (the legacy `ignoreDependencies` etc. variants still emit strings, so
 * consumers that switch on `config_key` keep working unchanged). The
 * runtime-coverage block is extended additively as the protocol evolves
 * (currently 0.3, which adds an optional capture_quality summary field). Other
 * additive examples: dupes --group-by adds optional grouped_by, total_issues,
 * groups fields without bumping.
 */
export type SchemaVersion = 7
/**
 * Fallow CLI version that produced this envelope. Renders to the JSON wire as
 * a bare string (e.g. `"2.74.0"`).
 */
export type ToolVersion = string
export type AuditCommand = "audit"
/**
 * Verdict for the audit command.
 */
export type AuditVerdict = ("pass" | "warn" | "fail")
/**
 * Analysis duration in milliseconds. Renders to the JSON wire as a bare
 * integer.
 */
export type ElapsedMs = number
export type AuditGate = ("new-only" | "all")
/**
 * A suggested action attached to a finding in the JSON output. Each finding
 * carries an `actions` array; consumers (agents, IDE clients, CI bots) can
 * dispatch on the `type` discriminant to choose the right remediation.
 *
 * The discriminator is `type` (snake_case `type` field), the payload uses the
 * matching kebab-case identifier per variant.
 *
 * ## `auto_fixable` is per-finding, not per action type
 *
 * Every action variant carries an `auto_fixable: bool` field. The value is
 * evaluated PER FINDING, not per action type: the same action type may
 * appear with `auto_fixable: true` on one finding and `auto_fixable: false`
 * on another, depending on per-instance guards in the `fallow fix` applier.
 * Agents that filter on `auto_fixable: true` must branch on the bool of
 * each individual finding's action, not on the action `type` alone.
 *
 * Current per-instance flips:
 *
 * - `remove-catalog-entry` (`unused-catalog-entries`): `true` only when the
 *   finding's `hardcoded_consumers` array is empty. When a workspace
 *   package still pins a hardcoded version of the same package, `fallow fix`
 *   skips the entry to avoid breaking `pnpm install`, and the action is
 *   emitted with `auto_fixable: false`.
 * - `remove-dependency` vs `move-dependency` (dependency findings): when the
 *   finding's `used_in_workspaces` array is non-empty, the primary action
 *   flips to `move-dependency` with `auto_fixable: false` (`fallow fix` will
 *   not remove a dependency that another workspace imports). On findings
 *   without cross-workspace consumers the action stays `remove-dependency`
 *   with `auto_fixable: true`.
 * - `add-to-config` for `ignoreExports` (`duplicate-exports`): `true` when
 *   `fallow fix` can safely apply the action without further user setup.
 *   That is: a fallow config file exists on disk, OR no config exists AND
 *   the working directory is NOT inside a monorepo subpackage (in which
 *   case the applier creates `.fallowrc.json` from `fallow init`'s
 *   framework-aware scaffolding and layers the new rules on top).
 *   `false` inside a monorepo subpackage with no workspace-root config
 *   (the applier refuses to fragment per-package configs across the
 *   monorepo and points at the workspace root instead).
 * - `update-catalog-reference` (`unresolved-catalog-references`): always
 *   `false` today (the catalog-switching applier is not wired in yet); the
 *   field is non-singleton so that future enablement does not require a
 *   schema change.
 *
 * All `suppress-line` and `suppress-file` actions are uniformly
 * `auto_fixable: false`. The field is non-singleton on the wire so that a
 * future auto-applier (e.g. an LLM-driven suppression writer) can promote
 * individual variants without a schema bump.
 */
export type IssueAction = (FixAction | SuppressLineAction | SuppressFileAction | AddToConfigAction)
/**
 * Discriminant string for [`FixAction`]. Kebab-case per the JSON output
 * contract.
 */
export type FixActionType = ("remove-export" | "delete-file" | "remove-dependency" | "move-dependency" | "remove-enum-member" | "remove-class-member" | "resolve-import" | "install-dependency" | "remove-duplicate" | "move-to-dev" | "refactor-cycle" | "refactor-re-export-cycle" | "refactor-boundary" | "export-type" | "remove-catalog-entry" | "remove-empty-catalog-group" | "update-catalog-reference" | "add-catalog-entry" | "remove-catalog-reference" | "remove-dependency-override" | "fix-dependency-override" | "resolve-policy-violation" | "move-to-server-module" | "split-mixed-barrel" | "hoist-directive" | "resolve-route-collision" | "resolve-dynamic-segment-name-conflict")
/**
 * Singleton discriminant for [`SuppressLineAction`].
 */
export type SuppressLineKind = "suppress-line"
/**
 * Scope marker for line suppressions that span multiple locations.
 */
export type SuppressLineScope = "per-location"
/**
 * Singleton discriminant for [`SuppressFileAction`].
 */
export type SuppressFileKind = "suppress-file"
/**
 * Singleton discriminant for [`AddToConfigAction`].
 */
export type AddToConfigKind = "add-to-config"
/**
 * Value payload for [`AddToConfigAction::value`]. The variants line up with
 * the documented per-`config_key` shapes; deserialization is untagged so
 * downstream consumers can switch on the JSON value's type.
 */
export type AddToConfigValue = (string | IgnoreExportsRule[] | {
[k: string]: unknown
})
/**
 * Audit-mode marker emitted on each finding when `fallow audit --format json`
 * runs with a base ref. `true` means the finding's structural key was not
 * present at the base ref (introduced by the current changeset); `false`
 * means it was inherited.
 *
 * Outside of audit sub-results the field is omitted, so call sites typically
 * hold `Option<AuditIntroduced>`. Renders to the JSON wire as a bare boolean.
 */
export type AuditIntroduced = boolean
/**
 * Where in package.json a dependency is listed.
 *
 * # Examples
 *
 * ```
 * use fallow_types::results::DependencyLocation;
 *
 * // All three variants are constructible
 * let loc = DependencyLocation::Dependencies;
 * let dev = DependencyLocation::DevDependencies;
 * let opt = DependencyLocation::OptionalDependencies;
 * // Debug output includes the variant name
 * assert!(format!("{loc:?}").contains("Dependencies"));
 * assert!(format!("{dev:?}").contains("DevDependencies"));
 * assert!(format!("{opt:?}").contains("OptionalDependencies"));
 * ```
 */
export type DependencyLocation = ("dependencies" | "devDependencies" | "optionalDependencies")
/**
 * The kind of member.
 */
export type MemberKind = ("enum_member" | "class_method" | "class_property" | "namespace_member" | "store_member")
/**
 * Discriminator for [`ReExportCycle`]: which structural shape was detected.
 */
export type ReExportCycleKind = ("multi-node" | "self-loop")
/**
 * Which rule-pack rule kind produced a [`PolicyViolation`].
 */
export type PolicyRuleKind = ("banned-call" | "banned-import")
/**
 * Effective severity of a single [`PolicyViolation`]. Per-rule `severity`
 * overrides the `rules."policy-violation"` master; `off` rules emit nothing,
 * so only `error` and `warn` appear on the wire. The exit-code gate inspects
 * this per-finding value, not the master severity.
 */
export type PolicyViolationSeverity = ("error" | "warn")
/**
 * The origin of a stale suppression: inline comment or JSDoc tag.
 */
export type SuppressionOrigin = ({
/**
 * The issue kind token from the comment (e.g., "unused-exports"), or None for blanket.
 */
issue_kind?: (string | null)
/**
 * Whether this was a file-level suppression.
 */
is_file_level: boolean
/**
 * Whether `issue_kind` parses to a known `IssueKind`. False when the
 * token is a typo or refers to a kind that was renamed or removed in
 * a newer fallow release. JSON consumers (CI annotations, MCP agents,
 * VS Code) branch on this to choose the right next-step text.
 * Omitted from the wire when `true` so producers that have not yet
 * adopted the field stay byte-compatible. See issue #449.
 */
kind_known?: boolean
type: "comment"
} | {
/**
 * The name of the export that was tagged.
 */
export_name: string
type: "jsdoc_tag"
})
/**
 * Where an override entry was declared. Serialized as the filename label
 * (`"pnpm-workspace.yaml"` or `"package.json"`) so the value in JSON output
 * matches the value users write in `ignoreDependencyOverrides[].source`.
 */
export type DependencyOverrideSource = ("pnpm-workspace.yaml" | "package.json")
/**
 * Why a dependency-override entry is misconfigured. `pnpm install` would
 * either fail at install time or silently no-op on these entries; surfacing
 * them statically catches the issue before pnpm does.
 */
export type DependencyOverrideMisconfigReason = ("unparsable-key" | "empty-value")
/**
 * Status of a regression-check pass.
 */
export type RegressionStatus = ("pass" | "exceeded" | "skipped")
/**
 * Interpretation of [`RegressionResult::tolerance`].
 */
export type RegressionToleranceKind = ("absolute" | "percentage")
/**
 * A diagnostic about a workspace-discovery candidate.
 *
 * The `message` field is a human-readable rendering derived from `kind`. It
 * always ends with a concrete next step ("fix the JSON syntax", "remove from
 * `workspaces`", "add to `ignorePatterns`") so first-time users have a path
 * forward.
 */
export type WorkspaceDiagnostic = ({
/**
 * Path to the directory or file that triggered the diagnostic.
 */
path: string
/**
 * Human-readable rendering derived from `kind` + `path`. Always ends
 * with a next-step hint.
 */
message: string
} & WorkspaceDiagnostic1)
export type WorkspaceDiagnostic1 = ({
kind: "undeclared-workspace"
} | {
/**
 * `serde_json` parse error text.
 */
error: string
kind: "malformed-package-json"
} | {
/**
 * The glob pattern that matched the directory.
 */
pattern: string
kind: "glob-matched-no-package-json"
} | {
/**
 * JSONC parse error text.
 */
error: string
kind: "malformed-tsconfig"
} | {
kind: "tsconfig-reference-dir-missing"
} | {
/**
 * On-disk size of the skipped file in bytes.
 */
size_bytes: number
kind: "skipped-large-file"
} | {
/**
 * On-disk size of the skipped file in bytes.
 */
size_bytes: number
kind: "skipped-minified-file"
})
/**
 * Discriminant for [`CloneGroupAction::kind`]. Mirrors the action types
 * emitted by the legacy `build_clone_group_actions` walker.
 */
export type CloneGroupActionType = ("extract-shared" | "suppress-line")
/**
 * The kind of refactoring suggested for a clone family.
 */
export type RefactoringKind = ("ExtractFunction" | "ExtractModule")
/**
 * Discriminant for [`CloneFamilyAction::kind`].
 */
export type CloneFamilyActionType = ("extract-shared" | "apply-suggestion" | "suppress-line")
/**
 * Which complexity threshold was exceeded.
 */
export type ExceededThreshold = ("cyclomatic" | "cognitive" | "both" | "crap" | "cyclomatic_crap" | "cognitive_crap" | "all")
/**
 * Severity tier indicating how far a function exceeds complexity thresholds.
 *
 * Determined by the highest tier reached across both cognitive and cyclomatic
 * scores. Default thresholds: cognitive 25/40, cyclomatic 30/50.
 */
export type FindingSeverity = ("moderate" | "high" | "critical")
/**
 * Coverage tier classification for CRAP findings.
 */
export type CoverageTier = ("none" | "partial" | "high")
/**
 * Provenance of a CRAP finding's coverage signal.
 */
export type CoverageSource = ("istanbul" | "estimated" | "estimated_component_inherited")
/**
 * Which complexity metric a [`ComplexityContribution`] adds to.
 */
export type ComplexityMetric = ("cyclomatic" | "cognitive")
/**
 * The syntactic construct that produced a single complexity increment.
 *
 * Mirrors `SonarSource` cognitive-complexity vocabulary where it overlaps.
 * `Case` means a `case` label carrying a test; a bare `default` adds nothing
 * to cyclomatic complexity and so produces no contribution.
 */
export type ComplexityContributionKind = ("if" | "else" | "else-if" | "ternary" | "logical-and" | "logical-or" | "nullish-coalescing" | "logical-assignment" | "optional-chain" | "for" | "for-in" | "for-of" | "while" | "do-while" | "switch" | "case" | "catch" | "labeled-break" | "labeled-continue" | "jsx-depth" | "hook-density" | "prop-count")
/**
 * Source for a finding's effective thresholds.
 */
export type ThresholdSource = "override"
/**
 * Discriminant for [`HealthFindingAction::kind`]. Mirrors the action types
 * emitted by `build_health_finding_actions`. A single finding's `actions`
 * array may carry multiple entries of different types: a finding that
 * exceeded both cyclomatic and CRAP at `coverage_tier: partial` will get
 * BOTH `increase-coverage` AND `refactor-function`, plus the trailing
 * `suppress-line`.
 */
export type HealthFindingActionType = ("refactor-function" | "add-tests" | "increase-coverage" | "suppress-file" | "suppress-line")
/**
 * Coverage model used for CRAP score computation.
 */
export type CoverageModel = ("static_binary" | "static_estimated" | "istanbul")
/**
 * Whether CRAP findings in the report used one coverage-source kind or a mix.
 */
export type CoverageSourceConsistency = ("uniform" | "mixed")
/**
 * Lifecycle state for a configured threshold override.
 */
export type ThresholdOverrideStatus = ("active" | "stale" | "no_match")
/**
 * Discriminant for [`UntestedFileAction::kind`]. Mirrors the action types
 * emitted by `build_untested_file_actions`.
 */
export type UntestedFileActionType = ("add-tests" | "suppress-file")
/**
 * Discriminant for [`UntestedExportAction::kind`]. Mirrors the action
 * types emitted by `build_untested_export_actions`.
 */
export type UntestedExportActionType = ("add-test-import" | "suppress-file")
/**
 * Churn trend indicator based on comparing recent vs older halves of the analysis period.
 */
export type ChurnTrend = ("accelerating" | "stable" | "cooling")
export type ContributorIdentifierFormat = ("raw" | "handle" | "anonymized" | "hash")
export type OwnershipState = ("active" | "unowned" | "declared_inactive" | "drifting")
/**
 * Discriminant for [`HotspotAction::kind`].
 */
export type HotspotActionType = ("refactor-file" | "add-tests" | "low-bus-factor" | "unowned-hotspot" | "ownership-drift")
/**
 * Strategy discriminant for the suggested CODEOWNERS pattern attached to
 * an `unowned-hotspot` action.
 */
export type HotspotActionHeuristic = "directory-deepest"
/**
 * Runtime coverage JSON contract version. This is scoped to the
 * `runtime_coverage` block and is independent of the top-level fallow
 * JSON `schema_version`.
 */
export type RuntimeCoverageSchemaVersion = "1"
/**
 * Top-level verdict for the whole runtime-coverage report. Mirrors
 * `fallow_cov_protocol::ReportVerdict`. The verdict is the SINGLE most
 * actionable finding; for the full set of findings see
 * [`RuntimeCoverageReport::signals`]. The verdict promotes `hot-path-touched`
 * above `cold-code-detected` in PR-review context (when the CLI was
 * given a change-scope: `--diff-file` or `--changed-since`) because the
 * touched-hot-path is event-tied to the current diff and reviewers need
 * it to be the top-line signal. In standalone analysis (no change
 * scope), `cold-code-detected` remains primary.
 */
export type RuntimeCoverageReportVerdict = ("clean" | "hot-path-touched" | "cold-code-detected" | "license-expired-grace" | "unknown")
/**
 * Discrete signal captured during runtime-coverage post-processing.
 * `verdict` collapses to one summary value; `signals` enumerates ALL
 * findings the report carries so JSON consumers, CI dashboards, and
 * agents can reason about them independently of the headline. Order is
 * stable: severity-descending so the first entry mirrors a sensible
 * non-PR-context verdict.
 */
export type RuntimeCoverageSignal = ("license-expired-grace" | "cold-code-detected" | "hot-path-touched")
/**
 * Runtime coverage source used to produce the summary.
 */
export type RuntimeCoverageDataSource = ("local" | "cloud")
/**
 * Protocol-level per-function runtime coverage verdict derived from the
 * decision table in fallow-cov-protocol. The CLI's `runtime_coverage.findings`
 * array omits `active` entries even though the underlying enum still includes
 * it.
 */
export type RuntimeCoverageVerdict = ("safe_to_delete" | "review_required" | "coverage_unavailable" | "low_traffic" | "active" | "unknown")
/**
 * Confidence level for a runtime coverage finding.
 */
export type RuntimeCoverageConfidence = ("very_high" | "high" | "medium" | "low" | "none" | "unknown")
/**
 * Blast-radius risk band. The current thresholds are high at >=20 static
 * callers or >=1,000,000 traffic-weighted caller reach; medium at >=5 callers
 * or >=50,000 weighted reach; low otherwise.
 */
export type RuntimeCoverageRiskBand = ("low" | "medium" | "high")
/**
 * License or trial watermark applied to runtime coverage output.
 */
export type RuntimeCoverageWatermark = ("trial-expired" | "license-expired-grace" | "unknown")
/**
 * Coverage-intelligence JSON contract version. Scoped to the
 * `coverage_intelligence` block and independent of the top-level fallow
 * JSON `schema_version`.
 */
export type CoverageIntelligenceSchemaVersion = "1"
/**
 * Headline verdict for the combined coverage-intelligence report.
 */
export type CoverageIntelligenceVerdict = ("risky-change-detected" | "high-confidence-delete" | "review-required" | "refactor-carefully" | "clean" | "unknown")
/**
 * Ordered evidence signals behind a coverage-intelligence finding.
 */
export type CoverageIntelligenceSignal = ("changed" | "hot_path" | "low_test_coverage" | "high_crap" | "static_unused" | "runtime_cold" | "no_test_path" | "runtime_reachable" | "ownership_drift" | "test_covered")
/**
 * Recommended action family for a combined finding.
 */
export type CoverageIntelligenceRecommendation = ("add-test-or-split-before-merge" | "delete-after-confirming-owner" | "review-before-changing" | "refactor-carefully-keep-behavior")
/**
 * Confidence in the joined evidence and resulting recommendation.
 */
export type CoverageIntelligenceConfidence = ("high" | "medium" | "low")
/**
 * Confidence tier for the cross-surface evidence match.
 */
export type CoverageIntelligenceMatchConfidence = ("path-function-line" | "path-line" | "direct")
/**
 * Category of refactoring recommendation.
 */
export type RecommendationCategory = ("urgent_churn_complexity" | "break_circular_dependency" | "split_high_impact" | "remove_dead_code" | "extract_complex_functions" | "extract_dependencies" | "add_test_coverage")
/**
 * A ranked refactoring recommendation for a file.
 *
 * ## Priority Formula
 *
 * ```text
 * priority = min(density, 1) × 30 + hotspot_boost × 25 + dead_code × 20 + fan_in_norm × 15 + fan_out_norm × 10
 * ```
 *
 * Fan-in and fan-out normalization uses adaptive percentile-based thresholds
 * (p95 of the project distribution, with floors) instead of fixed constants.
 *
 * ## Efficiency (default sort)
 *
 * ```text
 * efficiency = priority / effort_numeric   (Low=1, Medium=2, High=3)
 * ```
 *
 * Surfaces quick wins: high-priority, low-effort targets rank first.
 * Effort estimate for a refactoring target.
 */
export type EffortEstimate = ("low" | "medium" | "high")
/**
 * Confidence level for a refactoring recommendation.
 *
 * Based on the data source reliability:
 * - **High**: deterministic graph/AST analysis (dead code, circular deps, complexity)
 * - **Medium**: heuristic thresholds (fan-in/fan-out coupling)
 * - **Low**: depends on git history quality (churn-based recommendations)
 */
export type Confidence = ("high" | "medium" | "low")
/**
 * Discriminant for [`RefactoringTargetAction::kind`].
 */
export type RefactoringTargetActionType = ("apply-refactoring" | "suppress-line")
/**
 * Direction of a metric's change, semantically (improving/declining/stable).
 */
export type TrendDirection = ("improving" | "declining" | "stable")
/**
 * Discriminant for [`CssCandidateAction::kind`].
 */
export type CssCandidateActionType = ("verify-unused" | "verify-undefined" | "consolidate" | "replace-with-token" | "standardize")
/**
 * Discriminant for [`UnusedAtRule::kind`].
 */
export type UnusedAtRuleKind = ("property-registration" | "layer")
/**
 * Singleton GitHub review-event marker.
 */
export type ReviewEnvelopeEvent = "COMMENT"
/**
 * Per-line review comment. Schema is an `anyOf` between GitHub and GitLab
 * shapes; at runtime every entry in a single envelope comes from the same
 * provider because the envelope is built from one provider's branch in
 * `crates/cli/src/report/ci/review.rs::render_review_envelope`.
 */
export type ReviewComment = (GitHubReviewComment | GitLabReviewComment)
/**
 * Singleton side discriminator for [`GitHubReviewComment::side`].
 */
export type GitHubReviewSide = "RIGHT"
/**
 * Singleton position-type discriminator for [`GitLabReviewPosition`].
 */
export type GitLabReviewPositionType = "text"
/**
 * Schema-version discriminator for the review envelope.
 */
export type ReviewEnvelopeSchema = ("fallow-review-envelope/v1" | "fallow-review-envelope/v2")
/**
 * Review-envelope provider tag.
 */
export type ReviewProvider = ("github" | "gitlab")
/**
 * `meta.check_conclusion` for the GitHub review envelope. Maps to the
 * GitHub Checks API conclusion field.
 */
export type ReviewCheckConclusion = ("success" | "neutral" | "failure")
/**
 * Schema-version discriminator for the review reconcile envelope.
 */
export type ReviewReconcileSchema = "fallow-review-reconcile/v1"
export type CoverageSetupSchemaVersion = "1"
export type CoverageSetupFramework = ("nextjs" | "nestjs" | "nuxt" | "sveltekit" | "astro" | "remix" | "vite" | "plain_node" | "unknown")
export type CoverageSetupPackageManager = ("npm" | "pnpm" | "yarn" | "bun")
export type CoverageSetupRuntimeTarget = ("node" | "browser")
export type CoverageAnalyzeSchemaVersion = "1"
/**
 * Discovery outcome for a [`LogicalGroup`].
 */
export type LogicalGroupStatus = ("ok" | "empty" | "invalid_path")
/**
 * Resolver mode label for grouped envelopes (dead-code, dupes, health).
 *
 * `owner` groups by CODEOWNERS team, `directory` groups by top-level
 * directory prefix, `package` groups by workspace package name, `section`
 * groups by GitLab CODEOWNERS `[Section]` header name.
 */
export type GroupByMode = ("owner" | "directory" | "package" | "section")
/**
 * Wire-version discriminator for [`ImpactReport`]. Independent from the global
 * `SchemaVersion` (the impact report versions on its own cadence) and from the
 * on-disk `STORE_SCHEMA_VERSION` (the persisted store shape versions
 * separately). Serializes as a string `const` so JSON consumers can switch on
 * it, matching the other independently-versioned envelopes (e.g.
 * `CoverageAnalyzeSchemaVersion`).
 */
export type ImpactReportSchemaVersion = "1"
/**
 * Why Impact tracking is (or is not) active for a project. `Project` = an
 * explicit per-repo `enable`; `User` = the user-global default with no per-repo
 * decision; `Default` = off (no per-repo decision and no global default).
 */
export type EnabledSource = ("project" | "user" | "default")
/**
 * Direction of a count trend between two recorded runs.
 */
export type ImpactTrendDirection = ("improving" | "declining" | "stable")
/**
 * Independent wire-version for the cross-repo report, on its own cadence (it
 * versions separately from the per-project `ImpactReportSchemaVersion` and the
 * on-disk `STORE_SCHEMA_VERSION`).
 */
export type CrossRepoImpactSchemaVersion = "1"
/**
 * The `fallow security --format json` schema version. Independently versioned
 * from the main contract, mirroring `ImpactReportSchemaVersion`.
 */
export type SecuritySchemaVersion = ("1" | "2" | "3" | "4" | "5" | "6" | "7")
/**
 * Severity level for rules.
 *
 * Controls whether an issue type causes CI failure (`error`), is reported
 * without failing (`warn`), or is suppressed entirely (`off`).
 */
export type Severity = ("error" | "warn" | "off")
/**
 * Gate mode for `fallow security --gate <mode>`.
 */
export type SecurityGateMode = ("new" | "newly-reachable")
/**
 * Gate verdict on the wire. `fail` is the CI-state token; human output renders
 * it as "REVIEW REQUIRED" because these stay unverified candidates, never
 * confirmed vulnerabilities.
 */
export type SecurityGateVerdict = ("pass" | "fail")
/**
 * The kind of security candidate. Findings are CANDIDATES for downstream agent
 * verification, NOT verified vulnerabilities.
 */
export type SecurityFindingKind = ("client-server-leak" | "tainted-sink")
/**
 * Verification-priority tier for a security candidate. This is ranking, not an
 * exploitability verdict.
 */
export type SecuritySeverity = ("high" | "medium" | "low")
/**
 * The role a hop plays in a security finding's structural import trace.
 */
export type TraceHopRole = ("client-boundary" | "untrusted-source" | "module-source" | "intermediate" | "secret-source" | "sink")
/**
 * Dead-code issue kind linked to a security candidate.
 */
export type SecurityDeadCodeKind = ("unused-file" | "unused-export")
/**
 * How strongly the untrusted-source signal is associated with the sink, a
 * structured discriminator so a consumer can tier candidates without parsing
 * the human `evidence` prose. Present only when
 * [`SecurityReachability::reachable_from_untrusted_source`] is true. Neither
 * value proves exploitability; both are ranking signals (issue #885 doctrine:
 * rank, never gate).
 */
export type TaintConfidence = ("arg-level" | "module-level")
/**
 * Static URL construction shape captured for URL-shaped security sinks.
 */
export type SecurityUrlShape = ("fixed-origin-dynamic-path" | "dynamic-origin")
/**
 * Runtime coverage state for the function enclosing a security sink.
 * This is production-observation evidence, not an exploitability verdict.
 */
export type SecurityRuntimeState = ("runtime-hot" | "runtime-cold" | "never-executed" | "low-traffic" | "coverage-unavailable" | "runtime-unknown")
/**
 * Defensive control family detected on a source to sink path.
 */
export type SecurityControlKind = ("sanitization" | "validation" | "authentication" | "authorization")
/**
 * Why a sink-shaped callee could not be flattened into a static catalogue
 * path.
 */
export type SkippedSecurityCalleeReason = ("computed-member" | "dynamic-dispatch" | "unsupported-assignment-object")
/**
 * Syntactic expression shape for a skipped security callee.
 */
export type SkippedSecurityCalleeExpressionKind = ("static-member-expression" | "computed-member-expression" | "identifier" | "other")
/**
 * Discriminator value for [`CodeClimateIssue::kind`].
 */
export type CodeClimateIssueKind = "issue"
/**
 * CodeClimate severity scale.
 */
export type CodeClimateSeverity = ("info" | "minor" | "major" | "critical" | "blocker")
/**
 * Envelope emitted by `fallow --format codeclimate` and
 * `fallow --format gitlab-codequality`. GitLab Code Quality consumes the
 * same shape. The wire form is a bare JSON array, not an object.
 */
export type CodeClimateOutput = CodeClimateIssue[]

/**
 * `fallow audit --format json` envelope.
 */
export interface AuditOutput {
schema_version: SchemaVersion
version: ToolVersion
command: AuditCommand
verdict: AuditVerdict
changed_files_count: number
base_ref: string
/**
 * Human-readable provenance of `base_ref`, e.g. `merge-base with
 * origin/main`, `local main`, or `FALLOW_AUDIT_BASE=upstream/main`.
 * Present when the base was auto-detected or set via `FALLOW_AUDIT_BASE`;
 * absent for an explicit `--base` (the ref the user typed is already
 * self-describing).
 */
base_description?: (string | null)
head_sha?: (string | null)
elapsed_ms: ElapsedMs
base_snapshot_skipped?: (boolean | null)
summary: AuditSummary
attribution: AuditAttribution
_meta?: (Meta | null)
dead_code?: (CheckOutput | null)
duplication?: (DupesReportPayload | null)
complexity?: (HealthReport | null)
/**
 * Read-only follow-up commands computed from this run's findings. See
 * [`CheckOutput::next_steps`] for the contract.
 */
next_steps?: NextStep[]
}
/**
 * Per-category summary counts for the audit result.
 */
export interface AuditSummary {
dead_code_issues: number
dead_code_has_errors: boolean
complexity_findings: number
max_cyclomatic?: (number | null)
duplication_clone_groups: number
}
/**
 * New-vs-inherited issue counts for audit.
 */
export interface AuditAttribution {
gate: AuditGate
dead_code_introduced: number
dead_code_inherited: number
complexity_introduced: number
complexity_inherited: number
duplication_introduced: number
duplication_inherited: number
}
/**
 * Metric and rule definitions emitted under `_meta` when `--explain` is
 * passed (always present in MCP responses). Helps AI agents and CI systems
 * interpret metric values without re-reading the docs site.
 */
export interface Meta {
/**
 * URL to the documentation page for this command.
 */
docs?: (string | null)
/**
 * Local telemetry correlation metadata for agent follow-up runs.
 */
telemetry?: (TelemetryMeta | null)
/**
 * Per-field definitions for envelope fields and action payload fields.
 */
field_definitions?: {
[k: string]: string
}
/**
 * Per-metric definitions: name, description, range, interpretation.
 */
metrics?: {
[k: string]: MetaMetric
}
/**
 * Per-rule definitions for check command output.
 */
rules?: {
[k: string]: MetaRule
}
}
/**
 * Privacy-safe local run metadata emitted for JSON consumers.
 */
export interface TelemetryMeta {
/**
 * Ephemeral local token that may be passed to the hidden `--parent-run`
 * flag on a later command. It is not derived from repository, path, user,
 * machine, project, or cloud data.
 */
analysis_run_id?: (string | null)
}
/**
 * Single-metric definition inside [`Meta::metrics`].
 */
export interface MetaMetric {
/**
 * Human-readable metric name.
 */
name?: (string | null)
/**
 * What this metric measures and how it is computed.
 */
description?: (string | null)
/**
 * Valid value range (e.g., `"[0, 100]"`).
 */
range?: (string | null)
/**
 * How to read the value (e.g., `"lower is better"`).
 */
interpretation?: (string | null)
}
/**
 * Single-rule definition inside [`Meta::rules`].
 */
export interface MetaRule {
/**
 * Human-readable rule name.
 */
name?: (string | null)
/**
 * What this rule detects.
 */
description?: (string | null)
/**
 * URL to the rule documentation.
 */
docs?: (string | null)
}
/**
 * Envelope emitted by `fallow dead-code --format json` (plus the `check`
 * block inside the combined and audit envelopes).
 *
 * The body is the full `AnalysisResults` flattened into the envelope so
 * every issue array (`unused_files`, `unused_exports`, ...) lives at the
 * top level, matching the existing wire shape. `entry_points` lifts the
 * otherwise `#[serde(skip)]`'d `AnalysisResults::entry_point_summary` back
 * into the JSON output. `summary` carries the per-category counts the
 * JSON layer always emits.
 */
export interface CheckOutput {
schema_version: SchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
total_issues: number
entry_points?: (EntryPoints | null)
summary: CheckSummary
/**
 * Files not reachable from any entry point. Wrapped in
 * [`UnusedFileFinding`] so each entry carries a typed `actions` array
 * natively, replacing the pre-2.76 post-pass injection.
 */
unused_files: UnusedFileFinding[]
/**
 * Exports never imported by other modules. Wrapped in
 * [`UnusedExportFinding`] so each entry carries a typed `actions`
 * array natively.
 */
unused_exports: UnusedExportFinding[]
/**
 * Type exports never imported by other modules. Wrapped in
 * [`UnusedTypeFinding`]: the inner [`UnusedExport`] struct is shared
 * with `unused_exports` but the wrapper emits a type-targeted fix
 * description.
 */
unused_types: UnusedTypeFinding[]
/**
 * Exported symbols whose public signature references same-file private
 * types. Wrapped in [`PrivateTypeLeakFinding`] so each entry carries a
 * typed `actions` array natively.
 */
private_type_leaks: PrivateTypeLeakFinding[]
/**
 * Dependencies listed in package.json but never imported. Wrapped in
 * [`UnusedDependencyFinding`] so each entry carries a typed `actions`
 * array natively. The fix action swaps from `remove-dependency` to
 * `move-dependency` when `used_in_workspaces` is non-empty.
 */
unused_dependencies: UnusedDependencyFinding[]
/**
 * Dev dependencies listed in package.json but never imported. Wrapped
 * in [`UnusedDevDependencyFinding`]: same bare struct as
 * `unused_dependencies` with a `devDependencies`-targeted fix
 * description.
 */
unused_dev_dependencies: UnusedDevDependencyFinding[]
/**
 * Optional dependencies listed in package.json but never imported.
 * Wrapped in [`UnusedOptionalDependencyFinding`] with an
 * `optionalDependencies`-targeted fix description.
 */
unused_optional_dependencies: UnusedOptionalDependencyFinding[]
/**
 * Enum members never accessed. Wrapped in
 * [`UnusedEnumMemberFinding`] so each entry carries a typed `actions`
 * array natively.
 */
unused_enum_members: UnusedEnumMemberFinding[]
/**
 * Class members never accessed. Wrapped in
 * [`UnusedClassMemberFinding`]: same inner [`UnusedMember`] struct as
 * `unused_enum_members`, with a class-targeted fix description and the
 * `auto_fixable: false` default to reflect dependency-injection
 * patterns.
 */
unused_class_members: UnusedClassMemberFinding[]
/**
 * Store members (Pinia `state` / `getters` / `actions` key, or a
 * setup-store returned key) declared but never accessed by any consumer
 * project-wide. Wrapped in [`UnusedStoreMemberFinding`]: same inner
 * [`UnusedMember`] struct as `unused_class_members`, with a
 * store-targeted fix description. Cross-graph: the store binding is
 * imported (the module is reachable) yet a specific member is dead.
 */
unused_store_members?: UnusedStoreMemberFinding[]
/**
 * Import specifiers that could not be resolved. Wrapped in
 * [`UnresolvedImportFinding`] so each entry carries a typed `actions`
 * array natively.
 */
unresolved_imports: UnresolvedImportFinding[]
/**
 * Dependencies used in code but not listed in package.json. Wrapped in
 * [`UnlistedDependencyFinding`].
 */
unlisted_dependencies: UnlistedDependencyFinding[]
/**
 * Exports with the same name across multiple modules. Wrapped in
 * [`DuplicateExportFinding`] so each entry carries a typed `actions`
 * array natively, with the position-0 `add-to-config` `ignoreExports`
 * snippet wired in at wrapper construction.
 */
duplicate_exports: DuplicateExportFinding[]
/**
 * Production dependencies only used via type-only imports (could be
 * devDependencies). Only populated in production mode. Wrapped in
 * [`TypeOnlyDependencyFinding`].
 */
type_only_dependencies: TypeOnlyDependencyFinding[]
/**
 * Production dependencies only imported by test files (could be
 * devDependencies). Wrapped in [`TestOnlyDependencyFinding`].
 */
test_only_dependencies?: TestOnlyDependencyFinding[]
/**
 * Circular dependency chains detected in the module graph. Wrapped in
 * [`CircularDependencyFinding`] so each entry carries a typed `actions`
 * array natively.
 */
circular_dependencies: CircularDependencyFinding[]
/**
 * Cycles or self-loops in the re-export edge subgraph (barrel files
 * re-exporting from each other in a loop). Wrapped in
 * [`ReExportCycleFinding`] so each entry carries a typed `actions`
 * array natively (a `refactor-re-export-cycle` informational primary
 * plus a `suppress-file` secondary; cycles are file-scoped so a single
 * suppression breaks the cycle).
 */
re_export_cycles?: ReExportCycleFinding[]
/**
 * Imports that cross architecture boundary rules. Wrapped in
 * [`BoundaryViolationFinding`] so each entry carries a typed `actions`
 * array natively.
 */
boundary_violations?: BoundaryViolationFinding[]
/**
 * Files that matched no architecture boundary zone while
 * `boundaries.coverage.requireAllFiles` was enabled.
 */
boundary_coverage_violations?: BoundaryCoverageViolationFinding[]
/**
 * Calls from zoned files to callees forbidden for that zone via
 * `boundaries.calls.forbidden`. Wrapped in
 * [`BoundaryCallViolationFinding`] so each entry carries a typed
 * `actions` array natively.
 */
boundary_call_violations?: BoundaryCallViolationFinding[]
/**
 * Banned calls and banned imports matched by declarative rule packs
 * (`rulePacks` config). Wrapped in [`PolicyViolationFinding`] so each
 * entry carries a typed `actions` array natively. Each finding carries
 * its effective per-rule severity.
 */
policy_violations?: PolicyViolationFinding[]
/**
 * Suppression comments or JSDoc tags that no longer match any issue.
 */
stale_suppressions?: StaleSuppression[]
/**
 * Entries in pnpm-workspace.yaml's catalog: or catalogs: sections not
 * referenced by any workspace package via the catalog: protocol. Wrapped
 * in [`UnusedCatalogEntryFinding`] so each entry carries a typed
 * `actions` array natively, with per-instance `auto_fixable` derived
 * from `hardcoded_consumers`.
 */
unused_catalog_entries?: UnusedCatalogEntryFinding[]
/**
 * Named groups under pnpm-workspace.yaml's catalogs: section that declare
 * no package entries. The top-level catalog: map is not reported. Wrapped
 * in [`EmptyCatalogGroupFinding`].
 */
empty_catalog_groups?: EmptyCatalogGroupFinding[]
/**
 * Workspace package.json references to catalogs (`catalog:` or
 * `catalog:<name>`) that do not declare the consumed package. pnpm install
 * will error until the named catalog grows to include the package or the
 * reference is switched / removed. Wrapped in
 * [`UnresolvedCatalogReferenceFinding`] with the discriminated
 * `add-catalog-entry` / `update-catalog-reference` primary at position 0.
 */
unresolved_catalog_references?: UnresolvedCatalogReferenceFinding[]
/**
 * Entries in pnpm-workspace.yaml's overrides: section, or package.json's
 * pnpm.overrides block, whose target package is not declared by any
 * workspace package and is not present in pnpm-lock.yaml. Default severity
 * is warn because projects without a readable lockfile fall back to
 * manifest-only checks; the hint field flags those conservative cases.
 * Wrapped in [`UnusedDependencyOverrideFinding`].
 */
unused_dependency_overrides?: UnusedDependencyOverrideFinding[]
/**
 * pnpm.overrides entries whose key or value does not parse as a valid
 * override spec (empty key, empty value, malformed selector, unbalanced
 * parent matcher). pnpm install will reject these. Default severity is
 * error. Wrapped in [`MisconfiguredDependencyOverrideFinding`].
 */
misconfigured_dependency_overrides?: MisconfiguredDependencyOverrideFinding[]
/**
 * `"use client"` files that export a Next.js server-only / route-segment
 * config name (e.g. `metadata`, `revalidate`, `GET`). Next.js rejects this
 * at build time. Wrapped in [`InvalidClientExportFinding`] so each entry
 * carries a typed `actions` array natively. Default severity is `warn`.
 */
invalid_client_exports?: InvalidClientExportFinding[]
/**
 * Barrel files that re-export BOTH a `"use client"` origin module AND a
 * server-only origin module (the Next.js App Router footgun). Wrapped in
 * [`MixedClientServerBarrelFinding`] so each entry carries a typed
 * `actions` array natively. Default severity is `warn`.
 */
mixed_client_server_barrels?: MixedClientServerBarrelFinding[]
/**
 * `"use client"` / `"use server"` directives written as expression
 * statements after a non-directive statement, so the RSC bundler parses
 * them as ordinary strings and silently ignores them. Wrapped in
 * [`MisplacedDirectiveFinding`] so each entry carries a typed `actions`
 * array natively. Default severity is `warn`.
 */
misplaced_directives?: MisplacedDirectiveFinding[]
/**
 * Vue `inject(KEY)` / Svelte `getContext(KEY)` calls whose symbol KEY is
 * provided nowhere in the project (the injected-never-provided dead-half).
 * Wrapped in [`UnprovidedInjectFinding`] so each entry carries a typed
 * `actions` array natively. Default severity is `warn`.
 */
unprovided_injects?: UnprovidedInjectFinding[]
/**
 * Vue/Svelte single-file components that are reachable but rendered nowhere
 * (the imported-but-never-rendered dead-half). Wrapped in
 * [`UnrenderedComponentFinding`] so each entry carries a typed `actions`
 * array natively. Default severity is `warn`.
 */
unrendered_components?: UnrenderedComponentFinding[]
/**
 * Next.js App Router route files that resolve to the same URL within one
 * app-root (a guaranteed `next build` failure). Wrapped in
 * [`RouteCollisionFinding`] so each entry carries a typed `actions` array
 * natively. One finding per colliding file. Default severity is `warn`.
 */
route_collisions?: RouteCollisionFinding[]
/**
 * Sibling Next.js dynamic route segments at one tree position using
 * different param spellings (a dev / runtime error; `next build` does NOT
 * catch it). Wrapped in [`DynamicSegmentNameConflictFinding`] so each entry
 * carries a typed `actions` array natively. Default severity is `warn`.
 */
dynamic_segment_name_conflicts?: DynamicSegmentNameConflictFinding[]
/**
 * Vue `<script setup>` `defineProps` props referenced nowhere in their own
 * SFC (neither `<script>` nor `<template>`). Wrapped in
 * [`UnusedComponentPropFinding`] so each entry carries a typed `actions`
 * array natively. Default severity is `warn`.
 */
unused_component_props?: UnusedComponentPropFinding[]
/**
 * Vue `<script setup>` `defineEmits` events emitted nowhere in their own SFC
 * (no `emit('<name>')` call). Wrapped in [`UnusedComponentEmitFinding`] so
 * each entry carries a typed `actions` array natively. Default severity is
 * `warn`.
 */
unused_component_emits?: UnusedComponentEmitFinding[]
/**
 * Next.js Server Actions (exports of `"use server"` files) that no code in
 * the project references. Reclassified out of `unused_exports` for
 * `"use server"` files. Wrapped in [`UnusedServerActionFinding`] so each
 * entry carries a typed `actions` array natively. Default severity is
 * `warn`.
 */
unused_server_actions?: UnusedServerActionFinding[]
/**
 * SvelteKit `+page.{ts,server.ts,js,server.js}` `load()` return-object keys
 * read by no consumer. Wrapped in [`UnusedLoadDataKeyFinding`] so each entry
 * carries a typed `actions` array natively. Default severity is `warn`.
 */
unused_load_data_keys?: UnusedLoadDataKeyFinding[]
/**
 * `true` when the `unused-load-data-key` detector abstained project-wide
 * because a whole-object use of `page.data` / `$page.data` was seen
 * somewhere (S1 observability: an empty `unused_load_data_keys` with this
 * flag set is NOT a clean bill, it means the rule could not run safely).
 * Serialized only when `true` so the default JSON contract is unchanged.
 */
unused_load_data_keys_global_abstain?: boolean
/**
 * React/Preact props forwarded unchanged through `>= N` intermediate
 * pass-through components until a consumer (located per-chain records).
 * Wrapped in [`PropDrillingChainFinding`] so each entry carries a typed
 * `actions` array natively. Health signal: the rule defaults to `off`
 * (opt-in), so this is dormant and populated ONLY when the user enables it.
 */
prop_drilling_chains?: PropDrillingChainFinding[]
/**
 * React/Preact components whose entire body is a single spread-forwarded
 * child render (`return <Child {...props}/>`): pure structural indirection,
 * a candidate for inlining at call sites. Wrapped in [`ThinWrapperFinding`]
 * so each entry carries a typed `actions` array natively. Health signal: the
 * rule defaults to `off` (opt-in), so this is dormant and populated ONLY
 * when the user enables it.
 */
thin_wrappers?: ThinWrapperFinding[]
/**
 * React/Preact components that participate in a duplicate-prop-shape group:
 * three or more components across two or more files whose statically-known
 * prop NAME set is identical after stripping ubiquitous DOM / passthrough
 * names (a missing shared `Props` type / base component). Wrapped in
 * [`DuplicatePropShapeFinding`] so each entry carries a typed `actions`
 * array and its sibling roster natively. Health signal: the rule defaults to
 * `off` (opt-in), so this is dormant and populated ONLY when the user
 * enables it.
 */
duplicate_prop_shapes?: DuplicatePropShapeFinding[]
baseline_deltas?: (BaselineDeltas | null)
baseline?: (BaselineMatch | null)
regression?: (RegressionResult | null)
_meta?: (Meta | null)
workspace_diagnostics?: WorkspaceDiagnostic[]
/**
 * Read-only follow-up commands computed from this run's findings, emitted
 * at the JSON root so an agent acting on the output is pointed at fallow's
 * adjacent verification capabilities (trace, complexity breakdown, audit,
 * workspace scoping). Each command is runnable as-is and never mutating;
 * see [`NextStep`] for both contracts. Omitted when empty or when
 * `FALLOW_SUGGESTIONS=off`; does NOT contribute to `total_issues`.
 */
next_steps?: NextStep[]
}
/**
 * Entry-point detection summary embedded in `CheckOutput` and the combined
 * envelope.
 */
export interface EntryPoints {
/**
 * Total number of detected entry points.
 */
total: number
/**
 * Breakdown of entry points by detection source (e.g., `"package.json"`,
 * `"next.js"`, `"config entry"`). Underscored keys so dashboards can
 * drill into individual sources.
 */
sources: {
[k: string]: number
}
}
/**
 * Per-category issue counts for dead-code analysis. Always present in
 * `CheckOutput`; when `--summary` is used the individual issue arrays are
 * omitted but this object stays populated.
 */
export interface CheckSummary {
/**
 * Total number of issues across all categories.
 */
total_issues: number
/**
 * Unused source files.
 */
unused_files: number
/**
 * Unused value exports.
 */
unused_exports: number
/**
 * Unused type exports.
 */
unused_types: number
/**
 * Public exports whose signature references same-file private types.
 */
private_type_leaks: number
/**
 * Combined count of unused entries across `dependencies`,
 * `devDependencies`, and `optionalDependencies`. The per-section
 * breakdown lives in the individual issue arrays on `CheckOutput`.
 */
unused_dependencies: number
/**
 * Unused enum members.
 */
unused_enum_members: number
/**
 * Unused class members.
 */
unused_class_members: number
/**
 * Unused store members.
 */
unused_store_members?: number
/**
 * Vue/Svelte injects whose key is provided nowhere in the project.
 */
unprovided_injects?: number
/**
 * Vue/Svelte components reachable but rendered nowhere in the project.
 */
unrendered_components?: number
/**
 * Vue `<script setup>` props referenced nowhere inside their own SFC.
 */
unused_component_props?: number
/**
 * Vue `<script setup>` emits emitted nowhere inside their own SFC.
 */
unused_component_emits?: number
/**
 * Next.js Server Actions (exports of `"use server"` files) referenced by no
 * code in the project.
 */
unused_server_actions?: number
/**
 * SvelteKit `load()` return-object keys read by no consumer.
 */
unused_load_data_keys?: number
/**
 * Imports that could not be resolved against the project's module graph.
 */
unresolved_imports: number
/**
 * Dependencies imported but absent from `package.json`.
 */
unlisted_dependencies: number
/**
 * Same-named exports declared in more than one module.
 */
duplicate_exports: number
/**
 * Production dependencies only used via type-only imports (could be
 * devDependencies). Only populated in production mode.
 */
type_only_dependencies: number
/**
 * Production dependencies only imported by test files (could be
 * devDependencies).
 */
test_only_dependencies: number
/**
 * Cycles detected in the import graph.
 */
circular_dependencies: number
/**
 * Cycles or self-loops in the re-export edge subgraph (barrel files
 * re-exporting from each other in a loop).
 */
re_export_cycles?: number
/**
 * Imports that cross architecture boundary rules.
 */
boundary_violations: number
/**
 * Files that match no architecture boundary zone.
 */
boundary_coverage_violations?: number
/**
 * Calls from zoned files to callees forbidden for that zone.
 */
boundary_call_violations?: number
/**
 * Banned calls and banned imports matched by declarative rule packs.
 */
policy_violations?: number
/**
 * Suppression comments that no longer match a finding.
 */
stale_suppressions: number
/**
 * Unused pnpm-workspace catalog entries.
 */
unused_catalog_entries: number
/**
 * Empty named catalog groups.
 */
empty_catalog_groups: number
/**
 * Workspace package.json catalog references the workspace catalogs
 * do not declare.
 */
unresolved_catalog_references: number
/**
 * Pnpm `overrides:` entries whose target package is not declared by any
 * workspace package and not present in the lockfile.
 */
unused_dependency_overrides: number
/**
 * Pnpm `overrides:` entries whose key or value cannot be parsed.
 */
misconfigured_dependency_overrides: number
/**
 * `"use client"` files that export a Next.js server-only / route-config name.
 */
invalid_client_exports?: number
/**
 * Barrel files that re-export both a `"use client"` origin and a
 * server-only origin.
 */
mixed_client_server_barrels?: number
/**
 * Misplaced `"use client"` / `"use server"` directives written as
 * expression statements after a non-directive statement.
 */
misplaced_directives?: number
/**
 * Next.js App Router route files that resolve to the same URL within one
 * app-root.
 */
route_collisions?: number
/**
 * Sibling Next.js dynamic route segments at one position using different
 * param spellings.
 */
dynamic_segment_name_conflicts?: number
}
/**
 * Wire-shape envelope for an [`UnusedFile`] finding. The bare finding
 * flattens in via `#[serde(flatten)]`, with a typed `actions` array
 * populated at construction time and the audit-pass `introduced` flag
 * attached as an optional sibling.
 */
export interface UnusedFileFinding {
/**
 * Absolute path to the unused file.
 */
path: string
/**
 * Suggested next steps: a `delete-file` primary and a `suppress-file`
 * secondary. Always emitted (possibly empty for forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base. `None` when serialized directly from Rust.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * A code-change fix. `type` is one of the kebab-case identifiers in
 * [`FixActionType`].
 */
export interface FixAction {
type: FixActionType
/**
 * Whether `fallow fix` can apply this fix automatically. Evaluated PER
 * FINDING, not per action type: the same `type` may carry
 * `auto_fixable: true` on one finding and `auto_fixable: false` on
 * another when per-instance guards in the applier discriminate (e.g.
 * `remove-catalog-entry` flips on `hardcoded_consumers`, the primary
 * dependency action flips between `remove-dependency` /
 * `move-dependency` on `used_in_workspaces`). Filter on this bool of
 * each individual action, not on `type`. See the [`IssueAction`]
 * enum-level docs for the full list of per-instance flips.
 */
auto_fixable: boolean
/**
 * Human-readable description of the fix.
 */
description: string
/**
 * Optional context note. Present on non-auto-fixable actions, and on
 * auto-fixable re-export findings to warn about public API surface.
 */
note?: (string | null)
/**
 * Only present on `update-catalog-reference` actions: catalogs in the
 * same workspace that DO declare the package, sorted lexicographically.
 * Lets agents pick the catalog to switch to without re-reading the
 * source.
 */
available_in_catalogs?: (string[] | null)
/**
 * Only present on `update-catalog-reference` actions when exactly one
 * alternative catalog declares the package: the unambiguous switch
 * target. Lets deterministic (non-LLM) agents land the edit without
 * picking from a list. Absent when `available_in_catalogs` has zero
 * or more than one entry.
 */
suggested_target?: (string | null)
}
/**
 * Inline-comment suppression for a single finding line.
 */
export interface SuppressLineAction {
type: SuppressLineKind
/**
 * Always false for suppress actions.
 */
auto_fixable: boolean
/**
 * Human-readable description of the suppression.
 */
description: string
/**
 * The inline comment to place above the line (e.g.,
 * `// fallow-ignore-next-line unused-export`). When multiple
 * suppressible findings share the same path and line, this may contain a
 * comma-separated issue-kind list such as
 * `// fallow-ignore-next-line unused-export, complexity`.
 */
comment: string
/**
 * Present on multi-location issue types (e.g., `duplicate_exports`) to
 * indicate the comment must be applied at each location.
 */
scope?: (SuppressLineScope | null)
}
/**
 * File-wide suppression placed at the top of the source file.
 */
export interface SuppressFileAction {
type: SuppressFileKind
/**
 * Always false for suppress actions.
 */
auto_fixable: boolean
/**
 * Human-readable description of the suppression.
 */
description: string
/**
 * The file-level comment to place at the top of the file (e.g.,
 * `// fallow-ignore-file unused-file`).
 */
comment: string
}
/**
 * Edit a fallow config file (`.fallowrc.json`, `fallow.toml`, etc.) to
 * add the offending value to an `ignore*` rule.
 */
export interface AddToConfigAction {
type: AddToConfigKind
/**
 * True when `fallow fix` can apply this config action automatically.
 * Evaluated PER FINDING, not per action type: `ignoreExports`
 * duplicate-export actions are auto-fixable when `fallow fix` can
 * safely write the rule, which today means EITHER a fallow config
 * file already exists OR no config exists and the working directory
 * is NOT inside a monorepo subpackage (in which case the applier
 * creates `.fallowrc.json` from `fallow init`'s framework-aware
 * scaffolding). The action is `false` inside a monorepo subpackage
 * with no workspace-root config because the applier refuses to
 * fragment per-package configs across the monorepo. Older scalar
 * config-ignore actions (e.g. `ignoreDependencies` on dependency
 * findings) are always manual today. Filter on this bool of each
 * individual action, not on the `type` alone. See the [`IssueAction`]
 * enum-level docs for the full list of per-instance flips.
 */
auto_fixable: boolean
/**
 * Human-readable description of the config change.
 */
description: string
/**
 * The fallow config key to add the value to (e.g.,
 * `ignoreDependencies`).
 */
config_key: string
value: AddToConfigValue
/**
 * Optional URL pointing at a stable JSON Schema fragment that describes
 * the shape of `value`. Agents that intend to validate `value` before
 * writing it into a user's config can fetch the linked schema and run
 * it against `value`. The URL is a JSON Pointer fragment into fallow's
 * main config schema (e.g.
 * `schema.json#/properties/ignoreExports` for the ignoreExports
 * action, or `schema.json#/properties/ignoreDependencies/items` for
 * the per-package ignoreDependencies action). Strictly additive:
 * consumers that ignore the field keep working unchanged.
 */
value_schema?: (string | null)
}
/**
 * Single `ignoreExports` rule entry. The fallow config accepts an array of
 * these under the `ignoreExports` key.
 */
export interface IgnoreExportsRule {
/**
 * File path (forward slashes, relative to project root) to which this
 * rule applies. Globs are accepted.
 */
file: string
/**
 * Names of exports inside `file` to silently treat as used.
 */
exports: string[]
}
/**
 * Wire-shape envelope for an [`UnusedExport`] finding consumed under the
 * `unused_exports` key. Same Rust struct as [`UnusedTypeFinding`], with a
 * different fix description so consumers can tell value-export from
 * type-export removal at the action level.
 */
export interface UnusedExportFinding {
/**
 * File containing the unused export.
 */
path: string
/**
 * Name of the unused export.
 */
export_name: string
/**
 * Whether this is a type-only export.
 */
is_type_only: boolean
/**
 * 1-based line number of the export.
 */
line: number
/**
 * 0-based byte column offset.
 */
col: number
/**
 * Byte offset into the source file (used by the fix command).
 */
span_start: number
/**
 * Whether this finding comes from a barrel/index re-export rather than the source definition.
 */
is_re_export: boolean
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnusedExport`] finding consumed under the
 * `unused_types` key. Wraps the same bare [`UnusedExport`] struct as
 * [`UnusedExportFinding`] but emits a fix action targeted at type-only
 * declarations, with the same `is_re_export`-aware note swap.
 */
export interface UnusedTypeFinding {
/**
 * File containing the unused export.
 */
path: string
/**
 * Name of the unused export.
 */
export_name: string
/**
 * Whether this is a type-only export.
 */
is_type_only: boolean
/**
 * 1-based line number of the export.
 */
line: number
/**
 * 0-based byte column offset.
 */
col: number
/**
 * Byte offset into the source file (used by the fix command).
 */
span_start: number
/**
 * Whether this finding comes from a barrel/index re-export rather than the source definition.
 */
is_re_export: boolean
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for a [`PrivateTypeLeak`] finding. Mirrors
 * [`UnusedFileFinding`]: flattens the bare finding and carries a typed
 * `actions` array (`export-type` primary plus `suppress-line` secondary).
 */
export interface PrivateTypeLeakFinding {
/**
 * File containing the exported symbol.
 */
path: string
/**
 * Export whose public signature leaks the private type.
 */
export_name: string
/**
 * Private type referenced by the public signature.
 */
type_name: string
/**
 * 1-based line number of the leaking type reference.
 */
line: number
/**
 * 0-based byte column offset.
 */
col: number
/**
 * Byte offset of the type reference.
 */
span_start: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnusedDependency`] finding consumed under
 * the `unused_dependencies` key (production deps). Flattens the bare
 * finding; the typed `actions` array carries either a `remove-dependency`
 * or `move-dependency` primary depending on
 * `inner.used_in_workspaces`.
 */
export interface UnusedDependencyFinding {
/**
 * Package name, including internal workspace package names.
 */
package_name: string
location: DependencyLocation
/**
 * Path to the package.json where this dependency is listed.
 * For root deps this is `<root>/package.json`, for workspace deps it is `<ws>/package.json`.
 */
path: string
/**
 * 1-based line number of the dependency entry in package.json.
 */
line: number
/**
 * Workspace roots that import this package even though the declaring workspace does not.
 */
used_in_workspaces?: string[]
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnusedDependency`] finding consumed under
 * the `unused_dev_dependencies` key. Same bare struct as
 * [`UnusedDependencyFinding`]; the fix description points at
 * `devDependencies` and the suppress comment uses
 * `unused-dev-dependency`.
 */
export interface UnusedDevDependencyFinding {
/**
 * Package name, including internal workspace package names.
 */
package_name: string
location: DependencyLocation
/**
 * Path to the package.json where this dependency is listed.
 * For root deps this is `<root>/package.json`, for workspace deps it is `<ws>/package.json`.
 */
path: string
/**
 * 1-based line number of the dependency entry in package.json.
 */
line: number
/**
 * Workspace roots that import this package even though the declaring workspace does not.
 */
used_in_workspaces?: string[]
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnusedDependency`] finding consumed under
 * the `unused_optional_dependencies` key. Same bare struct as
 * [`UnusedDependencyFinding`]; the fix description points at
 * `optionalDependencies`. Reuses the `unused-dependency` suppress
 * `IssueKind` because there is no dedicated variant for optional deps.
 */
export interface UnusedOptionalDependencyFinding {
/**
 * Package name, including internal workspace package names.
 */
package_name: string
location: DependencyLocation
/**
 * Path to the package.json where this dependency is listed.
 * For root deps this is `<root>/package.json`, for workspace deps it is `<ws>/package.json`.
 */
path: string
/**
 * 1-based line number of the dependency entry in package.json.
 */
line: number
/**
 * Workspace roots that import this package even though the declaring workspace does not.
 */
used_in_workspaces?: string[]
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnusedMember`] finding consumed under the
 * `unused_enum_members` key.
 */
export interface UnusedEnumMemberFinding {
/**
 * File containing the unused member.
 */
path: string
/**
 * Name of the parent enum or class.
 */
parent_name: string
/**
 * Name of the unused member.
 */
member_name: string
kind: MemberKind
/**
 * 1-based line number.
 */
line: number
/**
 * 0-based byte column offset.
 */
col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnusedMember`] finding consumed under the
 * `unused_class_members` key. Same Rust struct as
 * [`UnusedEnumMemberFinding`]; the fix action and suppress comment carry
 * the class-member kebab-case identifier instead.
 */
export interface UnusedClassMemberFinding {
/**
 * File containing the unused member.
 */
path: string
/**
 * Name of the parent enum or class.
 */
parent_name: string
/**
 * Name of the unused member.
 */
member_name: string
kind: MemberKind
/**
 * 1-based line number.
 */
line: number
/**
 * 0-based byte column offset.
 */
col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnusedMember`] finding consumed under the
 * `unused_store_members` key (a Pinia `state` / `getters` / `actions` key, or
 * a setup-store returned key, declared but never accessed by any consumer
 * project-wide). Same Rust struct as [`UnusedClassMemberFinding`]. Emits only
 * a line-level suppress action: there is no safe auto-fix because a store
 * member can be accessed reflectively (a Pinia plugin, `store.$onAction`, or
 * dynamic dispatch) in ways syntactic analysis cannot see, so removal is a
 * behavioral change the user must own.
 */
export interface UnusedStoreMemberFinding {
/**
 * File containing the unused member.
 */
path: string
/**
 * Name of the parent enum or class.
 */
parent_name: string
/**
 * Name of the unused member.
 */
member_name: string
kind: MemberKind
/**
 * 1-based line number.
 */
line: number
/**
 * 0-based byte column offset.
 */
col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnresolvedImport`] finding. Mirrors
 * [`UnusedFileFinding`]: flattens the bare finding and carries a typed
 * `actions` array (`resolve-import` primary plus config and inline
 * suppression actions).
 */
export interface UnresolvedImportFinding {
/**
 * File containing the unresolved import.
 */
path: string
/**
 * The import specifier that could not be resolved.
 */
specifier: string
/**
 * 1-based line number.
 */
line: number
/**
 * 0-based byte column offset of the import statement.
 */
col: number
/**
 * 0-based byte column offset of the source string literal (the specifier in quotes).
 * Used by the LSP to underline just the specifier, not the entire import line.
 */
specifier_col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnlistedDependency`] finding. Carries an
 * `install-dependency` primary (non-auto-fixable) plus the standard
 * `ignoreDependencies` config suppress.
 */
export interface UnlistedDependencyFinding {
/**
 * Package name, including internal workspace package names, that is
 * imported but not listed in package.json.
 */
package_name: string
/**
 * Import sites where this unlisted dependency is used (file path, line, column).
 */
imported_from: ImportSite[]
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * A location where an import occurs.
 */
export interface ImportSite {
/**
 * File containing the import.
 */
path: string
/**
 * 1-based line number.
 */
line: number
/**
 * 0-based byte column offset.
 */
col: number
}
/**
 * Wire-shape envelope for a [`DuplicateExport`] finding. Carries up to
 * three actions in position-locked order: an `add-to-config` `ignoreExports`
 * snippet (only when `locations[]` carries at least one path) followed by
 * the `remove-duplicate` fix and the multi-location suppress.
 *
 * The `add-to-config` action sits at position 0 because the documented
 * primary slot points at the safe, non-destructive path: the shadcn /
 * Radix / bits-ui namespace-barrel case where every `index.*` reexports
 * the directory's neighbours. The `remove-duplicate` fix stays as the
 * secondary so consumers that pattern-match on `actions[0].type` for
 * "primary fix" never propose deletion of an intentional barrel surface.
 */
export interface DuplicateExportFinding {
/**
 * The duplicated export name.
 */
export_name: string
/**
 * Locations where this export name appears.
 */
locations: DuplicateLocation[]
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * A location where a duplicate export appears.
 */
export interface DuplicateLocation {
/**
 * File containing the duplicate export.
 */
path: string
/**
 * 1-based line number.
 */
line: number
/**
 * 0-based byte column offset.
 */
col: number
}
/**
 * Wire-shape envelope for a [`TypeOnlyDependency`] finding. Carries a
 * `move-to-dev` primary plus the standard `ignoreDependencies` config
 * suppress.
 */
export interface TypeOnlyDependencyFinding {
/**
 * Production dependency that is only used via type-only imports.
 */
package_name: string
/**
 * Path to the package.json where the dependency is listed.
 */
path: string
/**
 * 1-based line number of the dependency entry in package.json.
 */
line: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for a [`TestOnlyDependency`] finding. Carries a
 * `move-to-dev` primary (different prose than [`TypeOnlyDependencyFinding`])
 * plus the standard `ignoreDependencies` config suppress.
 */
export interface TestOnlyDependencyFinding {
/**
 * Production dependency that is only imported by test files , consider
 * moving to devDependencies.
 */
package_name: string
/**
 * Path to the package.json where the dependency is listed.
 */
path: string
/**
 * 1-based line number of the dependency entry in package.json.
 */
line: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for a [`CircularDependency`] finding. Mirrors
 * [`UnusedFileFinding`]: flattens the bare finding and carries a typed
 * `actions` array (`refactor-cycle` primary plus `suppress-line`
 * secondary).
 */
export interface CircularDependencyFinding {
/**
 * Files forming the cycle, in import order.
 */
files: string[]
/**
 * Number of files in the cycle.
 */
length: number
/**
 * 1-based line number of the import that starts the cycle (in the first file).
 */
line: number
/**
 * 0-based byte column offset of the import that starts the cycle.
 */
col: number
/**
 * Per-file import anchors, one entry per hop in cycle order: `edges[i]`
 * is the import in `files[i]` pointing to `files[(i + 1) % len]`. Always
 * the same length as `files`. Drives the per-file LSP diagnostic
 * squiggly. `#[serde(default)]` so pre-`edges` baselines deserialize;
 * always emitted on output but intentionally not in the schema's
 * `required` set (see the struct doc).
 */
edges?: CircularDependencyEdge[]
/**
 * Whether this cycle crosses workspace package boundaries.
 */
is_cross_package?: boolean
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * One import hop in a circular dependency: the file containing the import
 * and where that import statement sits.
 *
 * `edges[i]` is the import IN `path` (the hop SOURCE, equal to the cycle's
 * `files[i]`) that points to the NEXT file in the cycle
 * (`files[(i + 1) % files.len()]`); the target is not repeated here to keep
 * the wire compact. Enables a per-file diagnostic squiggly anchored under
 * the offending import rather than a single squiggly on the first file.
 *
 * `col` is a 0-based BYTE column, matching the cycle's top-level `col`;
 * converting it to a UTF-16 code-unit column for LSP clients is a tracked
 * follow-up shared with the existing field.
 */
export interface CircularDependencyEdge {
/**
 * The file containing the import (the hop SOURCE; equal to `files[i]`).
 */
path: string
/**
 * 1-based line number of the import statement pointing to the next file.
 */
line: number
/**
 * 0-based byte column offset of the import statement.
 */
col: number
}
/**
 * Wire-shape envelope for a [`ReExportCycle`] finding. Mirrors
 * [`CircularDependencyFinding`]: flattens the bare finding and carries a
 * typed `actions` array (`refactor-re-export-cycle` informational primary
 * plus `suppress-file` secondary; cycles are file-scoped so a single
 * file-level suppression on the alphabetically-first member breaks the
 * cycle, and no `// fallow-ignore-next-line` form makes sense because the
 * diagnostic is anchored at line 1 col 0 of each member).
 */
export interface ReExportCycleFinding {
/**
 * Files participating in the cycle, sorted lexicographically. For a
 * self-loop, exactly one entry.
 */
files: string[]
kind: ReExportCycleKind
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for a [`BoundaryViolation`] finding. Mirrors
 * [`UnusedFileFinding`]: flattens the bare finding and carries a typed
 * `actions` array (`refactor-boundary` primary plus `suppress-line`
 * secondary).
 */
export interface BoundaryViolationFinding {
/**
 * The file making the disallowed import.
 */
from_path: string
/**
 * The file being imported that violates the boundary.
 */
to_path: string
/**
 * The zone the importing file belongs to.
 */
from_zone: string
/**
 * The zone the imported file belongs to.
 */
to_zone: string
/**
 * The raw import specifier from the source file.
 */
import_specifier: string
/**
 * 1-based line number of the import statement in the source file.
 */
line: number
/**
 * 0-based byte column offset of the import statement.
 */
col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for a [`BoundaryCoverageViolation`] finding. Carries
 * actions for assigning the file to a zone or explicitly allowing it to stay
 * unmatched.
 */
export interface BoundaryCoverageViolationFinding {
/**
 * The unmatched source file.
 */
path: string
/**
 * 1-based line number used for diagnostics.
 */
line: number
/**
 * 0-based byte column offset used for diagnostics.
 */
col: number
/**
 * Suggested next steps.
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for a [`BoundaryCallViolation`] finding. Carries
 * actions for refactoring the forbidden call out of the zone or suppressing
 * it with the shared `boundary-violation` token.
 */
export interface BoundaryCallViolationFinding {
/**
 * The zoned source file making the forbidden call.
 */
path: string
/**
 * 1-based line number of the call site.
 */
line: number
/**
 * 0-based byte column offset of the call site.
 */
col: number
/**
 * The zone the calling file is classified into.
 */
zone: string
/**
 * The callee path as written at the call site (e.g. `cp.exec`).
 */
callee: string
/**
 * The configured pattern that matched (e.g. `child_process.*`), so
 * consumers can see both the written path and the rule that fired.
 */
pattern: string
/**
 * Suggested next steps.
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for a [`PolicyViolation`] finding. Carries actions for
 * replacing the banned call or import, or suppressing it with a scoped
 * `policy-violation:<pack>/<rule-id>` token.
 */
export interface PolicyViolationFinding {
/**
 * The source file containing the banned call or import.
 */
path: string
/**
 * 1-based line number of the call site or import declaration.
 */
line: number
/**
 * 0-based byte column offset of the call site or import declaration.
 */
col: number
/**
 * Name of the rule pack that declared the matching rule.
 */
pack: string
/**
 * Id of the matching rule inside the pack. `pack` plus `rule_id` is the
 * finding's policy identity.
 */
rule_id: string
kind: PolicyRuleKind
/**
 * What matched: the written callee path for `banned-call` (e.g.
 * `cp.exec`), or the raw import specifier for `banned-import` (e.g.
 * `moment/locale/nl`).
 */
matched: string
severity: PolicyViolationSeverity
/**
 * The rule's author-provided message, when set.
 */
message?: (string | null)
/**
 * Suggested next steps.
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * A suppression comment or JSDoc tag that no longer matches any issue.
 */
export interface StaleSuppression {
/**
 * File containing the stale suppression.
 */
path: string
/**
 * 1-based line number of the suppression comment or tag.
 */
line: number
/**
 * 0-based byte column offset.
 */
col: number
origin: SuppressionOrigin
}
/**
 * Wire-shape envelope for an [`UnusedCatalogEntry`] finding. Per-instance
 * `auto_fixable` flips to `false` when `hardcoded_consumers` is non-empty:
 * the entry cannot be removed safely while a workspace package still pins
 * the same package via a hardcoded version range.
 */
export interface UnusedCatalogEntryFinding {
/**
 * Package name declared in the catalog (e.g. `"react"`, `"@scope/lib"`).
 */
entry_name: string
/**
 * Catalog group: `"default"` for the top-level `catalog:` map, or the
 * named catalog key for entries declared under `catalogs.<name>:`.
 */
catalog_name: string
/**
 * Path to `pnpm-workspace.yaml`, relative to the analyzed root.
 */
path: string
/**
 * 1-based line number of the catalog entry within `pnpm-workspace.yaml`.
 */
line: number
/**
 * Workspace `package.json` files that declare the same package with a
 * hardcoded version range instead of `catalog:`. Empty when no consumer
 * uses a hardcoded version. Sorted lexicographically for deterministic
 * output.
 */
hardcoded_consumers?: string[]
/**
 * Suggested next steps. Always emitted.
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`EmptyCatalogGroup`] finding. Carries a
 * straightforward `remove-empty-catalog-group` primary plus a YAML-comment
 * suppress.
 */
export interface EmptyCatalogGroupFinding {
/**
 * Catalog group name declared under the top-level `catalogs:` map.
 */
catalog_name: string
/**
 * Path to `pnpm-workspace.yaml`, relative to the analyzed root.
 */
path: string
/**
 * 1-based line number of the empty group header within `pnpm-workspace.yaml`.
 */
line: number
/**
 * Suggested next steps. Always emitted.
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnresolvedCatalogReference`] finding. The
 * primary action at position 0 discriminates on `available_in_catalogs`:
 * `add-catalog-entry` when the array is empty (no other catalog declares
 * the package), or `update-catalog-reference` when at least one
 * alternative exists. When exactly one alternative exists, the action
 * also carries `suggested_target` so deterministic agents can land the
 * edit without picking from a list.
 */
export interface UnresolvedCatalogReferenceFinding {
/**
 * Package name being referenced via the catalog protocol (e.g. `"react"`).
 */
entry_name: string
/**
 * Catalog group the reference points at: `"default"` for bare `catalog:` references,
 * or the named catalog key for `catalog:<name>` references.
 */
catalog_name: string
/**
 * Absolute path to the consumer `package.json`. Matches the storage
 * convention used by every path-anchored finding type (`UnusedFile`,
 * `UnresolvedImport`, `UnusedExport`, etc.) so the shared filtering
 * pipelines (`filter_results_by_changed_files`, per-file overrides,
 * audit attribution) work without a separate root-join pass. JSON
 * output strips the project-root prefix via `serde_path::serialize`.
 */
path: string
/**
 * 1-based line number of the dependency entry in the consumer `package.json`.
 */
line: number
/**
 * Other catalogs (in the same `pnpm-workspace.yaml`) that DO declare this
 * package. Empty when no catalog has the package. Sorted lexicographically.
 * Lets agents and humans decide whether to switch the reference to a
 * different catalog or to add the entry to the named catalog.
 */
available_in_catalogs?: string[]
/**
 * Suggested next steps. Always emitted; position 0 is the discriminated
 * primary (see struct docs).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnusedDependencyOverride`] finding. Carries
 * a `remove-dependency-override` primary plus an `add-to-config`
 * `ignoreDependencyOverrides` suppress scoped to the target package and
 * declaration source.
 */
export interface UnusedDependencyOverrideFinding {
/**
 * The full original override key as written in the source (e.g.
 * `"react>react-dom"`, `"@types/react@<18"`). Preserved for round-trip
 * reporting so agents see the unmodified spelling.
 */
raw_key: string
/**
 * The target package the override rewrites (e.g. `"react-dom"` for
 * `"react>react-dom"`, `"@types/react"` for `"@types/react@<18"`).
 */
target_package: string
/**
 * Optional parent package (left side of `>`). `None` for bare-target keys.
 */
parent_package?: (string | null)
/**
 * Optional version selector on the target (e.g. `Some("<18")` for
 * `"@types/react@<18"`).
 */
version_constraint?: (string | null)
/**
 * The right-hand side of the entry: the version pnpm should force.
 */
version_range: string
source: DependencyOverrideSource
/**
 * Path to the source file. `pnpm-workspace.yaml` or a `package.json`,
 * stored as an absolute filesystem path so `--changed-since` and
 * per-file `overrides.rules` can compare directly against the analyzer's
 * changed-set / per-path rule lookups. JSON serialization strips the
 * project root via `serde_path::serialize`, matching the
 * `UnresolvedCatalogReference` convention.
 */
path: string
/**
 * 1-based line number of the entry within the source file.
 */
line: number
/**
 * Soft hint reminding consumers to verify the override before removal.
 * Emitted on every unused-override finding (both bare-target and
 * parent-chain shapes) because projects without a readable lockfile still
 * use the conservative package-manifest fallback.
 */
hint?: (string | null)
/**
 * Suggested next steps. Always emitted.
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for a [`MisconfiguredDependencyOverride`] finding.
 * Carries a `fix-dependency-override` primary plus the conditional
 * `add-to-config` `ignoreDependencyOverrides` suppress (skipped when both
 * `target_package` and `raw_key` are empty, since the rule matcher keys on
 * a non-empty package name).
 */
export interface MisconfiguredDependencyOverrideFinding {
/**
 * The full original override key as written in the source.
 */
raw_key: string
/**
 * Parsed target package name when the key was syntactically valid (the
 * `EmptyValue` reason path). `None` for `UnparsableKey` findings whose
 * key could not be parsed at all. Used by JSON `add-to-config` actions to
 * emit a paste-ready `ignoreDependencyOverrides` value that matches the
 * suppression matcher (which also keys on `target_package`); avoids the
 * pitfall where `raw_key` like `"react@<18"` would not match the rule
 * that targets package `"react"`.
 */
target_package?: (string | null)
/**
 * The right-hand side of the entry, exactly as written. Empty when the
 * value was missing.
 */
raw_value: string
reason: DependencyOverrideMisconfigReason
source: DependencyOverrideSource
/**
 * Path to the source file. Stored as an absolute filesystem path so
 * `--changed-since` and per-file `overrides.rules` can compare directly.
 * JSON serialization strips the project root via `serde_path::serialize`.
 */
path: string
/**
 * 1-based line number of the entry within the source file.
 */
line: number
/**
 * Suggested next steps. Always emitted.
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`InvalidClientExport`] finding. There is no safe
 * auto-fix: the export itself may be a legitimate client-component value
 * export that happens to collide with a Next.js server-only name, so removing
 * it could break the component. Actions are a manual `move-to-server-module`
 * fix (the real remediation) plus a line-level suppress.
 */
export interface InvalidClientExportFinding {
/**
 * File carrying the `"use client"` directive and the illegal export.
 */
path: string
/**
 * Name of the server-only / route-config export that is illegal in a
 * client file (e.g. `metadata`, `generateMetadata`, `revalidate`, `GET`).
 */
export_name: string
/**
 * The file-level directive that makes the export illegal. Always
 * `"use client"` today; carried so the message can name it verbatim.
 */
directive: string
/**
 * 1-based line number of the export.
 */
line: number
/**
 * 0-based byte column offset of the export.
 */
col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for a [`MixedClientServerBarrel`] finding. There is no
 * safe auto-fix: splitting a barrel into separate client and server modules is
 * a human decision (the barrel may intentionally aggregate both surfaces).
 * Actions are a manual `split-mixed-barrel` fix (the real remediation) plus a
 * line-level suppress.
 */
export interface MixedClientServerBarrelFinding {
/**
 * The barrel file re-exporting both a client and a server-only origin.
 */
path: string
/**
 * The `"use client"` origin's relative path or specifier as written in the
 * barrel's offending re-export.
 */
client_origin: string
/**
 * The server-only origin's relative path or specifier as written in the
 * barrel's offending re-export.
 */
server_origin: string
/**
 * 1-based line number of the barrel's first offending re-export.
 */
line: number
/**
 * 0-based byte column offset of the barrel's first offending re-export.
 */
col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for a [`MisplacedDirective`] finding. There is no safe
 * auto-fix: moving a directive to the leading prologue is a small but
 * judgement-bearing edit (the author may have intended the file to be a
 * server module after all). Actions are a manual `hoist-directive` fix (the
 * real remediation) plus a line-level suppress.
 */
export interface MisplacedDirectiveFinding {
/**
 * The file carrying the misplaced directive.
 */
path: string
/**
 * The directive string as written, either `"use client"` or
 * `"use server"` (without the surrounding quotes).
 */
directive: string
/**
 * 1-based line number of the misplaced directive statement.
 */
line: number
/**
 * 0-based byte column offset of the misplaced directive statement.
 */
col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnprovidedInject`] finding. There is no safe
 * auto-fix: the fix is binary but judgement-bearing (add a `provide` for the
 * key, or delete the dead inject). The only action is a line-level suppress.
 */
export interface UnprovidedInjectFinding {
/**
 * The file carrying the orphan inject / getContext call.
 */
path: string
/**
 * The injected key identifier as written at the call site.
 */
key_name: string
/**
 * Which framework's DI API this came from: `"vue"` or `"svelte"`.
 */
framework: string
/**
 * 1-based line number of the inject / getContext call.
 */
line: number
/**
 * 0-based byte column offset of the inject / getContext call.
 */
col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnrenderedComponent`] finding. There is no safe
 * auto-fix: the fix is binary but judgement-bearing (render the component
 * somewhere, or delete the dead component). The only action is a line-level
 * suppress.
 */
export interface UnrenderedComponentFinding {
/**
 * The component file that is reachable but rendered nowhere.
 */
path: string
/**
 * The component name (the `.vue`/`.svelte` file stem, PascalCase).
 */
component_name: string
/**
 * Which framework this component belongs to: `"vue"` or `"svelte"`.
 */
framework: string
/**
 * A barrel/file that re-exports this component, kept for the remediation
 * trace ("reachable via X, rendered nowhere"). Absolute in memory,
 * serialized workspace-relative (like `path`); `None` when not determinable.
 */
reachable_via?: (string | null)
/**
 * 1-based line number of the component (the file head; SFCs have no explicit
 * default-export statement).
 */
line: number
/**
 * 0-based byte column offset.
 */
col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for a [`RouteCollision`] finding. A route collision is a
 * guaranteed `next build` failure, so the PRIMARY action is manual guidance
 * (move or merge one of the colliding files), NOT a suppress: suppressing a
 * build error never makes the build pass. A file-level suppress is offered as
 * an escape hatch only.
 */
export interface RouteCollisionFinding {
/**
 * This colliding route file (a `page` or `route` leaf).
 */
path: string
/**
 * The URL pathname this file resolves to within its app-root, after
 * stripping route groups `(x)` and parallel-slot `@slot` prefixes (e.g.
 * `/about`, `/api/health`, `/blog/:slug`).
 */
url: string
/**
 * The other route files that resolve to the same URL within the same
 * app-root. Path-sorted for stable output / fingerprints.
 */
conflicting_paths: string[]
/**
 * 1-based line number (file-level finding, always 1).
 */
line: number
/**
 * 0-based byte column offset (file-level finding, always 0).
 */
col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for a [`DynamicSegmentNameConflict`] finding. The
 * conflict is a Next.js dev / runtime error (`next build` does NOT catch it),
 * so the primary action is manual guidance (rename the dynamic segments to a
 * single consistent slug name), with a file-level suppress as escape hatch.
 */
export interface DynamicSegmentNameConflictFinding {
/**
 * This route file living under one of the conflicting dynamic segments.
 */
path: string
/**
 * The tree position (parent URL after group/slot normalization) where the
 * dynamic segments conflict, e.g. `/shop` for `/shop/[id]` vs
 * `/shop/[slug]`. The app-root prefix is stripped.
 */
position: string
/**
 * The distinct conflicting dynamic-segment spellings at this position, as
 * written (e.g. `["[id]", "[slug]"]`). Sorted for stable output.
 */
conflicting_segments: string[]
/**
 * The other route files at the same position under a conflicting dynamic
 * segment. Path-sorted for stable output / fingerprints.
 */
conflicting_paths: string[]
/**
 * 1-based line number (file-level finding, always 1).
 */
line: number
/**
 * 0-based byte column offset (file-level finding, always 0).
 */
col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnusedComponentProp`] finding. There is no safe
 * auto-fix: removing a declared prop is judgement-bearing (the prop may be part
 * of a deliberately-stable public component API). The only action is a
 * line-level suppress at the prop declaration.
 */
export interface UnusedComponentPropFinding {
/**
 * The `.vue` SFC declaring the unused prop.
 */
path: string
/**
 * The component name (the `.vue` file stem).
 */
component_name: string
/**
 * The declared prop name that is never referenced.
 */
prop_name: string
/**
 * 1-based line number of the prop declaration.
 */
line: number
/**
 * 0-based byte column offset of the prop declaration.
 */
col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnusedComponentEmit`] finding. There is no safe
 * auto-fix: removing a declared emit is judgement-bearing (the event may be
 * part of a deliberately-stable public component API). The only action is a
 * line-level suppress at the emit declaration.
 */
export interface UnusedComponentEmitFinding {
/**
 * The `.vue` SFC declaring the unused emit.
 */
path: string
/**
 * The component name (the `.vue` file stem).
 */
component_name: string
/**
 * The declared emit event name that is never emitted.
 */
emit_name: string
/**
 * 1-based line number of the emit declaration.
 */
line: number
/**
 * 0-based byte column offset of the emit declaration.
 */
col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnusedServerAction`] finding. There is no safe
 * auto-fix: the fix is binary but judgement-bearing (wire the action up to a
 * consumer, or delete it). The only action is a line-level suppress.
 */
export interface UnusedServerActionFinding {
/**
 * The `"use server"` file that exports the unreferenced action.
 */
path: string
/**
 * The exported action name as written, or `"default"` for a default export.
 */
action_name: string
/**
 * 1-based line number of the export.
 */
line: number
/**
 * 0-based byte column offset of the export.
 */
col: number
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for an [`UnusedLoadDataKey`] finding. There is no safe
 * auto-fix: a `load()` fetch can have side effects, so deleting the key is a
 * human call. The only action is a line-level suppress.
 */
export interface UnusedLoadDataKeyFinding {
/**
 * The producer `+page.{ts,server.ts,js,server.js}` file declaring the key.
 */
path: string
/**
 * The returned-object key name read by no consumer.
 */
key_name: string
/**
 * 1-based line number of the key in the return object.
 */
line: number
/**
 * 0-based byte column offset of the key.
 */
col: number
/**
 * The route directory relative to the project root (`src/routes/blog`), for
 * agent remediation and per-route trend aggregation. `None` when not
 * determinable.
 */
route_dir?: (string | null)
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for a [`PropDrillingChain`] finding. There is no safe
 * auto-fix: collapsing a drilling chain (colocate the consumer, lift to a
 * context, or compose the component) is a design decision. The only action is a
 * line-level suppress at the source hop's prop declaration. The rule defaults
 * to `off` (opt-in health signal), so this finding is dormant by default.
 */
export interface PropDrillingChainFinding {
/**
 * The drilled prop name as declared at the chain SOURCE.
 */
prop: string
/**
 * The chain depth = the number of components the prop is forwarded THROUGH
 * (source + intermediates + consumer = `hops.len()`). Always `>= N`.
 */
depth: number
/**
 * The ordered hop trail from source to consumer. The first hop owns the
 * prop, the middle hops are pass-throughs, the last hop consumes it. The
 * finding anchor is the first hop (`path` / `line` for suppression + CI).
 */
hops: PropDrillHop[]
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * One hop in a prop-drilling chain: a component that received the prop and
 * passed it along (or, at the chain ends, the source that owns it and the
 * consumer that substantively reads it).
 */
export interface PropDrillHop {
/**
 * The file containing this hop's component.
 */
file: string
/**
 * 1-based line of the component definition (or the prop declaration at the
 * source hop). Anchors a jump-to-source for the agent.
 */
line: number
/**
 * The component name at this hop.
 */
component: string
}
/**
 * Wire-shape envelope for a [`ThinWrapper`] finding. There is no safe
 * auto-fix: inlining a thin wrapper at its call sites (or deleting it) is a
 * design decision. The only action is a line-level suppress at the wrapper's
 * definition. The rule defaults to `off` (opt-in health signal), so this
 * finding is dormant by default.
 */
export interface ThinWrapperFinding {
/**
 * The file containing the wrapper component.
 */
file: string
/**
 * 1-based line of the wrapper component definition (the finding anchor for
 * jump-to-source and line-level suppression).
 */
line: number
/**
 * The wrapper component name.
 */
component: string
/**
 * The single child component the wrapper forwards its props to (as written
 * at the render site).
 */
child_component: string
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * Wire-shape envelope for a [`DuplicatePropShape`] finding. There is no safe
 * auto-fix: extracting a shared `Props` type or a base component for a group of
 * same-shaped components is a design decision. The actions are manual guidance
 * (extract the shared shape) plus a line-level suppress at the component
 * definition and a file-level suppress escape hatch (mirroring the
 * route-collision multi-file model). The rule defaults to `off` (opt-in health
 * signal), so this finding is dormant by default.
 */
export interface DuplicatePropShapeFinding {
/**
 * The file containing this component.
 */
file: string
/**
 * 1-based line of this component definition (the finding anchor for
 * jump-to-source and line-level suppression).
 */
line: number
/**
 * This component name.
 */
component: string
/**
 * The shared SIGNIFICANT prop-name set (sorted, denylist-stripped). The
 * unit being grouped; identical across every member of the group.
 */
shape: string[]
/**
 * The total number of components in this group (this one plus every
 * sibling).
 */
group_size: number
/**
 * The OTHER components sharing this exact prop shape (path-sorted). A
 * file-level-suppressed member drops from its own finding but still appears
 * here, because the group is real regardless of suppression.
 */
sharing_components: DuplicatePropShapeMember[]
/**
 * Suggested next steps. Always emitted (possibly empty for
 * forward-compat).
 */
actions: IssueAction[]
/**
 * Set by the audit pass when this finding is introduced relative to
 * the merge-base.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * One member of a duplicate-prop-shape group: the OTHER components that share
 * the same significant prop-name set, listed in each member's
 * `sharing_components`. Path-sorted for stable output. A located reference (no
 * `shape`, which is carried once on the owning [`DuplicatePropShape`]).
 */
export interface DuplicatePropShapeMember {
/**
 * The file containing the sibling component.
 */
file: string
/**
 * 1-based line of the sibling component definition.
 */
line: number
/**
 * The sibling component name.
 */
component: string
}
/**
 * Per-category delta comparison against a saved baseline. Only present in
 * `CheckOutput` when `--baseline` is used.
 */
export interface BaselineDeltas {
/**
 * Net change in total issues vs baseline (positive = more issues).
 */
total_delta: number
/**
 * Per-category breakdown of current, baseline, and delta counts.
 */
per_category: {
[k: string]: BaselineCategoryDelta
}
}
/**
 * Single-category baseline delta entry inside [`BaselineDeltas::per_category`].
 */
export interface BaselineCategoryDelta {
/**
 * Current issue count for this category.
 */
current: number
/**
 * Baseline issue count for this category.
 */
baseline: number
/**
 * Change from baseline (current - baseline).
 */
delta: number
}
/**
 * Baseline match statistics. Shows how many baseline entries existed and how
 * many matched current issues. Useful for detecting stale baselines
 * programmatically. Only present in `CheckOutput` when `--baseline` is used.
 */
export interface BaselineMatch {
/**
 * Total number of entries in the loaded baseline file.
 */
entries: number
/**
 * Number of baseline entries that matched current issues and were
 * filtered.
 */
matched: number
}
/**
 * Result of regression detection (`--fail-on-regression`). Compares current
 * issue counts against a baseline from config or an explicit file.
 */
export interface RegressionResult {
status: RegressionStatus
/**
 * Baseline total before the change. Absent when status is `skipped`.
 */
baseline_total?: (number | null)
/**
 * Current total after the change. Absent when status is `skipped`.
 */
current_total?: (number | null)
/**
 * Difference current - baseline. Absent when status is `skipped`.
 */
delta?: (number | null)
/**
 * Configured tolerance, interpreted per [`RegressionToleranceKind`].
 * Absent when status is `skipped`.
 */
tolerance?: (number | null)
/**
 * Interpretation of the tolerance value.
 */
tolerance_kind?: (RegressionToleranceKind | null)
/**
 * Whether the regression exceeded the tolerance.
 */
exceeded: boolean
/**
 * Only present when status is `skipped`.
 */
reason?: (string | null)
}
/**
 * A read-only follow-up command fallow surfaces from the current findings,
 * emitted as the top-level `next_steps` array on each command's JSON envelope.
 *
 * `next_steps` exists to point agents and humans sideways to fallow's adjacent
 * verification capabilities (trace, complexity breakdown, audit, workspace
 * scoping) that telemetry shows agents rarely discover, because they act on the
 * output in front of them rather than on reference docs.
 *
 * ## Two hard contracts
 *
 * 1. **Read-only.** A `next_step` NEVER suggests `fallow fix` or any mutating
 *    command. Fallow surfaces evidence and verification paths; deciding and
 *    applying the remediation is the agent's job.
 * 2. **Runnable, placeholder-free.** `command` is always runnable as-is. It
 *    never contains an angle-bracket placeholder (`<...>`); finding-derived
 *    values are filled in from a real, deterministically-selected finding, and
 *    any environment- or user-specific value that cannot be made concrete lives
 *    in `reason` instead. An agent can copy `command` and run it without edits.
 *
 * Both contracts are enforced by unit tests in
 * `crates/cli/src/report/suggestions.rs`.
 *
 * Note: a SEPARATE, unrelated `next_steps` field exists on the
 * `coverage setup` envelope (`CoverageSetupOutput.next_steps`) as a plain
 * `Vec<String>` of human onboarding steps. Consumers that read multiple
 * envelope kinds must route on the envelope's `kind` before interpreting a
 * `next_steps` field: on analysis envelopes it is `Vec<NextStep>` objects, on
 * `coverage setup` it is `Vec<String>`.
 */
export interface NextStep {
/**
 * Stable kebab-case key for machine dispatch and de-duplication
 * (for example `"trace-unused-export"`). Identity is stable across runs;
 * the `command` and `reason` strings may vary with the findings.
 */
id: string
/**
 * A runnable, read-only command string. Placeholder-free by contract.
 */
command: string
/**
 * One short phrase explaining why this helps. Carries any value that
 * cannot be made concrete in `command`.
 */
reason: string
}
/**
 * Wire-shape payload for `fallow dupes --format json` (the body that
 * flattens into [`crate::output_envelope::DupesOutput`] and is also
 * emitted under the `dupes` / `duplication` key inside the combined and
 * audit envelopes).
 *
 * Mirrors [`DuplicationReport`] field-for-field, except `clone_groups`
 * and `clone_families` carry the typed wrapper envelopes instead of bare
 * findings, so the schema (and any TS / agent consumer) sees the typed
 * `actions[]` natively.
 */
export interface DupesReportPayload {
/**
 * All detected clone groups, each wrapped with typed actions.
 */
clone_groups: CloneGroupFinding[]
/**
 * Clone families, each wrapped with typed actions. Inner `groups`
 * inside each [`CloneFamilyFinding`] are themselves wrapped as
 * [`CloneGroupFinding`] entries carrying their own `actions[]` (and
 * optional audit-mode `introduced` flag), so JSON-Schema strict
 * consumers and TS consumers reading `clone_families[].groups[]` see
 * the same shape as the top-level `clone_groups[]` array (preserves
 * the issue #393 regression contract).
 */
clone_families: CloneFamilyFinding[]
/**
 * Mirrored directory pairs.
 */
mirrored_directories?: MirroredDirectory[]
stats: DuplicationStats
}
/**
 * Wire-shape envelope for a [`CloneGroup`] finding. Flattens the bare
 * group via `#[serde(flatten)]` and carries a typed `actions` array plus
 * the optional audit-mode `introduced` flag. Replaces the legacy
 * post-pass injection in `crates/cli/src/report/json.rs::inject_dupes_actions`.
 */
export interface CloneGroupFinding {
/**
 * All instances where this duplicated code appears.
 */
instances: CloneInstance[]
/**
 * Number of tokens in the duplicated block.
 */
token_count: number
/**
 * Number of lines in the duplicated block.
 */
line_count: number
/**
 * Stable content fingerprint, usually `dup:<8hex>` and widened on rare
 * report collisions. Addressable via `fallow dupes --trace dup:<fp>` (and
 * the `trace_clone` MCP tool) to deep-dive this group; shown alongside
 * each group in the human listing.
 */
fingerprint: string
/**
 * Best-effort human-readable name for the clone: the dominant repeated
 * identifier across the duplicated fragment (e.g. a shared `parseCsv`
 * function). `None` when the clone has no clear dominant name (generic or
 * tied identifiers); consumers then fall back to a file-based label. Lets
 * editors and agents label a clone by what it is rather than an opaque
 * ordinal.
 */
suggested_name?: (string | null)
/**
 * Suggested next steps: an `extract-shared` primary and a
 * `suppress-line` secondary. Always emitted (possibly empty for
 * forward-compat).
 */
actions: CloneGroupAction[]
/**
 * Set by the audit pass when this clone group is introduced relative
 * to the merge-base. `None` when serialized directly from Rust.
 */
introduced?: (AuditIntroduced | null)
}
/**
 * A single instance of duplicated code at a specific location.
 */
export interface CloneInstance {
/**
 * Path to the file containing this clone instance.
 */
file: string
/**
 * 1-based start line of the clone.
 */
start_line: number
/**
 * 1-based end line of the clone.
 */
end_line: number
/**
 * 0-based start column.
 */
start_col: number
/**
 * 0-based end column.
 */
end_col: number
/**
 * The actual source code fragment.
 */
fragment: string
}
/**
 * Per-action wire shape attached to each [`CloneGroupFinding`] and
 * [`AttributedCloneGroupFinding`]. Mirrors the action types previously
 * emitted by `inject_dupes_actions::build_clone_group_actions` in
 * `crates/cli/src/report/json.rs`: `extract-shared` plus `suppress-line`.
 */
export interface CloneGroupAction {
type: CloneGroupActionType
/**
 * Whether `fallow fix` can auto-apply this action. Both variants are
 * manual today; the field is non-singleton so a future auto-applier
 * does not need a schema change.
 */
auto_fixable: boolean
/**
 * Human-readable description of the action.
 */
description: string
/**
 * The inline comment to insert (e.g.,
 * `// fallow-ignore-next-line code-duplication`). Present on
 * `suppress-line`; absent on `extract-shared`.
 */
comment?: (string | null)
}
/**
 * Wire-shape envelope for a [`CloneFamily`] finding.
 *
 * Unlike most `*Finding` wrappers this one is NOT `#[serde(flatten)]` over
 * the bare [`CloneFamily`], because the family's nested
 * `groups: Vec<CloneGroup>` field needs to carry the typed
 * [`CloneGroupFinding`] wrapper too (so every nested clone group gets its
 * own `actions[]` array, matching the legacy post-pass behavior; see issue
 * #393 regression test). The wire shape stays byte-identical to the
 * previous post-pass output. No `introduced` field because `fallow audit`
 * attributes clone groups (not families) when running against a base ref.
 */
export interface CloneFamilyFinding {
/**
 * The files involved in this family (sorted for stable output).
 */
files: string[]
/**
 * Clone groups belonging to this family, each wrapped with typed
 * `actions[]` so consumers that read `clone_families[].groups[]`
 * directly see the same shape as the top-level `clone_groups[]`.
 */
groups: CloneGroupFinding[]
/**
 * Total number of duplicated lines across all groups.
 */
total_duplicated_lines: number
/**
 * Total number of duplicated tokens across all groups.
 */
total_duplicated_tokens: number
/**
 * Refactoring suggestions for this family.
 */
suggestions: RefactoringSuggestion[]
/**
 * Suggested next steps: an `extract-shared` primary, one
 * `apply-suggestion` per [`RefactoringSuggestion`] on the family, and
 * a trailing `suppress-line`. Always emitted (possibly empty for
 * forward-compat).
 */
actions: CloneFamilyAction[]
}
/**
 * A refactoring suggestion for a clone family.
 */
export interface RefactoringSuggestion {
kind: RefactoringKind
/**
 * Human-readable description of the suggestion.
 */
description: string
/**
 * Estimated lines that could be eliminated.
 */
estimated_savings: number
}
/**
 * Per-action wire shape attached to each [`CloneFamilyFinding`]. Mirrors
 * the action types previously emitted by
 * `build_clone_family_actions`: `extract-shared`, one `apply-suggestion`
 * per [`RefactoringSuggestion`] on the family, and a trailing
 * `suppress-line`.
 */
export interface CloneFamilyAction {
type: CloneFamilyActionType
/**
 * Whether `fallow fix` can auto-apply this action. All three variants
 * are manual today.
 */
auto_fixable: boolean
/**
 * Human-readable description of the action.
 */
description: string
/**
 * Additional context. Present on `extract-shared` (explaining that
 * the family's clone groups share the same files); absent otherwise.
 */
note?: (string | null)
/**
 * The inline comment to insert (e.g.,
 * `// fallow-ignore-next-line code-duplication`). Present on
 * `suppress-line` only.
 */
comment?: (string | null)
}
/**
 * A detected mirrored directory pattern: two directory prefixes that contain
 * identical files (e.g., `src/` and `deno/lib/`).
 */
export interface MirroredDirectory {
/**
 * First directory path (lexically smaller).
 */
dir_a: string
/**
 * Second directory path.
 */
dir_b: string
/**
 * Filenames shared between the two directories.
 */
shared_files: string[]
/**
 * Total duplicated lines across all shared files.
 */
total_lines: number
}
/**
 * Aggregate duplication statistics.
 */
export interface DuplicationStats {
/**
 * Total files analyzed.
 */
total_files: number
/**
 * Files containing at least one clone instance.
 */
files_with_clones: number
/**
 * Total lines across all analyzed files.
 */
total_lines: number
/**
 * Lines that are part of at least one clone.
 */
duplicated_lines: number
/**
 * Total tokens across all analyzed files.
 */
total_tokens: number
/**
 * Tokens that are part of at least one clone.
 */
duplicated_tokens: number
/**
 * Number of clone groups in the reported `clone_groups[]` array.
 * Matches `clone_groups[].length` post `minOccurrences` filtering; the
 * count of groups hidden by the filter is exposed in
 * `clone_groups_below_min_occurrences`.
 */
clone_groups: number
/**
 * Total clone instances across all reported groups. Matches the sum of
 * `clone_groups[].locations[].length` post `minOccurrences` filtering.
 */
clone_instances: number
/**
 * Percentage of duplicated lines (0.0 to 100.0). Always reflects the FULL
 * corpus, computed BEFORE the `minOccurrences` filter so trend lines and
 * `threshold` gates stay stable when the filter changes.
 */
duplication_percentage: number
/**
 * Number of clone groups hidden by `duplicates.minOccurrences`. Absent (or
 * `0`) when the filter is at its default of `2` and nothing was hidden.
 * Pre-filter clone group count = `clone_groups +
 * clone_groups_below_min_occurrences`.
 */
clone_groups_below_min_occurrences?: number
}
/**
 * Result of complexity analysis for reporting.
 */
export interface HealthReport {
/**
 * Functions and synthetic template entries exceeding complexity
 * thresholds, sorted by the --sort criteria. Each entry wraps its
 * inner [`ComplexityViolation`] payload (flattened on the wire) with
 * the typed `actions` list and an optional audit-mode `introduced`
 * flag.
 */
findings: HealthFinding[]
summary: HealthSummary
/**
 * Configured threshold override states. Entries are emitted for active
 * exceptions, stale exceptions, and full-run no-match cleanup hints.
 */
threshold_overrides?: ThresholdOverrideState[]
/**
 * Project-wide vital signs (always computed from available data).
 */
vital_signs?: (VitalSigns | null)
/**
 * Project-wide health score (only populated with `--score`).
 */
health_score?: (HealthScore | null)
/**
 * Per-file health scores. Only present when --file-scores is used. Sorted
 * by risk-aware triage concern, combining low maintainability and high
 * CRAP risk. Zero-function files (barrels) are excluded by default.
 */
file_scores?: FileHealthScore[]
/**
 * Static coverage gaps.
 *
 * Populated when coverage gaps are explicitly requested, or when the
 * top-level `health` command allows config severity to surface them in the
 * default report.
 */
coverage_gaps?: (CoverageGaps | null)
/**
 * Located prop-drilling chains (React/Preact props forwarded unchanged
 * through 3+ pass-through components). Only present when the opt-in
 * `prop-drilling` rule is enabled (it defaults to off). Each entry carries
 * the source, every pass-through hop, and the consumer with file + line +
 * component, so CI / an agent can act. Surfaced alongside hotspots as a
 * graph-derived health signal.
 */
prop_drilling_chains?: PropDrillingChainFinding[]
/**
 * Hotspot entries combining git churn with complexity. Only present when
 * --hotspots is used. Sorted by score descending (highest risk first).
 * Each entry wraps its inner [`HotspotEntry`] payload (flattened on the
 * wire) with a typed `actions` list.
 */
hotspots?: HotspotFinding[]
/**
 * Hotspot analysis summary (only set with `--hotspots`).
 */
hotspot_summary?: (HotspotSummary | null)
/**
 * Runtime coverage findings from the paid sidecar (only populated with
 * `--runtime-coverage`).
 */
runtime_coverage?: (RuntimeCoverageReport | null)
/**
 * Combined coverage, runtime, complexity, and change-scope verdicts.
 */
coverage_intelligence?: (CoverageIntelligenceReport | null)
/**
 * Functions exceeding 60 LOC (very high risk). Only present when unit size
 * very-high-risk bin >= 3%. Sorted by line count descending.
 */
large_functions?: LargeFunctionEntry[]
/**
 * Ranked refactoring recommendations. Only present when --targets is used.
 * Sorted by efficiency (priority/effort) descending. Each entry wraps
 * its inner [`RefactoringTarget`] payload (flattened on the wire) with
 * a typed `actions` list.
 */
targets?: RefactoringTargetFinding[]
/**
 * Adaptive thresholds used for target scoring (only set with `--targets`).
 */
target_thresholds?: (TargetThresholds | null)
/**
 * Health trend comparison against a previous snapshot (only set with `--trend`).
 */
health_trend?: (HealthTrend | null)
/**
 * Audit breadcrumb explaining systemic action-array adjustments. Present
 * only when at least one adjustment was made (e.g., health finding
 * suppression hints omitted because a baseline is active). When --group-by
 * is active, each entry of `groups` may carry its own `actions_meta`
 * describing the same omission so per-group consumers do not need to walk
 * back to the report root.
 */
actions_meta?: (HealthActionsMeta | null)
/**
 * Structural CSS analytics (specificity hotspots, `!important` density,
 * over-complex selectors, deep nesting). Present only with `--css`.
 */
css_analytics?: (CssAnalyticsReport | null)
}
/**
 * Wire envelope for a single complexity finding.
 */
export interface HealthFinding {
path: string
name: string
line: number
col: number
cyclomatic: number
cognitive: number
line_count: number
param_count: number
/**
 * Number of React hook calls in this function's body (`useState` /
 * `useEffect` / `useMemo` / `useCallback` / custom `use*`). Descriptive
 * hotspot context for React components; omitted when zero (non-React).
 */
react_hook_count?: number
/**
 * Deepest JSX element nesting reached in this function's body. Descriptive
 * hotspot context; omitted when zero (renders no JSX).
 */
react_jsx_max_depth?: number
/**
 * Number of props destructured from this component's first parameter.
 * Descriptive hotspot context; omitted when zero.
 */
react_prop_count?: number
/**
 * Per-kind React hook breakdown (state/effect/memo/callback/custom) plus
 * the max `useEffect` dependency-array arity, derived from the cached
 * `hook_uses` IR at the health layer. Descriptive refinement of
 * `react_hook_count`; present only when at least one component-scope hook
 * was attributed, so non-React findings stay byte-identical.
 */
react_hook_profile?: (ReactHookProfile | null)
exceeded: ExceededThreshold
severity: FindingSeverity
crap?: (number | null)
coverage_pct?: (number | null)
coverage_tier?: (CoverageTier | null)
coverage_source?: (CoverageSource | null)
inherited_from?: (string | null)
component_rollup?: (ComponentRollup | null)
/**
 * Per-decision-point complexity breakdown explaining WHICH constructs drove
 * the cyclomatic and cognitive scores. Populated only when the caller opts
 * in via `health --complexity-breakdown`; empty (and omitted from JSON)
 * otherwise so default and CI output stay lean.
 */
contributions?: ComplexityContribution[]
/**
 * Resolved thresholds used for this finding when a config override changed
 * at least one ceiling. Omitted for findings using global thresholds.
 */
effective_thresholds?: (HealthEffectiveThresholds | null)
/**
 * Source of the effective thresholds. Omitted when thresholds are global.
 */
threshold_source?: (ThresholdSource | null)
/**
 * Machine-actionable fix and suppress hints.
 */
actions: HealthFindingAction[]
/**
 * Audit-mode flag indicating whether the finding is new versus the base
 * snapshot.
 */
introduced?: (boolean | null)
}
/**
 * Per-component React hook profile derived from the cached `hook_uses` IR at
 * the health layer. Descriptive context that refines the bare
 * [`ComplexityViolation::react_hook_count`] headline with a per-kind breakdown
 * and the maximum `useEffect` dependency-array arity.
 *
 * Attached only when at least one component-scope hook was attributed to the
 * function, so non-React findings stay byte-identical on the wire. The
 * per-kind counts cover hooks recorded by the React visitor (calls inside an
 * identified component); a `use*` call inside a plain helper function is
 * counted in `react_hook_count` but NOT here, so the breakdown can sum to LESS
 * than `react_hook_count`. `react_hook_count` remains the headline total; this
 * is an additive refinement.
 */
export interface ReactHookProfile {
/**
 * `useState` call count attributed to this component.
 */
state: number
/**
 * `useEffect` call count attributed to this component.
 */
effect: number
/**
 * `useMemo` call count attributed to this component.
 */
memo: number
/**
 * `useCallback` call count attributed to this component.
 */
callback: number
/**
 * Custom `use*` hook call count attributed to this component.
 */
custom: number
/**
 * Largest `useEffect` dependency-array arity over the attributed effects
 * that carry a literal deps array. `None` when no attributed `useEffect`
 * had a literal array (absent or non-literal deps; ADR-001 syntactic-only,
 * so absence does NOT mean "no coupling").
 */
max_effect_dep_arity?: (number | null)
}
export interface ComponentRollup {
component: string
class_worst_function: string
class_cyclomatic: number
class_cognitive: number
template_path: string
template_cyclomatic: number
template_cognitive: number
}
/**
 * A single complexity increment, located at its source line/column.
 *
 * `weight` is the amount this construct added to `metric`; for nested
 * cognitive increments `weight == 1 + nesting`. Consumers that render inline
 * (the VS Code editor breakdown) group contributions by `line` and sum the
 * weights, deferring the per-kind list to a hover.
 */
export interface ComplexityContribution {
/**
 * 1-based line number where the construct begins.
 */
line: number
/**
 * 0-based byte column where the construct begins.
 */
col: number
metric: ComplexityMetric
kind: ComplexityContributionKind
/**
 * The amount added to `metric` at this site (`1 + nesting` for nested
 * cognitive increments, otherwise `1`).
 */
weight: number
/**
 * The nesting depth at the increment site (`0` when not nested). Lets a
 * consumer explain a cognitive `+3` as "+1 base, +2 nesting".
 */
nesting: number
}
/**
 * Resolved thresholds used to evaluate a health finding.
 */
export interface HealthEffectiveThresholds {
max_cyclomatic: number
max_cognitive: number
max_crap: number
}
/**
 * Suggested action attached to a [`ComplexityViolation`].
 *
 * Each complexity finding carries an array of these on the JSON wire
 * (`findings[].actions[]`). The action selector in
 * `crates/cli/src/report/json.rs::build_health_finding_actions` picks the
 * primary action based on which thresholds triggered the finding and the
 * bucketed coverage tier. See [`HealthFindingActionType`] for the full
 * discriminant list.
 *
 * `note`, `comment`, and `placement` are populated per-variant: refactor
 * actions carry a `note`, suppress-line / suppress-file actions carry
 * `comment` plus `placement`, and the coverage-leaning actions
 * (`add-tests`, `increase-coverage`) carry only `note`.
 *
 * [`ComplexityViolation`]: ../../fallow-cli/src/health_types/scores.rs
 */
export interface HealthFindingAction {
type: HealthFindingActionType
/**
 * Whether `fallow fix` can auto-apply this action. Today every health
 * finding action is manual, but the field is non-singleton so a future
 * auto-applier (e.g., an LLM-driven `refactor-function` worker) does
 * not need a schema change.
 */
auto_fixable: boolean
/**
 * Human-readable description of the action.
 */
description: string
/**
 * Additional context (e.g., the canonical CRAP formula, or a hint
 * about which branch type to extract). Present on most action types;
 * dropped only when the description carries the full ask.
 */
note?: (string | null)
/**
 * The inline comment to insert (e.g.,
 * `// fallow-ignore-next-line complexity` or
 * `<!-- fallow-ignore-file complexity -->`). Present on
 * `suppress-line` and `suppress-file` action variants.
 */
comment?: (string | null)
/**
 * Where to insert the suppress comment
 * (e.g., `above-function-declaration`, `above-angular-decorator`,
 * `above-component-worst-method`, or `top-of-template`). Present on
 * `suppress-line` and `suppress-file` action variants.
 */
placement?: (string | null)
/**
 * Project-relative path the action should target when the finding's
 * remediation lives in a different file from where the finding is
 * anchored. Currently populated on the `increase-coverage` action for
 * synthetic Angular `<template>` findings whose CRAP is inherited from
 * the owning `.component.ts`: the action points at the component file
 * (where the user actually adds tests) rather than the `.html` template
 * (where the finding is anchored but which is not directly testable).
 * Absent when the action's target is the finding's own file.
 */
target_path?: (string | null)
}
/**
 * Summary statistics for the health report.
 */
export interface HealthSummary {
files_analyzed: number
functions_analyzed: number
functions_above_threshold: number
max_cyclomatic_threshold: number
max_cognitive_threshold: number
max_crap_threshold: number
files_scored?: (number | null)
average_maintainability?: (number | null)
coverage_model?: (CoverageModel | null)
coverage_source_consistency?: (CoverageSourceConsistency | null)
istanbul_matched?: (number | null)
istanbul_total?: (number | null)
severity_critical_count: number
severity_high_count: number
severity_moderate_count: number
}
/**
 * Report entry describing whether a threshold override is active, stale, or
 * no longer matching any analyzed file or function.
 */
export interface ThresholdOverrideState {
status: ThresholdOverrideStatus
override_index: number
path?: (string | null)
function?: (string | null)
configured_thresholds: HealthConfiguredThresholds
effective_thresholds: HealthEffectiveThresholds
metrics?: (ThresholdOverrideMetrics | null)
reason?: (string | null)
}
/**
 * Threshold values configured by a single override entry.
 */
export interface HealthConfiguredThresholds {
max_cyclomatic?: (number | null)
max_cognitive?: (number | null)
max_crap?: (number | null)
}
/**
 * Current complexity metrics for a matched threshold override entry.
 */
export interface ThresholdOverrideMetrics {
cyclomatic: number
cognitive: number
crap?: (number | null)
}
/**
 * Project-wide vital signs , a fixed set of metrics for trend tracking.
 *
 * Metrics are `Option` when the data source was not available in the current run
 * (e.g., `duplication_pct` is `None` unless the duplication pipeline was run,
 * `hotspot_count` is `None` without git history).
 */
export interface VitalSigns {
/**
 * Percentage of files not reachable from any entry point.
 */
dead_file_pct?: (number | null)
/**
 * Percentage of exports never imported by other modules.
 */
dead_export_pct?: (number | null)
/**
 * Average cyclomatic complexity across all functions.
 */
avg_cyclomatic: number
/**
 * Percentage of functions at or above the critical cyclomatic threshold.
 * Used by the scale-invariant health score.
 */
critical_complexity_pct?: (number | null)
/**
 * 90th percentile cyclomatic complexity.
 */
p90_cyclomatic: number
/**
 * Code duplication percentage (None if duplication pipeline was not run).
 */
duplication_pct?: (number | null)
/**
 * Number of hotspot files (score >= 50). None if git history unavailable.
 */
hotspot_count?: (number | null)
/**
 * Number of files in the top 1% of the within-project hotspot ranking.
 */
hotspot_top_pct_count?: (number | null)
/**
 * Average maintainability index across all scored files (0–100).
 */
maintainability_avg?: (number | null)
/**
 * Percentage of scored files with maintainability index below 70. Null if
 * file scores were not computed.
 */
maintainability_low_pct?: (number | null)
/**
 * Number of unused dependencies (dependencies + devDependencies + optional).
 */
unused_dep_count?: (number | null)
/**
 * Unused dependencies per 1,000 files. Null if dead code analysis did not
 * run.
 */
unused_deps_per_k_files?: (number | null)
/**
 * Number of circular dependency chains.
 */
circular_dep_count?: (number | null)
/**
 * Circular dependency chains per 1,000 files. Null if dead code analysis
 * did not run.
 */
circular_deps_per_k_files?: (number | null)
/**
 * Raw counts backing the percentages (for orientation header display).
 */
counts?: (VitalSignsCounts | null)
/**
 * Function size risk profile: percentage of functions in each size bin.
 */
unit_size_profile?: (RiskProfile | null)
/**
 * Functions above 60 LOC per 1,000 functions. Null if no functions
 * analyzed.
 */
functions_over_60_loc_per_k?: (number | null)
/**
 * Parameter count risk profile: percentage of functions in each param bin.
 */
unit_interfacing_profile?: (RiskProfile | null)
/**
 * 95th percentile fan-in across all files. Null if file scores not
 * computed.
 */
p95_fan_in?: (number | null)
/**
 * Percentage of files with fan-in above the project's p95 threshold.
 */
coupling_high_pct?: (number | null)
/**
 * Number of located prop-drilling chains (React/Preact props forwarded
 * unchanged through 3+ pass-through components). `None` unless the opt-in
 * `prop-drilling` rule is enabled (it defaults to off), so the small capped
 * penalty and the hotspot surface are dormant by default.
 */
prop_drilling_chain_count?: (number | null)
/**
 * The deepest located prop-drilling chain's depth (forwarding hops). `None`
 * when no chains were found or the rule is off. Descriptive context only.
 */
prop_drilling_max_depth?: (number | null)
/**
 * 95th-percentile DISTINCT-PARENTS render fan-in across React/Preact
 * components (the component-graph analogue of `p95_fan_in`, which percentiles
 * per-FILE module fan-in). `None` on non-React runs. Descriptive
 * blast-radius context, NOT a gate. Mirrors `compute_coupling_concentration`.
 */
p95_render_fan_in?: (number | null)
/**
 * Percentage of components whose render fan-in exceeds the project's
 * `max(p95, 10)` threshold (reuses the coupling-concentration floor; NO new
 * tunable constant). `None` on non-React runs. Mirrors `coupling_high_pct`.
 */
render_fan_in_high_pct?: (number | null)
/**
 * The single highest DISTINCT-PARENTS count across all components (the
 * headline blast-radius number: the most distinct render LOCATIONS any one
 * component is rendered from, the honest edit-ripple count). `render_sites`
 * (incl. repeats) is secondary per-component context, never the headline.
 * `None` on non-React runs. Descriptive context, no threshold.
 */
max_render_fan_in?: (number | null)
/**
 * The highest-fan-in React/Preact components, located (component name +
 * project-relative path + render-site / distinct-parent counts), sorted by
 * distinct parents (the honest headline axis) descending, tie-broken on
 * render sites descending, and capped at a small N. Lets a consumer see
 * WHICH component carries the headline `max_render_fan_in`, not just the
 * number. Empty (and omitted from JSON) on non-React runs, so the contract
 * stays byte-identical there. Descriptive blast-radius context, NOT a gate.
 */
top_render_fan_in?: RenderFanInTopComponent[]
/**
 * Total lines of code across all parsed modules.
 */
total_loc?: number
}
/**
 * Raw counts backing the vital signs percentages.
 *
 * Stored alongside `VitalSigns` in snapshots so that Phase 2b trend reporting
 * can decompose percentage changes into numerator vs denominator shifts.
 */
export interface VitalSignsCounts {
/**
 * Total number of discovered source files.
 */
total_files: number
/**
 * Total number of exports across all files.
 */
total_exports: number
dead_files: number
dead_exports: number
duplicated_lines?: (number | null)
total_lines?: (number | null)
files_scored?: (number | null)
total_deps: number
}
/**
 * Risk profile: percentage of functions in each risk bin.
 *
 * Bins are defined by thresholds that depend on the measured property:
 * - **Unit size**: low risk (1-15 LOC), medium risk (16-30), high risk (31-60), very high risk (>60)
 * - **Unit interfacing**: low risk (0-2 params), medium risk (3-4), high risk (5-6), very high risk (>=7)
 *
 * Percentages sum to approximately 100.0 (subject to rounding).
 */
export interface RiskProfile {
/**
 * Percentage of functions in the low-risk bin.
 */
low_risk: number
/**
 * Percentage of functions in the medium-risk bin.
 */
medium_risk: number
/**
 * Percentage of functions in the high-risk bin.
 */
high_risk: number
/**
 * Percentage of functions in the very-high-risk bin.
 */
very_high_risk: number
}
/**
 * One located high-fan-in React/Preact component for the descriptive
 * `top_render_fan_in` blast-radius list on [`VitalSigns`].
 *
 * The component-graph analogue of a high-fan-in module: `distinct_parents` is
 * the HEADLINE axis (the honest count of distinct parent components / render
 * LOCATIONS that render this component), `render_sites` is secondary "incl.
 * repeats" context (every JSX render SITE, so a single parent rendering one
 * child five times is five sites but one parent). Undercount-safe like the
 * underlying metric: a child rendered via a JSX spread / dynamic /
 * member-expression tag resolves to no component, so a true high-fan-in
 * component can only be undersold.
 */
export interface RenderFanInTopComponent {
/**
 * The component name.
 */
component: string
/**
 * Project-relative path of the file declaring the component. Serialized with
 * forward slashes (same serializer the other relativized health paths use).
 */
path: string
/**
 * Total JSX render SITES that resolve to this component across the project.
 * SECONDARY "incl. repeats" context, not the headline (see `distinct_parents`).
 */
render_sites: number
/**
 * Distinct `(parent_file, parent_component)` keys that render this component.
 * The HEADLINE blast-radius axis: distinct render LOCATIONS.
 */
distinct_parents: number
}
export interface HealthScore {
formula_version: number
score: number
grade: string
penalties: HealthScorePenalties
}
/**
 * Per-component penalty breakdown for the health score.
 */
export interface HealthScorePenalties {
dead_files?: (number | null)
dead_exports?: (number | null)
complexity: number
p90_complexity: number
maintainability?: (number | null)
hotspots?: (number | null)
unused_deps?: (number | null)
circular_deps?: (number | null)
unit_size?: (number | null)
coupling?: (number | null)
duplication?: (number | null)
/**
 * Small capped penalty for prop-drilling chains. `None` unless the opt-in
 * `prop-drilling` rule is enabled; sized like the coupling penalty (~5pt cap).
 */
prop_drilling?: (number | null)
}
/**
 * Per-file health score combining complexity, coupling, and dead code metrics.
 */
export interface FileHealthScore {
path: string
fan_in: number
fan_out: number
dead_code_ratio: number
complexity_density: number
maintainability_index: number
total_cyclomatic: number
total_cognitive: number
function_count: number
lines: number
crap_max: number
crap_above_threshold: number
}
/**
 * Static test coverage gaps derived from the module graph. Shows runtime files
 * and exports with no test dependency path.
 */
export interface CoverageGaps {
summary: CoverageGapSummary
/**
 * Runtime files with no test dependency path. Each entry carries its
 * own `actions` array via [`UntestedFileFinding`].
 */
files?: UntestedFileFinding[]
/**
 * Runtime exports with no test-reachable reference chain. Each entry
 * carries its own `actions` array via [`UntestedExportFinding`].
 */
exports?: UntestedExportFinding[]
}
/**
 * Aggregate coverage-gap counters for the current analysis scope.
 */
export interface CoverageGapSummary {
/**
 * Runtime-reachable files in scope.
 */
runtime_files: number
/**
 * Runtime-reachable files also reachable from tests.
 */
covered_files: number
/**
 * Percentage of runtime files that are test-reachable.
 */
file_coverage_pct: number
/**
 * Runtime files with no test dependency path.
 */
untested_files: number
/**
 * Runtime exports with no test-reachable reference chain.
 */
untested_exports: number
}
/**
 * Wire-shape envelope for an [`UntestedFile`] finding. Carries the bare
 * [`UntestedFile`] flattened in plus a typed `actions` array. The action
 * vec is computed at construction time using a project-root-relative path
 * so descriptions match `strip_root_prefix`'s post-pass output on the inner
 * `path` field. Schemars derives the merged shape natively; this retires
 * the `augment_finding_definition` graft for `UntestedFile`.
 */
export interface UntestedFileFinding {
/**
 * Absolute file path.
 */
path: string
/**
 * Number of value exports declared by the file.
 */
value_export_count: number
/**
 * Suggested next steps: an `add-tests` primary and a `suppress-file`
 * secondary. Always emitted (possibly empty for forward-compat).
 */
actions: UntestedFileAction[]
}
/**
 * Suggested action attached to an [`UntestedFile`] coverage-gap finding.
 *
 * `build_untested_file_actions` emits a two-entry array on every
 * untested-file item: an `add-tests` primary action (scaffold tests for
 * the runtime file) and a `suppress-file` action
 * (`// fallow-ignore-file coverage-gaps`). Both variants share the same
 * struct shape; the field that is populated (`note` for `add-tests`,
 * `comment` for `suppress-file`) depends on the `kind`.
 *
 * [`UntestedFile`]: ../../fallow-cli/src/health_types/coverage.rs
 */
export interface UntestedFileAction {
type: UntestedFileActionType
/**
 * Whether `fallow fix` can auto-apply this action. Today both
 * variants are manual.
 */
auto_fixable: boolean
/**
 * Human-readable description of the action.
 */
description: string
/**
 * Additional context for the `add-tests` variant (explains why no
 * test path reaches this file). Absent on `suppress-file`.
 */
note?: (string | null)
/**
 * The file-level comment to insert. Present on `suppress-file`
 * (`// fallow-ignore-file coverage-gaps`). Absent on `add-tests`.
 */
comment?: (string | null)
}
/**
 * Wire-shape envelope for an [`UntestedExport`] finding. Same pattern as
 * [`UntestedFileFinding`]: flattens the bare finding and carries a typed
 * `actions` array computed at construction time.
 */
export interface UntestedExportFinding {
/**
 * Absolute file path.
 */
path: string
/**
 * Export name.
 */
export_name: string
/**
 * 1-based source line.
 */
line: number
/**
 * 0-based source column.
 */
col: number
/**
 * Suggested next steps: an `add-test-import` primary and a
 * `suppress-file` secondary.
 */
actions: UntestedExportAction[]
}
/**
 * Suggested action attached to an [`UntestedExport`] coverage-gap
 * finding.
 *
 * `build_untested_export_actions` emits a two-entry array on every
 * untested-export item: an `add-test-import` primary action (import the
 * export from a test-reachable module) and a `suppress-file` action
 * (`// fallow-ignore-file coverage-gaps`). The export-specific variant
 * `add-test-import` reflects that a test-reachable reference chain, not
 * just any test coverage, is what closes the gap.
 *
 * [`UntestedExport`]: ../../fallow-cli/src/health_types/coverage.rs
 */
export interface UntestedExportAction {
type: UntestedExportActionType
/**
 * Whether `fallow fix` can auto-apply this action. Today both
 * variants are manual.
 */
auto_fixable: boolean
/**
 * Human-readable description of the action.
 */
description: string
/**
 * Additional context for the `add-test-import` variant (explains the
 * runtime-reachable / test-unreachable asymmetry). Absent on
 * `suppress-file`.
 */
note?: (string | null)
/**
 * The file-level comment to insert. Present on `suppress-file`
 * (`// fallow-ignore-file coverage-gaps`). Absent on
 * `add-test-import`.
 */
comment?: (string | null)
}
/**
 * Wire envelope for a single hotspot entry.
 *
 * Flattens [`HotspotEntry`] for wire continuity and adds the typed
 * `actions` list. The `#[serde(flatten)]` keeps each `hotspots[]` item
 * byte-identical to the pre-wrapper shape: inner fields (`path`,
 * `score`, `commits`, `weighted_commits`, ...) sit at the top level
 * alongside `actions`. Optional inner fields (`ownership`,
 * `is_test_path`) keep their original `skip_serializing_if` behaviour
 * because serde applies the flatten before the parent serializer runs.
 *
 * Construct via [`HotspotFinding::with_actions`] in the typical health
 * pipeline (the typed action builder operates on the inner
 * [`HotspotEntry`]) or via [`HotspotFinding::from`] for fixture and
 * test code.
 */
export interface HotspotFinding {
path: string
score: number
commits: number
weighted_commits: number
lines_added: number
lines_deleted: number
complexity_density: number
fan_in: number
trend: ChurnTrend
ownership?: (OwnershipMetrics | null)
is_test_path?: boolean
/**
 * Machine-actionable refactor and review hints. Always populated;
 * the list never empties because the action selector unconditionally
 * emits `refactor-file` plus `add-tests`. Ownership-derived variants
 * (`low-bus-factor`, `unowned-hotspot`, `ownership-drift`) are
 * appended when `--ownership` is active and the corresponding signal
 * fires.
 */
actions: HotspotAction[]
}
export interface OwnershipMetrics {
bus_factor: number
contributor_count: number
top_contributor: ContributorEntry
recent_contributors?: ContributorEntry[]
suggested_reviewers?: ContributorEntry[]
declared_owner?: (string | null)
unowned?: (boolean | null)
ownership_state: OwnershipState
drift: boolean
drift_reason?: (string | null)
}
export interface ContributorEntry {
identifier: string
format: ContributorIdentifierFormat
share: number
stale_days: number
commits: number
}
/**
 * Suggested action attached to a [`HotspotEntry`].
 *
 * The action list always begins with `refactor-file` plus `add-tests`.
 * Ownership-derived variants (`low-bus-factor`, `unowned-hotspot`,
 * `ownership-drift`) are appended only when `--ownership` is active AND
 * the corresponding signal fires for the hotspot.
 *
 * [`HotspotEntry`]: ../../fallow-cli/src/health_types/scores.rs
 */
export interface HotspotAction {
type: HotspotActionType
/**
 * Whether `fallow fix` can auto-apply this action. Today every
 * hotspot action is manual.
 */
auto_fixable: boolean
/**
 * Human-readable description of the action.
 */
description: string
/**
 * Additional context for the action. Absent on `low-bus-factor` when
 * the finding's description already carries the full ask (no
 * suggested reviewers and not a low-commit file).
 */
note?: (string | null)
/**
 * Suggested CODEOWNERS pattern. Present only on `unowned-hotspot`
 * actions. Derived per the [`heuristic`](Self::heuristic) field;
 * consumers should branch on [`heuristic`](Self::heuristic) rather
 * than assume a stable algorithm.
 */
suggested_pattern?: (string | null)
/**
 * Strategy used to derive [`suggested_pattern`](Self::suggested_pattern).
 * Reserved for future evolution (`codeowners-cluster`, etc.).
 */
heuristic?: (HotspotActionHeuristic | null)
}
export interface HotspotSummary {
since: string
min_commits: number
files_analyzed: number
files_excluded: number
shallow_clone: boolean
}
/**
 * Runtime coverage findings merged into the health report or emitted by
 * `fallow coverage analyze`. Present in health output when --runtime-coverage
 * is used. Shape mirrors the runtime coverage JSON contract; cloud mode
 * fetches runtime facts explicitly and merges them locally with AST/static
 * analysis.
 */
export interface RuntimeCoverageReport {
schema_version: RuntimeCoverageSchemaVersion
verdict: RuntimeCoverageReportVerdict
/**
 * All signals captured by post-processing. Independent of `verdict`,
 * which is the single most actionable signal under the current
 * context. Empty when the report is `Clean` and not under license
 * grace. Order is stable severity-descending.
 */
signals?: RuntimeCoverageSignal[]
summary: RuntimeCoverageSummary
/**
 * Surfaced runtime coverage findings (`safe_to_delete`, `review_required`,
 * `low_traffic`, `coverage_unavailable`). Omitted when empty. `active`
 * functions stay out of this list so the CLI output remains actionable.
 */
findings?: RuntimeCoverageFinding[]
/**
 * Top runtime functions by invocation count. Omitted when empty.
 */
hot_paths?: RuntimeCoverageHotPath[]
/**
 * First-class blast-radius entries for runtime-observed functions. Present
 * whenever runtime coverage analysis runs.
 */
blast_radius: RuntimeCoverageBlastRadiusEntry[]
/**
 * First-class production-importance entries for runtime-observed
 * functions. Present whenever runtime coverage analysis runs.
 */
importance: RuntimeCoverageImportanceEntry[]
/**
 * License/trial watermark for grace-mode output. Omitted when not
 * applicable.
 */
watermark?: (RuntimeCoverageWatermark | null)
/**
 * Non-fatal merge or coverage diagnostics. Omitted when empty.
 */
warnings?: RuntimeCoverageMessage[]
}
/**
 * Summary block mirroring `fallow_cov_protocol::Summary` (0.3 shape).
 */
export interface RuntimeCoverageSummary {
data_source: RuntimeCoverageDataSource
/**
 * Timestamp of the newest runtime payload included in the report. Null for
 * local single-capture artifacts that do not carry cloud receipt metadata.
 */
last_received_at?: (string | null)
/**
 * Number of functions the sidecar could observe in the V8 or Istanbul
 * dump.
 */
functions_tracked: number
/**
 * Tracked functions that received at least one invocation.
 */
functions_hit: number
/**
 * Tracked functions that were never invoked.
 */
functions_unhit: number
/**
 * Functions the sidecar could not track (lazy-parsed, worker thread,
 * dynamic code, unresolved source map).
 */
functions_untracked: number
/**
 * Ratio of functions_hit / functions_tracked, expressed as a percent.
 */
coverage_percent: number
/**
 * Total number of observed invocations across all functions. Denominator
 * for low-traffic classification.
 */
trace_count: number
/**
 * Days of observation covered by the supplied dump (Phase 2 local analysis
 * emits 0 , set by the beacon/cloud in Phase 3+).
 */
period_days: number
/**
 * Distinct deployments contributing to the supplied dump (Phase 2 local
 * analysis emits 0).
 */
deployments_seen: number
/**
 * Capture-quality telemetry. `None` for protocol-0.2 sidecars; protocol-0.3+
 * sidecars always populate it. Fuels the human-output short-window warning
 * and the quantified trial CTA, and is passed through to JSON consumers so
 * agent pipelines can surface the same signal.
 */
capture_quality?: (RuntimeCoverageCaptureQuality | null)
}
/**
 * Quality-of-capture signals emitted by the sidecar so the CLI can explain
 * short-window captures honestly instead of letting users blame the tool.
 */
export interface RuntimeCoverageCaptureQuality {
/**
 * Total observation window in seconds. Finer-grained than period_days
 * (which rounds up to whole days).
 */
window_seconds: number
/**
 * Number of distinct production instances that contributed to the dump.
 */
instances_observed: number
/**
 * True when the untracked-function ratio exceeds the sidecar's lazy-parse
 * threshold (30%). Signals that many untracked functions likely reflect
 * lazy-parsed code rather than unreachable code.
 */
lazy_parse_warning: boolean
/**
 * functions_untracked / functions_tracked as a percentage, rounded to 2
 * decimal places.
 */
untracked_ratio_percent: number
}
export interface RuntimeCoverageFinding {
/**
 * Per-finding suppression key of the form `fallow:prod:<hash>` (first 8 hex
 * of SHA-256(file + function + line + 'prod')). Hashes the current line, so
 * it changes when the function moves. Use this to suppress one finding.
 */
id: string
/**
 * Cross-surface join key of the form `fallow:fn:<hash>`
 * (`fallow_cov_protocol::function_identity_id`, hashes file + name +
 * start_line). The same function shares ONE value across findings, hot
 * paths, blast-radius, and importance entries (the per-finding `id` uses a
 * per-surface salt, so it differs by surface), and across V8, Istanbul,
 * and oxc producers (columns are excluded from the hash). Like `id`, it
 * changes when the function's file, name, or start line changes; it is a
 * cross-surface / cross-producer join key, not a line-move-immune one.
 * `null` when the producing surface (or an un-migrated cloud) supplied no
 * `FunctionIdentity`.
 */
stable_id?: (string | null)
/**
 * Content digest of the function's full-span source slice
 * (`fallow_cov_protocol::source_hash_for`: first 8 bytes of SHA-256 as 16
 * lowercase hex). Unlike `stable_id`, this is stable across line moves: a
 * moved-but-unedited function keeps the same value, so baselines can
 * suppress it after a pure line shift. `null` when the producing surface
 * supplied no `source_hash`.
 */
source_hash?: (string | null)
/**
 * File path relative to the project root.
 */
path: string
/**
 * Static function name as reported in the merged coverage result.
 */
function: string
/**
 * 1-indexed line number the function starts on.
 */
line: number
verdict: RuntimeCoverageVerdict
/**
 * Raw V8 invocation count. `None` when the function was untracked
 * (lazy-parsed, worker thread, or dynamic code).
 */
invocations?: (number | null)
confidence: RuntimeCoverageConfidence
evidence: RuntimeCoverageEvidence
/**
 * Suggested actions for this finding. Omitted when empty.
 */
actions?: RuntimeCoverageAction[]
}
/**
 * Supporting evidence for a finding (mirrors `fallow_cov_protocol::Evidence`).
 */
export interface RuntimeCoverageEvidence {
/**
 * `used` when the function is reachable in the module graph, `unused`
 * otherwise.
 */
static_status: string
/**
 * `covered` when the project's test suite hits this function,
 * `not_covered` otherwise.
 */
test_coverage: string
/**
 * `tracked` when V8 observed the function, `untracked` otherwise.
 */
v8_tracking: string
/**
 * Reason the function is untracked. Populated only when v8_tracking is
 * `untracked`. Values: `lazy_parsed`, `worker_thread`, `dynamic_eval`,
 * `unknown`.
 */
untracked_reason?: (string | null)
/**
 * Days of observation backing this finding.
 */
observation_days: number
/**
 * Distinct deployments backing this finding.
 */
deployments_observed: number
}
/**
 * Suggested follow-up action for a runtime coverage finding.
 */
export interface RuntimeCoverageAction {
/**
 * Action identifier, normalized to `type` in JSON output. Known values
 * emitted by `fallow coverage analyze`: `delete-cold-code`
 * (verdict=safe_to_delete), `review-runtime` (verdict=review_required).
 * The sidecar may emit additional protocol-specific identifiers;
 * consumers should treat unknown values as forward-compat extensions.
 */
type: string
description: string
/**
 * Whether fallow can apply this action automatically.
 */
auto_fixable: boolean
}
export interface RuntimeCoverageHotPath {
/**
 * Stable content-hash ID of the form `fallow:hot:<hash>`.
 */
id: string
/**
 * Cross-surface join key (`fallow:fn:<hash>`) for the hot function. Stable
 * across line moves; shared with the same function's findings / blast /
 * importance entries. `null` when no `FunctionIdentity` was supplied.
 */
stable_id?: (string | null)
/**
 * File path relative to the project root.
 */
path: string
/**
 * Function name for the hot path.
 */
function: string
/**
 * 1-indexed line number the function starts on.
 */
line: number
/**
 * 1-indexed line the function ends on (inclusive). Mirrors
 * `fallow_cov_protocol::HotPath::end_line` (added in protocol 0.5).
 * Older 0.4-shape sidecars omit the field on the wire; serde defaults
 * to `0`, which the line-overlap filter MUST treat as a single-line
 * range (`line..=line`) rather than a span.
 */
end_line: number
/**
 * Observed invocation count for the hot path.
 */
invocations: number
/**
 * Percentile rank over this response's hot-path distribution. `100`
 * means the busiest, `0` means the quietest function that qualified.
 */
percentile: number
/**
 * Suggested actions for this hot path (e.g., review-on-change). Omitted
 * when empty.
 */
actions?: RuntimeCoverageAction[]
}
export interface RuntimeCoverageBlastRadiusEntry {
/**
 * Stable content-hash ID of the form `fallow:blast:<hash>`.
 */
id: string
/**
 * Cross-surface join key (`fallow:fn:<hash>`) for the function. Stable
 * across line moves; shared with the same function's findings / hot-path /
 * importance entries. `null` when no `FunctionIdentity` was supplied.
 */
stable_id?: (string | null)
/**
 * File path relative to the project root.
 */
file: string
/**
 * Function name for the blast-radius entry.
 */
function: string
/**
 * 1-indexed line number the function starts on.
 */
line: number
/**
 * Static caller count from the module graph.
 */
caller_count: number
/**
 * Caller reach weighted by observed runtime traffic.
 */
caller_count_weighted_by_traffic: number
/**
 * Distinct deploy SHAs that touched the function in the observation
 * window. Cloud mode only; omitted in local mode.
 */
deploys_touched?: (number | null)
risk_band: RuntimeCoverageRiskBand
}
export interface RuntimeCoverageImportanceEntry {
/**
 * Stable content-hash ID of the form `fallow:importance:<hash>`.
 */
id: string
/**
 * Cross-surface join key (`fallow:fn:<hash>`) for the function. Stable
 * across line moves; shared with the same function's findings / hot-path /
 * blast-radius entries. `null` when no `FunctionIdentity` was supplied.
 */
stable_id?: (string | null)
/**
 * File path relative to the project root.
 */
file: string
/**
 * Function name for the importance entry.
 */
function: string
/**
 * 1-indexed line number the function starts on.
 */
line: number
/**
 * Observed invocation count for this function.
 */
invocations: number
/**
 * Cyclomatic complexity from the static health pipeline.
 */
cyclomatic: number
/**
 * Number of CODEOWNERS owners matched for this file. Zero means no owner
 * was resolved.
 */
owner_count: number
/**
 * 0-100 explainable score from log-scaled traffic, capped complexity
 * weight, and ownership-risk weight.
 */
importance_score: number
/**
 * Templated one-sentence explanation for the score.
 */
reason: string
}
export interface RuntimeCoverageMessage {
code: string
/**
 * Human-readable warning message.
 */
message: string
}
/**
 * Combined coverage, runtime, complexity, and change-scope verdicts.
 */
export interface CoverageIntelligenceReport {
schema_version: CoverageIntelligenceSchemaVersion
verdict: CoverageIntelligenceVerdict
summary: CoverageIntelligenceSummary
findings: CoverageIntelligenceFinding[]
}
/**
 * Aggregate metadata for coverage-intelligence output.
 */
export interface CoverageIntelligenceSummary {
findings: number
risky_changes: number
high_confidence_deletes: number
review_required: number
refactor_carefully: number
skipped_ambiguous_matches: number
}
/**
 * One combined coverage-intelligence finding.
 */
export interface CoverageIntelligenceFinding {
/**
 * Stable finding ID of the form `fallow:coverage-intel:<hash>`.
 */
id: string
/**
 * File path relative to the project root.
 */
path: string
/**
 * Function or export identity when known.
 */
identity?: (string | null)
/**
 * 1-indexed source line.
 */
line: number
verdict: CoverageIntelligenceVerdict
signals: CoverageIntelligenceSignal[]
recommendation: CoverageIntelligenceRecommendation
confidence: CoverageIntelligenceConfidence
related_ids?: string[]
evidence: CoverageIntelligenceEvidence
actions: CoverageIntelligenceAction[]
}
/**
 * Compact evidence values that led to a recommendation.
 */
export interface CoverageIntelligenceEvidence {
coverage_pct?: (number | null)
crap?: (number | null)
runtime_verdict?: (string | null)
invocations?: (number | null)
static_status?: (string | null)
test_coverage?: (string | null)
changed?: boolean
ownership_state?: (string | null)
match_confidence: CoverageIntelligenceMatchConfidence
}
/**
 * Machine-actionable next step for a coverage-intelligence finding.
 */
export interface CoverageIntelligenceAction {
/**
 * Action identifier, normalized to `type` in JSON output.
 */
type: string
description: string
/**
 * Whether fallow can apply this action automatically.
 */
auto_fixable: boolean
}
/**
 * A function exceeding the very-high-risk size threshold (>60 LOC).
 */
export interface LargeFunctionEntry {
path: string
name: string
line: number
line_count: number
}
/**
 * Wire envelope for a single refactoring target.
 *
 * Flattens [`RefactoringTarget`] for wire continuity and adds the typed
 * `actions` list. The `#[serde(flatten)]` keeps each `targets[]` item
 * byte-identical to the pre-wrapper shape: inner fields (`path`,
 * `priority`, `efficiency`, `recommendation`, `category`, ...) sit at
 * the top level alongside `actions`. Optional inner fields (`factors`,
 * `evidence`) keep their original `skip_serializing_if` behaviour.
 *
 * Construct via [`RefactoringTargetFinding::with_actions`] in the
 * typical health pipeline or via [`RefactoringTargetFinding::from`] for
 * fixture and test code.
 */
export interface RefactoringTargetFinding {
/**
 * Absolute file path (stripped to relative in output).
 */
path: string
/**
 * Priority score (0–100, higher = more urgent).
 */
priority: number
/**
 * Efficiency score (priority / effort). Higher = better quick-win value.
 * Surfaces low-effort, high-priority targets first.
 */
efficiency: number
/**
 * One-line actionable recommendation.
 */
recommendation: string
category: RecommendationCategory
effort: EffortEstimate
confidence: Confidence
/**
 * Contributing factors that triggered this recommendation. Empty array
 * omitted from JSON.
 */
factors?: ContributingFactor[]
/**
 * Structured evidence linking to specific analysis data.
 */
evidence?: (TargetEvidence | null)
/**
 * Machine-actionable refactoring and suppression hints. Always
 * populated; the list never empties because the action selector
 * unconditionally emits `apply-refactoring`. A trailing
 * `suppress-line` is appended only when the target carries
 * [`RefactoringTarget::evidence`] linking to specific functions.
 */
actions: RefactoringTargetAction[]
}
/**
 * A contributing factor that triggered or strengthened a recommendation.
 */
export interface ContributingFactor {
/**
 * Metric name (matches JSON field names: `"fan_in"`, `"dead_code_ratio"`, etc.).
 */
metric: string
/**
 * Raw metric value for programmatic use.
 */
value: number
/**
 * Threshold that was exceeded.
 */
threshold: number
/**
 * Human-readable explanation.
 */
detail: string
}
/**
 * Evidence linking a target back to specific analysis data.
 *
 * Provides enough detail for an AI agent to act on a recommendation
 * without a second tool call.
 */
export interface TargetEvidence {
/**
 * Names of unused exports (populated for `RemoveDeadCode` targets).
 */
unused_exports?: string[]
/**
 * Complex functions with line numbers and cognitive scores (populated for `ExtractComplexFunctions`).
 */
complex_functions?: EvidenceFunction[]
/**
 * Files forming the import cycle (populated for `BreakCircularDependency` targets).
 */
cycle_path?: string[]
/**
 * Files that directly import this target, with imported and local symbols.
 */
direct_callers?: DirectCallerEvidence[]
/**
 * Other duplicate-code instances that share a clone group with this target.
 */
clone_siblings?: CloneSiblingEvidence[]
}
/**
 * A function referenced in target evidence.
 */
export interface EvidenceFunction {
/**
 * Function name.
 */
name: string
/**
 * 1-based line number.
 */
line: number
/**
 * Cognitive complexity score.
 */
cognitive: number
}
/**
 * A direct importer referenced in target evidence.
 */
export interface DirectCallerEvidence {
/**
 * File that directly imports the target.
 */
path: string
/**
 * Symbols imported from the target by this file.
 */
symbols?: DirectCallerSymbolEvidence[]
}
/**
 * Symbol details for a direct importer.
 */
export interface DirectCallerSymbolEvidence {
/**
 * Imported binding name.
 */
imported: string
/**
 * Local binding name in the importing file.
 */
local: string
/**
 * Whether the import is type-only.
 */
type_only: boolean
}
/**
 * A duplicate-code sibling referenced in target evidence.
 */
export interface CloneSiblingEvidence {
/**
 * File containing the sibling clone instance.
 */
path: string
/**
 * 1-based start line of the sibling clone.
 */
start_line: number
/**
 * 1-based end line of the sibling clone.
 */
end_line: number
/**
 * Stable duplicate-group handle, matching `dupes --trace dup:<id>`.
 */
fingerprint: string
}
/**
 * Suggested action attached to a [`RefactoringTarget`].
 *
 * The list always begins with `apply-refactoring`. A trailing
 * `suppress-line` is appended only when the target carries `evidence`
 * linking to specific functions (e.g., `extract_complex_functions`,
 * `add_test_coverage`).
 *
 * Unlike [`HealthFindingAction`], the `suppress-line` variant emitted
 * here does NOT carry a `placement` field: the parent
 * [`RefactoringTarget`] points at a file (not a specific function
 * declaration site), so a per-line placement hint would have no
 * referent. Consumers that want the placement metadata should follow
 * the target's `evidence.complex_functions` back to the matching
 * `ComplexityViolation` and read placement from THAT action instead.
 *
 * [`RefactoringTarget`]: ../../fallow-cli/src/health_types/targets.rs
 */
export interface RefactoringTargetAction {
type: RefactoringTargetActionType
/**
 * Whether `fallow fix` can auto-apply this action. Today both
 * variants are manual.
 */
auto_fixable: boolean
/**
 * Human-readable description of the action. For `apply-refactoring`
 * this is the target's own `recommendation` string; for
 * `suppress-line` it is the suppression prompt.
 */
description: string
/**
 * Recommendation category for `apply-refactoring` actions. Mirrors
 * the parent target's
 * [`category`](../../fallow-cli/src/health_types/targets.rs.html)
 * field so consumers can route on the action alone.
 */
category?: (string | null)
/**
 * The inline comment to insert. Present on `suppress-line` actions
 * when evidence exists.
 */
comment?: (string | null)
}
/**
 * Adaptive thresholds used for refactoring target scoring.
 *
 * Derived from the project's metric distribution (percentile-based with floors).
 * Exposed in JSON output so consumers can interpret scores in context.
 */
export interface TargetThresholds {
/**
 * Fan-in saturation point for priority formula (p95, floor 5).
 */
fan_in_p95: number
/**
 * Fan-in moderate threshold for contributing factors (p75, floor 3).
 */
fan_in_p75: number
/**
 * Fan-out saturation point for priority formula (p95, floor 8).
 */
fan_out_p95: number
/**
 * Fan-out high threshold for rules and contributing factors (p90, floor 5).
 */
fan_out_p90: number
}
/**
 * Trend comparison between the current run and a previous snapshot. Shows
 * per-metric deltas with directional indicators.
 */
export interface HealthTrend {
compared_to: TrendPoint
/**
 * Per-metric deltas.
 */
metrics: TrendMetric[]
/**
 * Number of snapshots found in the snapshot directory.
 */
snapshots_loaded: number
overall_direction: TrendDirection
}
/**
 * A reference to a snapshot used in trend comparison.
 */
export interface TrendPoint {
/**
 * ISO 8601 timestamp of the snapshot.
 */
timestamp: string
/**
 * Git SHA at time of snapshot.
 */
git_sha?: (string | null)
/**
 * Health score from the snapshot (stored, not re-derived).
 */
score?: (number | null)
/**
 * Letter grade from the snapshot.
 */
grade?: (string | null)
/**
 * Coverage model used for CRAP computation in this snapshot.
 */
coverage_model?: (CoverageModel | null)
/**
 * Schema version of the compared snapshot.
 */
snapshot_schema_version?: (number | null)
}
/**
 * A single metric's trend between two snapshots.
 */
export interface TrendMetric {
/**
 * Metric identifier (e.g., `"score"`, `"dead_file_pct"`).
 */
name: string
/**
 * Human-readable label (e.g., `"Health Score"`, `"Dead Files"`).
 */
label: string
/**
 * Previous value (from snapshot).
 */
previous: number
/**
 * Current value (from this run).
 */
current: number
/**
 * Absolute change (current − previous).
 */
delta: number
direction: TrendDirection
/**
 * Unit for display (e.g., `"%"`, `""`, `"pts"`).
 */
unit: string
/**
 * Raw count from previous snapshot (for JSON consumers).
 */
previous_count?: (TrendCount | null)
/**
 * Raw count from current run (for JSON consumers).
 */
current_count?: (TrendCount | null)
}
/**
 * Raw numerator/denominator for a percentage metric.
 */
export interface TrendCount {
/**
 * The numerator (e.g., dead files count).
 */
value: number
/**
 * The denominator (e.g., total files).
 */
total: number
}
/**
 * Auditable breadcrumb recording when health-finding `suppress-line`
 * action hints were omitted from the report.
 *
 * Set at construction time on [`HealthReport::actions_meta`] (and on
 * each [`HealthGroup::actions_meta`](crate::health_types::HealthGroup)
 * when grouped) by the report builder, derived from the active
 * [`HealthActionContext`]. Lets consumers see "where did the
 * suppress-line hints go?" without having to grep the config or CLI
 * history.
 *
 * Stable `reason` codes:
 * - `baseline-active`: a baseline is active and inline ignores would
 *   become dead annotations once the baseline regenerates.
 * - `config-disabled`: `health.suggestInlineSuppression` is `false`.
 * - `unspecified`: the caller did not record a reason.
 */
export interface HealthActionsMeta {
/**
 * Always `true` when the breadcrumb is emitted. Absent from the wire
 * when no suppression occurred.
 */
suppression_hints_omitted: boolean
/**
 * Stable code describing why the suppression occurred.
 */
reason: string
/**
 * Scope of the omission. Always `"health-findings"` today.
 */
scope: string
}
/**
 * Structural CSS analytics surfaced by `fallow health --css`.
 */
export interface CssAnalyticsReport {
/**
 * Stylesheets with at least one structurally notable rule, in scan order.
 */
files: CssFileAnalytics[]
summary: CssAnalyticsSummary
/**
 * Vue SFCs whose `<style scoped>` defines classes used nowhere else in the
 * component (cleanup candidates).
 */
scoped_unused?: ScopedUnusedClasses[]
/**
 * `@keyframes` defined but referenced via no `animation` / `animation-name`
 * in any stylesheet, with the stylesheet that defines them (cleanup
 * candidates; an animation name can still be applied from JavaScript).
 * The "defined-but-unused" direction.
 */
unreferenced_keyframes?: UnreferencedKeyframes[]
/**
 * Animation references (`animation` / `animation-name`) to a `@keyframes`
 * name that is defined in NO stylesheet anywhere in the project, with the
 * first stylesheet that references them. The "used-but-undefined" direction
 * (the inverse of `unreferenced_keyframes`): usually a typo or a removed
 * animation, occasionally a `@keyframes` defined in CSS-in-JS (which the
 * CSS parser never sees). Conservative candidates, never gated findings.
 */
undefined_keyframes?: UndefinedKeyframes[]
/**
 * Groups of style rules across the project that share an identical
 * declaration block (4+ declarations, sorted and `!important`-aware),
 * grouped by content: copy-paste consolidation candidates (fallow's
 * duplication signal applied to CSS). Sorted by estimated savings
 * descending.
 */
duplicate_declaration_blocks?: CssDuplicateBlock[]
/**
 * Tailwind arbitrary-value utilities (`w-[13px]`, `bg-[#abc]`) found in
 * markup, which hardcode a one-off value instead of a configured scale
 * token (design-token bypass). Present only when the project uses Tailwind.
 * Sorted by use count descending. Candidates, not findings: an arbitrary
 * value is sometimes the right call.
 */
tailwind_arbitrary_values?: TailwindArbitraryValue[]
/**
 * Unused CSS at-rule entities: an `@property` registered but never read via
 * `var()` in any stylesheet, or an `@layer` declared but never populated by
 * a block. Cleanup candidates (an `@property` can be read from JS; a layer
 * can be populated via `@import layer()`). Located by first definition.
 */
unused_at_rules?: UnusedAtRule[]
/**
 * Static `class` / `className` tokens in markup that match no CSS class
 * defined anywhere in the project AND are one edit away from a class that
 * IS defined (a likely typo or stale rename, with the suggested class). The
 * CSS analogue of an unresolved import; the near-miss restriction keeps it
 * near-zero false-positive (Tailwind utilities and third-party classes are
 * not one edit from an authored class). Candidates, never gated: the token
 * could be defined in CSS-in-JS or an external stylesheet the parser never
 * sees. Sorted by `(path, line, class)`.
 */
unresolved_class_references?: UnresolvedClassReference[]
/**
 * Global CSS classes (defined in a plain `.css`/`.scss` rule) whose literal
 * name is referenced by NO in-project markup, static or dynamic (the CSS
 * analogue of an unused export). Heavily gated to stay near-zero-false-
 * positive: emitted only when the project is plain-CSS-dominant, the
 * stylesheet is locally consumed (not a published design-system surface),
 * and the whole project is in scope. Candidates, never gated findings: the
 * class may be used by an HTML email, server template, CMS, or Markdown the
 * parser never scans. Sorted by `(path, line, class)`.
 */
unreferenced_css_classes?: UnreferencedCssClass[]
/**
 * `@font-face` families declared in a stylesheet but referenced by no
 * `font-family` anywhere in the project: a dead web-font payload (the font
 * file is downloaded but never applied). Located at the declaring
 * stylesheet. Cleanup candidates: the family could be applied from inline
 * styles or set via JavaScript. Sorted by `(path, family)`.
 */
unused_font_faces?: UnusedFontFace[]
/**
 * Tailwind v4 `@theme` design tokens (`--color-brand`, `--radius-card`)
 * defined in a stylesheet but used by no generated utility, `var()` read,
 * `@apply`, or arbitrary value anywhere in the project: dead design tokens
 * (the `unused-export` of the token era). Present only when the project is
 * Tailwind v4 (a `tailwindcss` dependency plus at least one `@theme` block)
 * and not a plugin / published-library / partial-scope run. Candidates,
 * never gated findings: the token may be consumed by a Tailwind plugin or a
 * downstream repo. Sorted by `(path, line, token)`.
 */
unused_theme_tokens?: UnusedThemeToken[]
/**
 * The project authors `font-size` values in several units (`px`, `rem`,
 * `em`, `%`), with a per-unit distinct-value count: a type-scale
 * inconsistency smell (mixing `px` and `rem` for type works against
 * user-zoom accessibility). Present only above a conservative floor.
 * Advisory candidate, never gated: the spread can be intentional (fixed
 * chrome in `px`, body type in `rem`).
 *
 * Color-notation mixing (hex vs rgb vs hsl) is deliberately NOT surfaced:
 * the CSS parser canonicalizes every legacy sRGB notation to hex before
 * fallow sees the value, so the authored distinction is already gone and
 * cannot be recovered without a separate raw-token pass.
 */
font_size_unit_mix?: (CssNotationConsistency | null)
}
/**
 * Per-stylesheet CSS analytics.
 */
export interface CssFileAnalytics {
/**
 * Project-root-relative, forward-slash path.
 */
path: string
analytics: CssAnalytics
}
/**
 * Stylesheet-level structural CSS analytics, computed from the parsed CSS
 * syntax tree. Feeds `fallow health` penalty weights and located findings,
 * never a standalone CSS score.
 */
export interface CssAnalytics {
/**
 * Total declarations across every style rule (normal plus `!important`).
 */
total_declarations: number
/**
 * Total `!important` declarations across every style rule.
 */
important_declarations: number
/**
 * Number of style rules.
 */
rule_count: number
/**
 * Number of style rules with no declarations.
 */
empty_rule_count: number
/**
 * Deepest style-rule nesting depth observed (0 = no nesting).
 */
max_nesting_depth: number
/**
 * Rules that crossed the structural floor, in source order. Bounded; see
 * [`Self::notable_truncated`]. The scalar aggregates above always reflect
 * the full stylesheet regardless of truncation.
 */
notable_rules: CssRuleMetric[]
/**
 * `true` when more rules crossed the structural floor than `notable_rules`
 * retains (compiled utility CSS can emit thousands of `!important` rules),
 * so consumers can note that per-rule findings were capped.
 */
notable_truncated: boolean
/**
 * Distinct color VALUES in the stylesheet, sorted (a palette-size /
 * design-token-sprawl signal). The parser canonicalizes notation, so the
 * authored format is NOT preserved: `red`, `#f00`, `#ff0000`, and
 * `rgb(255,0,0)` all collapse to one entry, and every legacy sRGB notation
 * renders as hex. Notation-MIXING (hex vs rgb vs hsl) is therefore not
 * detectable from this set; it would need a separate raw-token pass.
 */
colors: string[]
/**
 * Distinct `font-size` declaration values in the stylesheet, sorted.
 */
font_sizes: string[]
/**
 * Distinct `z-index` declaration values in the stylesheet, sorted.
 */
z_indexes: string[]
/**
 * Distinct `box-shadow` declaration values in the stylesheet, sorted. A
 * high count signals an uncontrolled shadow scale (design-token sprawl).
 */
box_shadows: string[]
/**
 * Distinct `border-radius` declaration values in the stylesheet, sorted.
 */
border_radii: string[]
/**
 * Distinct `line-height` declaration values in the stylesheet, sorted.
 */
line_heights: string[]
/**
 * Distinct custom properties (`--x`) DEFINED in the stylesheet, sorted.
 */
defined_custom_properties: string[]
/**
 * Distinct custom properties REFERENCED via `var()` in the stylesheet.
 */
referenced_custom_properties: string[]
/**
 * Distinct `@keyframes` names DEFINED in the stylesheet, sorted.
 */
defined_keyframes: string[]
/**
 * Distinct `@keyframes` names REFERENCED via `animation` / `animation-name`.
 */
referenced_keyframes: string[]
/**
 * Distinct custom properties REGISTERED via an `@property` rule, sorted.
 */
registered_custom_properties: string[]
/**
 * Distinct cascade layers DECLARED (via `@layer a, b;` statements or named
 * `@layer a { }` blocks), sorted.
 */
declared_layers: string[]
/**
 * Distinct cascade layers POPULATED by a named `@layer a { }` block, sorted.
 * A layer declared but never populated (and not imported into) is a
 * cleanup candidate.
 */
populated_layers: string[]
/**
 * Distinct font families DECLARED by an `@font-face` rule in the stylesheet,
 * sorted. A declared family referenced by no `font-family` anywhere is a
 * dead web-font payload (cleanup candidate).
 */
defined_font_faces: string[]
/**
 * Distinct font families REFERENCED via `font-family` / `font` in the
 * stylesheet, sorted (generic keywords like `serif` excluded).
 */
referenced_font_families: string[]
}
/**
 * Structural CSS metrics for a single style rule, computed from the parsed CSS
 * syntax tree. A rule is recorded only when it crosses a structural floor (an
 * id selector, a complex selector, a `!important` declaration, or deep
 * nesting), so the vector stays bounded on normal stylesheets.
 *
 * Not persisted in the extraction cache: `fallow health` computes these
 * on demand from the CSS source, so there is no `bitcode` derive.
 */
export interface CssRuleMetric {
/**
 * 1-based line of the rule's first selector.
 */
line: number
/**
 * 1-based column of the rule's first selector.
 */
col: number
/**
 * Specificity component `a` (id selectors), max across the rule's selectors.
 */
specificity_a: number
/**
 * Specificity component `b` (class / attribute / pseudo-class selectors).
 */
specificity_b: number
/**
 * Specificity component `c` (type / pseudo-element selectors).
 */
specificity_c: number
/**
 * Largest selector component count across the rule's selector list.
 */
complexity: number
/**
 * Declaration count in the rule (normal plus `!important`).
 */
declaration_count: number
/**
 * `!important` declaration count in the rule.
 */
important_count: number
/**
 * Style-rule nesting depth (0 = top level).
 */
nesting_depth: number
}
/**
 * Project-wide CSS analytics aggregates across every analyzed stylesheet
 * (including stylesheets with no notable rule, which are not listed
 * individually).
 */
export interface CssAnalyticsSummary {
/**
 * Stylesheets analyzed (standard CSS only; SCSS is skipped).
 */
files_analyzed: number
/**
 * Total style rules across analyzed stylesheets.
 */
total_rules: number
/**
 * Total declarations across analyzed stylesheets.
 */
total_declarations: number
/**
 * Total `!important` declarations across analyzed stylesheets.
 */
important_declarations: number
/**
 * Total empty style rules across analyzed stylesheets.
 */
empty_rules: number
/**
 * Deepest style-rule nesting depth observed across analyzed stylesheets.
 */
max_nesting_depth: number
/**
 * Distinct color values (authored form) across the whole codebase. A high
 * count signals an uncontrolled palette (design-token sprawl).
 */
unique_colors: number
/**
 * Distinct `font-size` values across the whole codebase.
 */
unique_font_sizes: number
/**
 * Distinct `z-index` values across the whole codebase.
 */
unique_z_indexes: number
/**
 * Distinct `box-shadow` values across the whole codebase (shadow-scale sprawl).
 */
unique_box_shadows: number
/**
 * Distinct `border-radius` values across the whole codebase (radius-scale sprawl).
 */
unique_border_radii: number
/**
 * Distinct `line-height` values across the whole codebase (type-scale sprawl).
 */
unique_line_heights: number
/**
 * Distinct custom properties (`--x`) defined anywhere in the codebase.
 */
custom_properties_defined: number
/**
 * Custom properties defined but never referenced via `var()` in any
 * stylesheet (the defined-but-unused direction). These are cleanup
 * CANDIDATES, not confirmed dead: a property may still be read or set from
 * JavaScript or inline HTML styles.
 */
custom_properties_unreferenced: number
/**
 * Distinct custom properties referenced via `var()` that are defined in no
 * stylesheet anywhere (the used-but-undefined direction). A COUNT only, not
 * a located list: a `var(--x)` with no CSS definition is extremely common
 * in JavaScript-driven theming and design-token libraries, so locating
 * these would be net-noise. The count is an architecture signal (how much
 * of the `var()` surface is resolved outside CSS), not a finding.
 */
custom_properties_undefined: number
/**
 * Distinct `@keyframes` defined anywhere in the codebase.
 */
keyframes_defined: number
/**
 * `@keyframes` defined but never referenced via `animation` /
 * `animation-name` in any stylesheet (the defined-but-unused direction;
 * cleanup CANDIDATES; an animation name can still be applied from
 * JavaScript).
 */
keyframes_unreferenced: number
/**
 * Distinct animation names referenced via `animation` / `animation-name`
 * that resolve to no `@keyframes` definition anywhere (the used-but-
 * undefined direction). Located in `undefined_keyframes`; usually a typo or
 * a removed animation.
 */
keyframes_undefined: number
/**
 * Total Vue `<style scoped>` classes used nowhere else in their component
 * (cleanup candidates), across all SFCs.
 */
scoped_unused_classes: number
/**
 * Number of distinct declaration blocks (4+ declarations) that appear in
 * two or more rules across the project (copy-paste consolidation
 * candidates). Located in `duplicate_declaration_blocks`.
 */
duplicate_declaration_blocks: number
/**
 * Total declarations removable by consolidating every duplicate block:
 * the sum of `(occurrence_count - 1) * declaration_count` across groups.
 */
duplicate_declarations_total: number
/**
 * Distinct Tailwind arbitrary-value tokens used in markup (design-token
 * bypass). Zero when the project does not use Tailwind. Located in
 * `tailwind_arbitrary_values`.
 */
tailwind_arbitrary_values: number
/**
 * Total Tailwind arbitrary-value occurrences across markup.
 */
tailwind_arbitrary_value_uses: number
/**
 * `@property` registrations never referenced via `var()` in any stylesheet
 * (located in `unused_at_rules`). Cleanup candidates.
 */
unused_property_registrations: number
/**
 * Cascade layers declared but never populated by a block (located in
 * `unused_at_rules`). Cleanup candidates.
 */
unused_layers: number
/**
 * Static markup class tokens that match no defined CSS class but are one
 * edit from a defined class (likely typos / stale renames). Located in
 * `unresolved_class_references`. Candidates, never gated.
 */
unresolved_class_references: number
/**
 * Global CSS classes defined in a stylesheet but referenced by no in-project
 * markup (located in `unreferenced_css_classes`). Heavily gated cleanup
 * candidates; zero on preprocessor-dominant or partial-scope runs.
 */
unreferenced_css_classes: number
/**
 * `@font-face` families declared but referenced by no `font-family` anywhere
 * (located in `unused_font_faces`). Dead web-font cleanup candidates.
 */
unused_font_faces: number
/**
 * Tailwind v4 `@theme` design tokens defined but used by no generated
 * utility, `var()`, `@apply`, or arbitrary value anywhere (located in
 * `unused_theme_tokens`). Dead-design-token cleanup candidates; zero when
 * the project is not Tailwind v4 or a plugin / published-library /
 * partial-scope run gated the scan out.
 */
unused_theme_tokens: number
/**
 * Number of distinct `font-size` units (`px` / `rem` / `em` / `%`) authored
 * across the codebase. Mixing units is a type-scale consistency smell,
 * broken out in `font_size_unit_mix`.
 */
font_size_units_used: number
/**
 * Number of analyzed stylesheets whose per-rule `notable_rules` list was
 * truncated at the per-file cap, so a consumer knows the per-rule detail is
 * incomplete without walking every file.
 */
notable_truncated_files: number
}
/**
 * A Vue SFC's `<style scoped>` classes that appear nowhere else in the
 * component (cleanup candidates).
 */
export interface ScopedUnusedClasses {
/**
 * Project-root-relative, forward-slash path to the SFC.
 */
path: string
/**
 * The scoped class names with no use elsewhere in the component, sorted.
 */
classes: string[]
/**
 * Read-only verification step(s) an agent can run before removing the
 * candidate. Always at least one entry, so consumers can iterate
 * `actions` uniformly across every finding type.
 */
actions: CssCandidateAction[]
}
/**
 * A read-only verification step attached to a CSS cleanup candidate.
 *
 * CSS candidates (unreferenced `@keyframes`, unused scoped classes) are never
 * auto-removed: an animation name can still be applied from JavaScript, and a
 * class can be assembled from a dynamic string binding. The action gives an
 * agent a machine-readable next step, mirroring the `actions` array carried by
 * every other health finding, plus an optional runnable probe to confirm the
 * candidate is genuinely unused before deleting it.
 */
export interface CssCandidateAction {
type: CssCandidateActionType
/**
 * Always `false`: CSS candidates are never auto-fixed (`fallow fix` does
 * not touch them) because the residual consumer may live outside CSS.
 */
auto_fixable: boolean
/**
 * Human-readable description of what to confirm before removing.
 */
description: string
/**
 * A runnable, read-only, placeholder-free token search that surfaces any
 * out-of-CSS use of the candidate. Absent when no shell-safe command can
 * be built (e.g. the residual risk is a dynamic string binding that a
 * single search cannot probe), in which case `description` is the guide.
 */
command?: (string | null)
}
/**
 * A `@keyframes` defined in a stylesheet but referenced by no animation in any
 * stylesheet (cleanup candidate).
 */
export interface UnreferencedKeyframes {
/**
 * The `@keyframes` name.
 */
name: string
/**
 * Project-root-relative, forward-slash path to the stylesheet that defines it.
 */
path: string
/**
 * Read-only verification step(s) an agent can run before removing the
 * candidate. Always at least one entry, so consumers can iterate
 * `actions` uniformly across every finding type.
 */
actions: CssCandidateAction[]
}
/**
 * An animation reference (`animation` / `animation-name`) to a `@keyframes`
 * name that is defined in no stylesheet anywhere in the project (the
 * "used-but-undefined" direction). Usually a typo or a removed animation;
 * occasionally a `@keyframes` defined in CSS-in-JS the CSS parser never sees.
 */
export interface UndefinedKeyframes {
/**
 * The referenced `@keyframes` name that resolves to no definition.
 */
name: string
/**
 * Project-root-relative, forward-slash path to the first stylesheet that
 * references it.
 */
path: string
/**
 * Read-only verification step(s) an agent can run before fixing the
 * reference. Always at least one entry, so consumers can iterate `actions`
 * uniformly across every finding type.
 */
actions: CssCandidateAction[]
}
/**
 * A group of style rules across the project that share an identical declaration
 * block: a copy-paste consolidation candidate (fallow's duplication signal
 * applied to CSS). Only blocks of 4+ declarations appearing in 2+ rules are
 * reported, so the signal stays a strong copy-paste indicator rather than
 * flagging legitimately-repeated small blocks.
 */
export interface CssDuplicateBlock {
/**
 * Declarations in the shared block.
 */
declaration_count: number
/**
 * Number of rules that share the block (always >= 2).
 */
occurrence_count: number
/**
 * Declarations removable by extracting the block into one shared rule:
 * `(occurrence_count - 1) * declaration_count`.
 */
estimated_savings: number
/**
 * The rules sharing the block, sorted by `(path, line)`.
 */
occurrences: CssBlockOccurrence[]
/**
 * Read-only guidance step(s), so consumers can iterate `actions`
 * uniformly across every finding type. Always at least one entry.
 */
actions: CssCandidateAction[]
}
/**
 * One occurrence of a duplicate declaration block.
 */
export interface CssBlockOccurrence {
/**
 * Project-root-relative, forward-slash path to the stylesheet.
 */
path: string
/**
 * 1-based line of the rule's first selector.
 */
line: number
}
/**
 * A distinct Tailwind arbitrary-value utility token used in markup, with its
 * total use count and first location (a design-token-bypass candidate).
 */
export interface TailwindArbitraryValue {
/**
 * The `prefix-[value]` token (e.g. `w-[13px]`). Variant prefixes are
 * stripped, so `hover:w-[13px]` and `w-[13px]` aggregate under `w-[13px]`.
 */
value: string
/**
 * Total occurrences across all scanned markup files.
 */
count: number
/**
 * Project-root-relative, forward-slash path to the first file using it.
 */
path: string
/**
 * 1-based line of the first occurrence.
 */
line: number
/**
 * Read-only action(s): a find-all-occurrences search so the token can be
 * replaced with a scale token. Always at least one entry, so consumers can
 * iterate `actions` uniformly across every finding type.
 */
actions: CssCandidateAction[]
}
/**
 * An unused CSS at-rule entity (an `@property` registration with no `var()`
 * reference, or an `@layer` declaration never populated), located by its first
 * definition. A cleanup candidate, never a gated finding.
 */
export interface UnusedAtRule {
type: UnusedAtRuleKind
/**
 * The entity name (`--x` for `@property`, the layer name for `@layer`).
 */
name: string
/**
 * Project-root-relative, forward-slash path to the first defining stylesheet.
 */
path: string
/**
 * Read-only verification step(s) before removal (parity with other findings).
 */
actions: CssCandidateAction[]
}
/**
 * A static `class` / `className` token in markup that matches no CSS class
 * defined anywhere in the project but is one edit away from a class that IS
 * defined (a likely typo or stale rename). The CSS analogue of an unresolved
 * import. A candidate, never a gated finding: the token could be defined in
 * CSS-in-JS or an external stylesheet the parser never sees.
 */
export interface UnresolvedClassReference {
/**
 * The static class token referenced in markup (no dot).
 */
class: string
/**
 * The defined CSS class one edit away: the likely intended class.
 */
suggestion: string
/**
 * Project-root-relative, forward-slash path to the markup file.
 */
path: string
/**
 * 1-based line of the `class` / `className` attribute.
 */
line: number
/**
 * Read-only verification step(s) before fixing the reference. Always at
 * least one entry, so consumers can iterate `actions` uniformly across
 * every finding type.
 */
actions: CssCandidateAction[]
}
/**
 * A global CSS class defined in a plain `.css`/`.scss` rule whose literal name
 * is referenced by no in-project markup (the CSS analogue of an unused export).
 * A heavily-gated candidate, never a gated finding: the class may be applied
 * from an HTML email, server template, CMS, or Markdown the parser never sees.
 */
export interface UnreferencedCssClass {
/**
 * The class name (no dot).
 */
class: string
/**
 * Project-root-relative, forward-slash path to the defining stylesheet.
 */
path: string
/**
 * 1-based line of the class's first definition.
 */
line: number
/**
 * Read-only verification step(s) before removing. Always at least one entry,
 * so consumers can iterate `actions` uniformly across every finding type.
 */
actions: CssCandidateAction[]
}
/**
 * An `@font-face` family declared in a stylesheet but referenced by no
 * `font-family` anywhere in the project: a dead web-font payload. A cleanup
 * candidate (the family could be applied from inline styles or JavaScript).
 */
export interface UnusedFontFace {
/**
 * The declared font family name (quotes stripped).
 */
family: string
/**
 * Project-root-relative, forward-slash path to the declaring stylesheet.
 */
path: string
/**
 * Read-only verification step(s) before removing. Always at least one entry,
 * so consumers can iterate `actions` uniformly across every finding type.
 */
actions: CssCandidateAction[]
}
/**
 * A Tailwind v4 `@theme` design token defined in a stylesheet whose generated
 * utility, `var()` reads, and arbitrary-value references appear nowhere in the
 * project: a dead design token (the `unused-export` of the token era). A
 * candidate, never a gated finding: the token could be consumed by a Tailwind
 * plugin, a published design-system surface, or a non-CSS-aware build step the
 * scan cannot see (those cases are gated out before this is emitted).
 */
export interface UnusedThemeToken {
/**
 * The full custom property as authored, including the `--` prefix
 * (`--color-brand`).
 */
token: string
/**
 * The Tailwind v4 theme namespace the token belongs to (`color`, `radius`,
 * `font-weight`, `breakpoint`, ...).
 */
namespace: string
/**
 * Project-root-relative, forward-slash path to the declaring stylesheet.
 */
path: string
/**
 * 1-based line of the token's definition inside the `@theme` block.
 */
line: number
/**
 * Read-only verification step(s) before removing. Always at least one entry,
 * so consumers can iterate `actions` uniformly across every finding type.
 */
actions: CssCandidateAction[]
}
/**
 * A design-token notation-consistency candidate: the distinct notations used
 * across the codebase for one value axis (today, length units on `font-size`),
 * with a per-notation distinct-value count. Emitted only above a floor, since
 * mixing notations for one axis is a "no single source of truth" smell.
 * Advisory: the action is "standardize on one notation", not a single search.
 */
export interface CssNotationConsistency {
/**
 * The value axis these notations describe, e.g. `"Colors"` or
 * `"Font sizes"`.
 */
axis: string
/**
 * Per-notation distinct-value counts, sorted by count descending then
 * notation name (so the dominant notation is first and ties are stable).
 */
notations: CssNotationCount[]
/**
 * Read-only guidance step(s), so consumers can iterate `actions` uniformly
 * across every candidate type. Always at least one entry.
 */
actions: CssCandidateAction[]
}
/**
 * One notation bucket and the count of distinct values authored in it.
 */
export interface CssNotationCount {
/**
 * The notation family, e.g. `"hex"`, `"rgb"`, `"hsl"`, `"modern"`, `"px"`,
 * `"rem"`, `"em"`, `"%"`.
 */
notation: string
/**
 * Distinct values authored in this notation across the codebase.
 */
count: number
}
/**
 * Envelope emitted by `fallow explain <issue-type> --format json`.
 *
 * Standalone rule explanation. This command does not run project analysis
 * and intentionally returns a compact object without `schema_version` /
 * `version` metadata; consumers that need those should call any other
 * fallow JSON-producing command.
 */
export interface ExplainOutput {
id: string
name: string
summary: string
rationale: string
example: string
how_to_fix: string
docs: string
}
/**
 * Envelope emitted by `fallow --format review-github` / `review-gitlab`.
 */
export interface ReviewEnvelopeOutput {
event?: (ReviewEnvelopeEvent | null)
body: string
summary?: ReviewEnvelopeSummary
comments: ReviewComment[]
marker_regex?: string
marker_regex_flags?: string
meta: ReviewEnvelopeMeta
}
/**
 * Summary block on [`ReviewEnvelopeOutput`].
 */
export interface ReviewEnvelopeSummary {
body: string
fingerprint: string
}
/**
 * GitHub pull-request review comment.
 */
export interface GitHubReviewComment {
path: string
line: number
side: GitHubReviewSide
body: string
fingerprint: string
truncated?: boolean
}
/**
 * GitLab merge-request discussion comment.
 */
export interface GitLabReviewComment {
body: string
position: GitLabReviewPosition
fingerprint: string
truncated?: boolean
}
/**
 * `position` block inside [`GitLabReviewComment`]. Mirrors the GitLab
 * merge-request discussion-position API.
 */
export interface GitLabReviewPosition {
base_sha?: (string | null)
start_sha?: (string | null)
head_sha?: (string | null)
position_type: GitLabReviewPositionType
old_path: string
new_path: string
new_line: number
}
/**
 * `meta` block inside [`ReviewEnvelopeOutput`].
 */
export interface ReviewEnvelopeMeta {
schema: ReviewEnvelopeSchema
provider: ReviewProvider
check_conclusion?: (ReviewCheckConclusion | null)
}
/**
 * Envelope emitted by `fallow ci reconcile-review --format json`. Used by
 * CI integrations to drive comment carry-over and stale-comment cleanup
 * across PR / MR revisions.
 */
export interface ReviewReconcileOutput {
schema: ReviewReconcileSchema
provider: ReviewProvider
target?: (string | null)
dry_run: boolean
comments: number
current_fingerprints: number
existing_fingerprints: number
new_fingerprints: number
stale_fingerprints: number
new: string[]
stale: string[]
provider_warning?: (string | null)
resolution_comments_posted: number
threads_resolved: number
apply_hint?: (string | null)
apply_errors: string[]
failed_fingerprints?: string[]
unapplied_fingerprints?: string[]
}
/**
 * `fallow coverage setup --json` envelope.
 */
export interface CoverageSetupOutput {
schema_version: CoverageSetupSchemaVersion
framework_detected: CoverageSetupFramework
package_manager?: (CoverageSetupPackageManager | null)
runtime_targets: CoverageSetupRuntimeTarget[]
members: CoverageSetupMember[]
config_written?: unknown
commands: string[]
files_to_edit: CoverageSetupFileToEdit[]
snippets: CoverageSetupSnippet[]
dockerfile_snippet?: (string | null)
next_steps: string[]
warnings: string[]
_meta?: unknown
}
export interface CoverageSetupMember {
name: string
path: string
framework_detected: CoverageSetupFramework
package_manager?: (CoverageSetupPackageManager | null)
runtime_targets: CoverageSetupRuntimeTarget[]
files_to_edit: CoverageSetupFileToEdit[]
snippets: CoverageSetupSnippet[]
dockerfile_snippet?: (string | null)
warnings: string[]
}
export interface CoverageSetupFileToEdit {
path: string
reason: string
}
export interface CoverageSetupSnippet {
label: string
path: string
content: string
}
export interface CoverageAnalyzeOutput {
schema_version: CoverageAnalyzeSchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
runtime_coverage: RuntimeCoverageReport
_meta?: (Meta | null)
}
/**
 * Envelope emitted by `fallow list --boundaries --format json`. Surfaces
 * the architecture boundary zones, rules, and (issue #373) the user's
 * pre-expansion `autoDiscover` logical groups so consumers can render
 * grouping intent that `expand_auto_discover` would otherwise flatten out
 * of `zones[]`.
 */
export interface ListBoundariesOutput {
boundaries: BoundariesListing
}
/**
 * `boundaries` block carried by [`ListBoundariesOutput`].
 */
export interface BoundariesListing {
configured: boolean
zone_count: number
zones: BoundariesListZone[]
rule_count: number
rules: BoundariesListRule[]
logical_group_count: number
logical_groups: BoundariesListLogicalGroup[]
}
/**
 * A boundary zone after preset and `autoDiscover` expansion. Each entry
 * classifies files into a single zone via glob patterns.
 */
export interface BoundariesListZone {
name: string
patterns: string[]
file_count: number
}
/**
 * A boundary import rule, expanded to operate on concrete child zone
 * names after `autoDiscover` flattening. The user's pre-expansion rule
 * (keyed on the logical parent name, if any) is preserved on the
 * corresponding [`BoundariesListLogicalGroup::authored_rule`].
 */
export interface BoundariesListRule {
from: string
allow: string[]
}
/**
 * A pre-expansion `autoDiscover` logical group surfaced for observability
 * (issue #373). Captured during `expand_auto_discover` so consumers can
 * see the user-authored parent name and grouping intent after expansion
 * would otherwise flatten it out of [`BoundariesListing::zones`].
 */
export interface BoundariesListLogicalGroup {
name: string
children: string[]
auto_discover: string[]
status: LogicalGroupStatus
source_zone_index: number
file_count: number
authored_rule?: (AuthoredRule | null)
fallback_zone?: (string | null)
merged_from?: (number[] | null)
original_zone_root?: (string | null)
child_source_indices?: number[]
}
/**
 * Pre-expansion rule preserved on a [`LogicalGroup`].
 */
export interface AuthoredRule {
/**
 * Authored `allow` list.
 */
allow: string[]
/**
 * Authored `allowTypeOnly` list.
 */
allow_type_only?: string[]
}
/**
 * `fallow workspaces --format json` envelope.
 */
export interface WorkspacesOutput {
/**
 * Number of workspace package entries in `workspaces`.
 */
workspace_count: number
/**
 * Workspace packages discovered from package manager and tsconfig workspace
 * declarations. Paths are project-root-relative and use forward slashes.
 */
workspaces: WorkspaceInfo[]
/**
 * Workspace discovery diagnostics produced while reading workspace
 * declarations. Present for compatibility with the current wire contract,
 * even when empty.
 */
workspace_diagnostics: WorkspaceDiagnostic[]
}
/**
 * One workspace package emitted by `fallow workspaces --format json`.
 */
export interface WorkspaceInfo {
/**
 * Package name from the workspace package.json. This is the value accepted
 * by `--workspace <name>`.
 */
name: string
/**
 * Project-root-relative path to the workspace directory, normalized to
 * forward slashes for cross-platform JSON consumers.
 */
path: string
/**
 * Whether the package is a generated or platform-specific dependency
 * package rather than a hand-authored workspace.
 */
is_internal_dependency: boolean
}
/**
 * Envelope emitted by `fallow health --format json` (plus the `health` block
 * inside the combined and audit envelopes).
 *
 * The body is `HealthReport` flattened into the envelope so every report
 * field (`findings`, `summary`, `vital_signs`, `hotspots`, `actions_meta`,
 * ...) lives at the top level. Grouped runs populate `grouped_by` +
 * `groups` with per-bucket recomputed metrics. The `actions_meta`
 * breadcrumb is modeled on `HealthReport` as an `Option<HealthActionsMeta>`
 * and is set at construction time by the report builder when the active
 * `HealthActionContext` requests suppress-line omission, so the schema
 * documents the field and serde populates it natively.
 */
export interface HealthOutput {
schema_version: SchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
/**
 * Functions and synthetic template entries exceeding complexity
 * thresholds, sorted by the --sort criteria. Each entry wraps its
 * inner [`ComplexityViolation`] payload (flattened on the wire) with
 * the typed `actions` list and an optional audit-mode `introduced`
 * flag.
 */
findings: HealthFinding[]
summary: HealthSummary
/**
 * Configured threshold override states. Entries are emitted for active
 * exceptions, stale exceptions, and full-run no-match cleanup hints.
 */
threshold_overrides?: ThresholdOverrideState[]
/**
 * Project-wide vital signs (always computed from available data).
 */
vital_signs?: (VitalSigns | null)
/**
 * Project-wide health score (only populated with `--score`).
 */
health_score?: (HealthScore | null)
/**
 * Per-file health scores. Only present when --file-scores is used. Sorted
 * by risk-aware triage concern, combining low maintainability and high
 * CRAP risk. Zero-function files (barrels) are excluded by default.
 */
file_scores?: FileHealthScore[]
/**
 * Static coverage gaps.
 *
 * Populated when coverage gaps are explicitly requested, or when the
 * top-level `health` command allows config severity to surface them in the
 * default report.
 */
coverage_gaps?: (CoverageGaps | null)
/**
 * Located prop-drilling chains (React/Preact props forwarded unchanged
 * through 3+ pass-through components). Only present when the opt-in
 * `prop-drilling` rule is enabled (it defaults to off). Each entry carries
 * the source, every pass-through hop, and the consumer with file + line +
 * component, so CI / an agent can act. Surfaced alongside hotspots as a
 * graph-derived health signal.
 */
prop_drilling_chains?: PropDrillingChainFinding[]
/**
 * Hotspot entries combining git churn with complexity. Only present when
 * --hotspots is used. Sorted by score descending (highest risk first).
 * Each entry wraps its inner [`HotspotEntry`] payload (flattened on the
 * wire) with a typed `actions` list.
 */
hotspots?: HotspotFinding[]
/**
 * Hotspot analysis summary (only set with `--hotspots`).
 */
hotspot_summary?: (HotspotSummary | null)
/**
 * Runtime coverage findings from the paid sidecar (only populated with
 * `--runtime-coverage`).
 */
runtime_coverage?: (RuntimeCoverageReport | null)
/**
 * Combined coverage, runtime, complexity, and change-scope verdicts.
 */
coverage_intelligence?: (CoverageIntelligenceReport | null)
/**
 * Functions exceeding 60 LOC (very high risk). Only present when unit size
 * very-high-risk bin >= 3%. Sorted by line count descending.
 */
large_functions?: LargeFunctionEntry[]
/**
 * Ranked refactoring recommendations. Only present when --targets is used.
 * Sorted by efficiency (priority/effort) descending. Each entry wraps
 * its inner [`RefactoringTarget`] payload (flattened on the wire) with
 * a typed `actions` list.
 */
targets?: RefactoringTargetFinding[]
/**
 * Adaptive thresholds used for target scoring (only set with `--targets`).
 */
target_thresholds?: (TargetThresholds | null)
/**
 * Health trend comparison against a previous snapshot (only set with `--trend`).
 */
health_trend?: (HealthTrend | null)
/**
 * Audit breadcrumb explaining systemic action-array adjustments. Present
 * only when at least one adjustment was made (e.g., health finding
 * suppression hints omitted because a baseline is active). When --group-by
 * is active, each entry of `groups` may carry its own `actions_meta`
 * describing the same omission so per-group consumers do not need to walk
 * back to the report root.
 */
actions_meta?: (HealthActionsMeta | null)
/**
 * Structural CSS analytics (specificity hotspots, `!important` density,
 * over-complex selectors, deep nesting). Present only with `--css`.
 */
css_analytics?: (CssAnalyticsReport | null)
grouped_by?: (GroupByMode | null)
groups?: (HealthGroup[] | null)
_meta?: (Meta | null)
workspace_diagnostics?: WorkspaceDiagnostic[]
/**
 * Read-only follow-up commands computed from this run's findings. See
 * [`CheckOutput::next_steps`] for the contract.
 */
next_steps?: NextStep[]
}
/**
 * A health report scoped to a single group.
 *
 * `key` is the group label produced by the resolver (workspace package name,
 * CODEOWNERS owner, directory, or section). `owners` is populated only for
 * `--group-by section` (mirrors dead-code grouped output).
 *
 * Per-group `vital_signs` and `health_score` are recomputed from the
 * files in the group, so they answer "what is the health of workspace X" in
 * a single invocation. `files_analyzed` and `functions_above_threshold`
 * summarise the subset for parity with the project-level
 * [`crate::health_types::HealthSummary`].
 */
export interface HealthGroup {
/**
 * Group identifier produced by the resolver. For 'package' grouping:
 * workspace package name (e.g. '@scope/app-a') or '(root)' for files
 * outside any workspace. For 'owner' grouping: the CODEOWNERS team. For
 * 'directory' grouping: the top-level directory prefix. For 'section'
 * grouping: the GitLab CODEOWNERS section name, or '(no section)' /
 * '(unowned)' for unmatched files.
 */
key: string
/**
 * Section default owners (GitLab CODEOWNERS `[Section] @owner1 @owner2`).
 * Present only when grouped_by is 'section'.
 */
owners?: (string[] | null)
/**
 * Files participating in this group after workspace and ignore filters.
 */
files_analyzed: number
/**
 * Number of findings in this group, mirroring the project-level
 * `summary.functions_above_threshold` semantics post-baseline /
 * post-`--top` truncation. When `--top` was supplied this reflects the
 * rendered finding count, not the un-truncated total.
 */
functions_above_threshold: number
/**
 * Whether CRAP findings in this group share a single coverage-source kind
 * (`uniform`) or combine Istanbul / estimated / inherited sources
 * (`mixed`). Absent when no grouped finding carries CRAP source data.
 */
coverage_source_consistency?: (CoverageSourceConsistency | null)
/**
 * Per-group vital signs recomputed from the files in this group. Absent
 * when --score-only suppressed top-level vital signs.
 */
vital_signs?: (VitalSigns | null)
/**
 * Per-group health score recomputed from the per-group vital signs. Absent
 * when --score was not requested.
 */
health_score?: (HealthScore | null)
/**
 * Findings restricted to files in this group. Each entry is the typed
 * [`HealthFinding`] wrapper around a
 * [`ComplexityViolation`](crate::health_types::ComplexityViolation)
 * payload.
 */
findings?: HealthFinding[]
/**
 * File scores restricted to files in this group.
 */
file_scores?: FileHealthScore[]
/**
 * Hotspots restricted to files in this group. Each entry is the typed
 * [`HotspotFinding`] wrapper around a
 * [`HotspotEntry`](crate::health_types::HotspotEntry) payload.
 */
hotspots?: HotspotFinding[]
/**
 * Large functions in files belonging to this group.
 */
large_functions?: LargeFunctionEntry[]
/**
 * Refactoring targets in files belonging to this group. Each entry is
 * the typed [`RefactoringTargetFinding`] wrapper around a
 * [`RefactoringTarget`](crate::health_types::RefactoringTarget)
 * payload.
 */
targets?: RefactoringTargetFinding[]
/**
 * Auditable breadcrumb recording why `suppress-line` action hints
 * were omitted from this group's findings. Mirrors the project-level
 * `HealthReport.actions_meta`; populated at construction time when the
 * per-group [`HealthActionContext`](crate::health_types::HealthActionContext)
 * suppresses inline hints.
 */
actions_meta?: (HealthActionsMeta | null)
}
/**
 * Wire-shape payload for `fallow dupes --format json` (the body that
 * flattens into [`crate::output_envelope::DupesOutput`] and is also
 * emitted under the `dupes` / `duplication` key inside the combined and
 * audit envelopes).
 *
 * Mirrors [`DuplicationReport`] field-for-field, except `clone_groups`
 * and `clone_families` carry the typed wrapper envelopes instead of bare
 * findings, so the schema (and any TS / agent consumer) sees the typed
 * `actions[]` natively.
 */
export interface DupesOutput {
schema_version: SchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
/**
 * All detected clone groups, each wrapped with typed actions.
 */
clone_groups: CloneGroupFinding[]
/**
 * Clone families, each wrapped with typed actions. Inner `groups`
 * inside each [`CloneFamilyFinding`] are themselves wrapped as
 * [`CloneGroupFinding`] entries carrying their own `actions[]` (and
 * optional audit-mode `introduced` flag), so JSON-Schema strict
 * consumers and TS consumers reading `clone_families[].groups[]` see
 * the same shape as the top-level `clone_groups[]` array (preserves
 * the issue #393 regression contract).
 */
clone_families: CloneFamilyFinding[]
/**
 * Mirrored directory pairs.
 */
mirrored_directories?: MirroredDirectory[]
stats: DuplicationStats
grouped_by?: (GroupByMode | null)
total_issues?: (number | null)
groups?: (DuplicationGroup[] | null)
/**
 * `_meta` block with metric / rule definitions, emitted when `--explain`
 * is passed (always present in MCP responses).
 */
_meta?: (Meta | null)
/**
 * Workspace-discovery diagnostics surfaced during config load
 * (issue #473). See [`CheckOutput::workspace_diagnostics`] for the full
 * contract; the same list is repeated on each top-level command's
 * envelope so single-command consumers see it without having to look at
 * a separate top-level field.
 */
workspace_diagnostics?: WorkspaceDiagnostic[]
/**
 * Read-only follow-up commands computed from this run's findings. See
 * [`CheckOutput::next_steps`] for the contract.
 */
next_steps?: NextStep[]
}
/**
 * A single grouped duplication bucket. Per-group `stats` are dedup-aware and
 * computed over the FULL group BEFORE any `--top` truncation.
 */
export interface DuplicationGroup {
/**
 * Group label (owner / directory / package / section). `(unowned)` for
 * files with no CODEOWNERS rule, `(no section)` for pre-section rules in
 * section mode.
 */
key: string
stats: DuplicationStats
/**
 * Clone groups attributed to this owner, each wrapped with the typed
 * `actions[]` array. Each group's `primary_owner` is its largest-owner
 * key; per-instance `owner` lets consumers see cross-bucket fan-out
 * without re-resolving paths.
 */
clone_groups: AttributedCloneGroupFinding[]
/**
 * Clone families overlapping this bucket, each wrapped with the typed
 * `actions[]` array.
 */
clone_families: CloneFamilyFinding[]
}
/**
 * Wire-shape envelope for an [`AttributedCloneGroup`] finding (per-bucket
 * duplication attribution emitted under `fallow dupes --group-by`).
 * Flattens the attributed group and carries the same typed
 * `CloneGroupAction` array as [`CloneGroupFinding`]; no `introduced`
 * field because `fallow audit` does not run on grouped output.
 */
export interface AttributedCloneGroupFinding {
/**
 * Largest-owner attribution: the resolver key with the most instances in
 * this clone group. Ties broken alphabetically (smallest key wins).
 */
primary_owner: string
token_count: number
line_count: number
/**
 * Each instance carries its own `owner` field alongside the standard
 * CloneInstance shape.
 */
instances: AttributedInstance[]
/**
 * Stable content fingerprint, usually `dup:<8hex>` and widened on rare
 * report collisions. Addressable via `fallow dupes --trace dup:<fp>`.
 * Computed from the group's instances, so it matches the top-level
 * `clone_groups[].fingerprint` for the same clone.
 */
fingerprint: string
/**
 * Suggested next steps. Always emitted.
 */
actions: CloneGroupAction[]
}
/**
 * A clone instance plus its per-instance owner key (for inline JSON / SARIF
 * rendering).
 *
 * Each instance carries its own `owner` field alongside the standard
 * `CloneInstance` shape (file / start_line / end_line / start_col / end_col /
 * fragment), so consumers can attribute instances to resolver keys without
 * re-resolving paths.
 */
export interface AttributedInstance {
/**
 * Path to the file containing this clone instance.
 */
file: string
/**
 * 1-based start line of the clone.
 */
start_line: number
/**
 * 1-based end line of the clone.
 */
end_line: number
/**
 * 0-based start column.
 */
start_col: number
/**
 * 0-based end column.
 */
end_col: number
/**
 * The actual source code fragment.
 */
fragment: string
/**
 * Resolver key for this specific instance (per-instance, not the
 * group-level largest-owner).
 */
owner: string
}
/**
 * Envelope emitted by `fallow dead-code --group-by ... --format json`.
 *
 * Issues are partitioned into resolver buckets (CODEOWNERS team, directory
 * prefix, workspace package, or GitLab CODEOWNERS section) instead of flat
 * arrays. Each bucket carries the same issue-array shape as the ungrouped
 * `CheckOutput` body, plus per-group `key` / `owners` / `total_issues`.
 */
export interface CheckGroupedOutput {
schema_version: SchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
grouped_by: GroupByMode
total_issues: number
groups: CheckGroupedEntry[]
_meta?: (Meta | null)
/**
 * Read-only follow-up commands computed from the full (ungrouped) findings.
 * See [`CheckOutput::next_steps`] for the contract.
 */
next_steps?: NextStep[]
}
/**
 * Single resolver bucket inside `CheckGroupedOutput`. Carries the group's
 * identifier, optional section owners, and a per-group flattened
 * `AnalysisResults`.
 */
export interface CheckGroupedEntry {
key: string
owners?: (string[] | null)
total_issues: number
/**
 * Files not reachable from any entry point. Wrapped in
 * [`UnusedFileFinding`] so each entry carries a typed `actions` array
 * natively, replacing the pre-2.76 post-pass injection.
 */
unused_files: UnusedFileFinding[]
/**
 * Exports never imported by other modules. Wrapped in
 * [`UnusedExportFinding`] so each entry carries a typed `actions`
 * array natively.
 */
unused_exports: UnusedExportFinding[]
/**
 * Type exports never imported by other modules. Wrapped in
 * [`UnusedTypeFinding`]: the inner [`UnusedExport`] struct is shared
 * with `unused_exports` but the wrapper emits a type-targeted fix
 * description.
 */
unused_types: UnusedTypeFinding[]
/**
 * Exported symbols whose public signature references same-file private
 * types. Wrapped in [`PrivateTypeLeakFinding`] so each entry carries a
 * typed `actions` array natively.
 */
private_type_leaks: PrivateTypeLeakFinding[]
/**
 * Dependencies listed in package.json but never imported. Wrapped in
 * [`UnusedDependencyFinding`] so each entry carries a typed `actions`
 * array natively. The fix action swaps from `remove-dependency` to
 * `move-dependency` when `used_in_workspaces` is non-empty.
 */
unused_dependencies: UnusedDependencyFinding[]
/**
 * Dev dependencies listed in package.json but never imported. Wrapped
 * in [`UnusedDevDependencyFinding`]: same bare struct as
 * `unused_dependencies` with a `devDependencies`-targeted fix
 * description.
 */
unused_dev_dependencies: UnusedDevDependencyFinding[]
/**
 * Optional dependencies listed in package.json but never imported.
 * Wrapped in [`UnusedOptionalDependencyFinding`] with an
 * `optionalDependencies`-targeted fix description.
 */
unused_optional_dependencies: UnusedOptionalDependencyFinding[]
/**
 * Enum members never accessed. Wrapped in
 * [`UnusedEnumMemberFinding`] so each entry carries a typed `actions`
 * array natively.
 */
unused_enum_members: UnusedEnumMemberFinding[]
/**
 * Class members never accessed. Wrapped in
 * [`UnusedClassMemberFinding`]: same inner [`UnusedMember`] struct as
 * `unused_enum_members`, with a class-targeted fix description and the
 * `auto_fixable: false` default to reflect dependency-injection
 * patterns.
 */
unused_class_members: UnusedClassMemberFinding[]
/**
 * Store members (Pinia `state` / `getters` / `actions` key, or a
 * setup-store returned key) declared but never accessed by any consumer
 * project-wide. Wrapped in [`UnusedStoreMemberFinding`]: same inner
 * [`UnusedMember`] struct as `unused_class_members`, with a
 * store-targeted fix description. Cross-graph: the store binding is
 * imported (the module is reachable) yet a specific member is dead.
 */
unused_store_members?: UnusedStoreMemberFinding[]
/**
 * Import specifiers that could not be resolved. Wrapped in
 * [`UnresolvedImportFinding`] so each entry carries a typed `actions`
 * array natively.
 */
unresolved_imports: UnresolvedImportFinding[]
/**
 * Dependencies used in code but not listed in package.json. Wrapped in
 * [`UnlistedDependencyFinding`].
 */
unlisted_dependencies: UnlistedDependencyFinding[]
/**
 * Exports with the same name across multiple modules. Wrapped in
 * [`DuplicateExportFinding`] so each entry carries a typed `actions`
 * array natively, with the position-0 `add-to-config` `ignoreExports`
 * snippet wired in at wrapper construction.
 */
duplicate_exports: DuplicateExportFinding[]
/**
 * Production dependencies only used via type-only imports (could be
 * devDependencies). Only populated in production mode. Wrapped in
 * [`TypeOnlyDependencyFinding`].
 */
type_only_dependencies: TypeOnlyDependencyFinding[]
/**
 * Production dependencies only imported by test files (could be
 * devDependencies). Wrapped in [`TestOnlyDependencyFinding`].
 */
test_only_dependencies?: TestOnlyDependencyFinding[]
/**
 * Circular dependency chains detected in the module graph. Wrapped in
 * [`CircularDependencyFinding`] so each entry carries a typed `actions`
 * array natively.
 */
circular_dependencies: CircularDependencyFinding[]
/**
 * Cycles or self-loops in the re-export edge subgraph (barrel files
 * re-exporting from each other in a loop). Wrapped in
 * [`ReExportCycleFinding`] so each entry carries a typed `actions`
 * array natively (a `refactor-re-export-cycle` informational primary
 * plus a `suppress-file` secondary; cycles are file-scoped so a single
 * suppression breaks the cycle).
 */
re_export_cycles?: ReExportCycleFinding[]
/**
 * Imports that cross architecture boundary rules. Wrapped in
 * [`BoundaryViolationFinding`] so each entry carries a typed `actions`
 * array natively.
 */
boundary_violations?: BoundaryViolationFinding[]
/**
 * Files that matched no architecture boundary zone while
 * `boundaries.coverage.requireAllFiles` was enabled.
 */
boundary_coverage_violations?: BoundaryCoverageViolationFinding[]
/**
 * Calls from zoned files to callees forbidden for that zone via
 * `boundaries.calls.forbidden`. Wrapped in
 * [`BoundaryCallViolationFinding`] so each entry carries a typed
 * `actions` array natively.
 */
boundary_call_violations?: BoundaryCallViolationFinding[]
/**
 * Banned calls and banned imports matched by declarative rule packs
 * (`rulePacks` config). Wrapped in [`PolicyViolationFinding`] so each
 * entry carries a typed `actions` array natively. Each finding carries
 * its effective per-rule severity.
 */
policy_violations?: PolicyViolationFinding[]
/**
 * Suppression comments or JSDoc tags that no longer match any issue.
 */
stale_suppressions?: StaleSuppression[]
/**
 * Entries in pnpm-workspace.yaml's catalog: or catalogs: sections not
 * referenced by any workspace package via the catalog: protocol. Wrapped
 * in [`UnusedCatalogEntryFinding`] so each entry carries a typed
 * `actions` array natively, with per-instance `auto_fixable` derived
 * from `hardcoded_consumers`.
 */
unused_catalog_entries?: UnusedCatalogEntryFinding[]
/**
 * Named groups under pnpm-workspace.yaml's catalogs: section that declare
 * no package entries. The top-level catalog: map is not reported. Wrapped
 * in [`EmptyCatalogGroupFinding`].
 */
empty_catalog_groups?: EmptyCatalogGroupFinding[]
/**
 * Workspace package.json references to catalogs (`catalog:` or
 * `catalog:<name>`) that do not declare the consumed package. pnpm install
 * will error until the named catalog grows to include the package or the
 * reference is switched / removed. Wrapped in
 * [`UnresolvedCatalogReferenceFinding`] with the discriminated
 * `add-catalog-entry` / `update-catalog-reference` primary at position 0.
 */
unresolved_catalog_references?: UnresolvedCatalogReferenceFinding[]
/**
 * Entries in pnpm-workspace.yaml's overrides: section, or package.json's
 * pnpm.overrides block, whose target package is not declared by any
 * workspace package and is not present in pnpm-lock.yaml. Default severity
 * is warn because projects without a readable lockfile fall back to
 * manifest-only checks; the hint field flags those conservative cases.
 * Wrapped in [`UnusedDependencyOverrideFinding`].
 */
unused_dependency_overrides?: UnusedDependencyOverrideFinding[]
/**
 * pnpm.overrides entries whose key or value does not parse as a valid
 * override spec (empty key, empty value, malformed selector, unbalanced
 * parent matcher). pnpm install will reject these. Default severity is
 * error. Wrapped in [`MisconfiguredDependencyOverrideFinding`].
 */
misconfigured_dependency_overrides?: MisconfiguredDependencyOverrideFinding[]
/**
 * `"use client"` files that export a Next.js server-only / route-segment
 * config name (e.g. `metadata`, `revalidate`, `GET`). Next.js rejects this
 * at build time. Wrapped in [`InvalidClientExportFinding`] so each entry
 * carries a typed `actions` array natively. Default severity is `warn`.
 */
invalid_client_exports?: InvalidClientExportFinding[]
/**
 * Barrel files that re-export BOTH a `"use client"` origin module AND a
 * server-only origin module (the Next.js App Router footgun). Wrapped in
 * [`MixedClientServerBarrelFinding`] so each entry carries a typed
 * `actions` array natively. Default severity is `warn`.
 */
mixed_client_server_barrels?: MixedClientServerBarrelFinding[]
/**
 * `"use client"` / `"use server"` directives written as expression
 * statements after a non-directive statement, so the RSC bundler parses
 * them as ordinary strings and silently ignores them. Wrapped in
 * [`MisplacedDirectiveFinding`] so each entry carries a typed `actions`
 * array natively. Default severity is `warn`.
 */
misplaced_directives?: MisplacedDirectiveFinding[]
/**
 * Vue `inject(KEY)` / Svelte `getContext(KEY)` calls whose symbol KEY is
 * provided nowhere in the project (the injected-never-provided dead-half).
 * Wrapped in [`UnprovidedInjectFinding`] so each entry carries a typed
 * `actions` array natively. Default severity is `warn`.
 */
unprovided_injects?: UnprovidedInjectFinding[]
/**
 * Vue/Svelte single-file components that are reachable but rendered nowhere
 * (the imported-but-never-rendered dead-half). Wrapped in
 * [`UnrenderedComponentFinding`] so each entry carries a typed `actions`
 * array natively. Default severity is `warn`.
 */
unrendered_components?: UnrenderedComponentFinding[]
/**
 * Next.js App Router route files that resolve to the same URL within one
 * app-root (a guaranteed `next build` failure). Wrapped in
 * [`RouteCollisionFinding`] so each entry carries a typed `actions` array
 * natively. One finding per colliding file. Default severity is `warn`.
 */
route_collisions?: RouteCollisionFinding[]
/**
 * Sibling Next.js dynamic route segments at one tree position using
 * different param spellings (a dev / runtime error; `next build` does NOT
 * catch it). Wrapped in [`DynamicSegmentNameConflictFinding`] so each entry
 * carries a typed `actions` array natively. Default severity is `warn`.
 */
dynamic_segment_name_conflicts?: DynamicSegmentNameConflictFinding[]
/**
 * Vue `<script setup>` `defineProps` props referenced nowhere in their own
 * SFC (neither `<script>` nor `<template>`). Wrapped in
 * [`UnusedComponentPropFinding`] so each entry carries a typed `actions`
 * array natively. Default severity is `warn`.
 */
unused_component_props?: UnusedComponentPropFinding[]
/**
 * Vue `<script setup>` `defineEmits` events emitted nowhere in their own SFC
 * (no `emit('<name>')` call). Wrapped in [`UnusedComponentEmitFinding`] so
 * each entry carries a typed `actions` array natively. Default severity is
 * `warn`.
 */
unused_component_emits?: UnusedComponentEmitFinding[]
/**
 * Next.js Server Actions (exports of `"use server"` files) that no code in
 * the project references. Reclassified out of `unused_exports` for
 * `"use server"` files. Wrapped in [`UnusedServerActionFinding`] so each
 * entry carries a typed `actions` array natively. Default severity is
 * `warn`.
 */
unused_server_actions?: UnusedServerActionFinding[]
/**
 * SvelteKit `+page.{ts,server.ts,js,server.js}` `load()` return-object keys
 * read by no consumer. Wrapped in [`UnusedLoadDataKeyFinding`] so each entry
 * carries a typed `actions` array natively. Default severity is `warn`.
 */
unused_load_data_keys?: UnusedLoadDataKeyFinding[]
/**
 * `true` when the `unused-load-data-key` detector abstained project-wide
 * because a whole-object use of `page.data` / `$page.data` was seen
 * somewhere (S1 observability: an empty `unused_load_data_keys` with this
 * flag set is NOT a clean bill, it means the rule could not run safely).
 * Serialized only when `true` so the default JSON contract is unchanged.
 */
unused_load_data_keys_global_abstain?: boolean
/**
 * React/Preact props forwarded unchanged through `>= N` intermediate
 * pass-through components until a consumer (located per-chain records).
 * Wrapped in [`PropDrillingChainFinding`] so each entry carries a typed
 * `actions` array natively. Health signal: the rule defaults to `off`
 * (opt-in), so this is dormant and populated ONLY when the user enables it.
 */
prop_drilling_chains?: PropDrillingChainFinding[]
/**
 * React/Preact components whose entire body is a single spread-forwarded
 * child render (`return <Child {...props}/>`): pure structural indirection,
 * a candidate for inlining at call sites. Wrapped in [`ThinWrapperFinding`]
 * so each entry carries a typed `actions` array natively. Health signal: the
 * rule defaults to `off` (opt-in), so this is dormant and populated ONLY
 * when the user enables it.
 */
thin_wrappers?: ThinWrapperFinding[]
/**
 * React/Preact components that participate in a duplicate-prop-shape group:
 * three or more components across two or more files whose statically-known
 * prop NAME set is identical after stripping ubiquitous DOM / passthrough
 * names (a missing shared `Props` type / base component). Wrapped in
 * [`DuplicatePropShapeFinding`] so each entry carries a typed `actions`
 * array and its sibling roster natively. Health signal: the rule defaults to
 * `off` (opt-in), so this is dormant and populated ONLY when the user
 * enables it.
 */
duplicate_prop_shapes?: DuplicatePropShapeFinding[]
}
/**
 * The rendered impact report, derived purely from the store (no analysis run).
 */
export interface ImpactReport {
schema_version: ImpactReportSchemaVersion
enabled: boolean
enabled_source: EnabledSource
record_count: number
_meta?: (Meta | null)
first_recorded?: (string | null)
/**
 * Git SHA of the most recent recorded run, so a consumer can tell which
 * commit the `surfacing` counts belong to. This is an ABBREVIATED SHA
 * (`git rev-parse --short`), so it is for display/correlation only and will
 * not match a full 40-character SHA from `$GITHUB_SHA` or the git API
 * without expansion. None when the latest run had no SHA (not a git repo)
 * or there are no records yet.
 */
latest_git_sha?: (string | null)
/**
 * Counts from the most recent recorded run. These are CHANGED-FILE scoped
 * (each record comes from a `fallow audit` run, whose default `new-only`
 * gate counts only findings in the changed files of that run), NOT a
 * whole-project total.
 */
surfacing?: (ImpactCounts | null)
/**
 * Trend between the two most recent records. None until two records exist.
 */
trend?: (TrendSummary | null)
/**
 * Counts from the most recent whole-project `fallow` run. WHOLE-PROJECT
 * scope (not changed-file), so this is the current issue total across the
 * whole repo, context next to the actionable changed-file `surfacing`
 * count. None until a full `fallow` run has been recorded. v1.6.
 */
project_surfacing?: (ImpactCounts | null)
/**
 * Trend between the two most recent whole-project records. Comparable over
 * time (same whole-project denominator every run), unlike the changed-file
 * `trend`. None until two full `fallow` runs exist. v1.6.
 */
project_trend?: (TrendSummary | null)
containment_count: number
/**
 * Most recent containment events (newest last), capped for display.
 */
recent_containment: ContainmentEvent[]
/**
 * Lifetime count of findings fallow credits as genuinely resolved (code
 * removed or refactored, never a `fallow-ignore`). v1.5.
 */
resolved_total: number
/**
 * Lifetime count of findings silenced by a newly-added `fallow-ignore`.
 * Reported as honest context, never as a win. v1.5.
 */
suppressed_total: number
/**
 * Most recent resolution events (newest last), capped for display. v1.5.
 */
recent_resolved: ResolutionEvent[]
/**
 * Whether per-finding attribution has a baseline yet. False on a freshly
 * upgraded v1 store (no frontier captured), which the renderer uses to show
 * "resolution tracking starts from your next run" instead of a bare zero.
 */
attribution_active: boolean
/**
 * Whether the local agent onboarding prompt has been explicitly declined.
 * Stored in the user config dir (per project) so agents avoid cross-session
 * nags without writing into the repo.
 */
onboarding_declined: boolean
/**
 * Whether the user ever made an explicit enable/disable decision for
 * Impact tracking. `enabled: false` with `explicit_decision: false` means
 * "never asked"; with `true` it means "asked and declined". Agents use
 * this to offer the impact opt-in exactly once per project.
 */
explicit_decision: boolean
}
/**
 * Per-category issue counts captured at a recorded run.
 */
export interface ImpactCounts {
total_issues: number
dead_code: number
complexity: number
duplication: number
}
/**
 * A computed trend between the two most recent records.
 */
export interface TrendSummary {
direction: ImpactTrendDirection
/**
 * Signed delta in total issues (current minus previous).
 */
total_delta: number
previous_total: number
current_total: number
}
export interface ContainmentEvent {
blocked_at: string
cleared_at: string
git_sha?: (string | null)
blocked_counts: ImpactCounts
}
export interface ResolutionEvent {
kind: string
path: string
symbol?: (string | null)
git_sha?: (string | null)
timestamp: string
}
/**
 * The cross-repo aggregate report (`fallow impact --all --format json`).
 */
export interface CrossRepoImpactReport {
schema_version: CrossRepoImpactSchemaVersion
/**
 * Per-project stores successfully parsed (add `unreadable_count` for the
 * total number of store files found in the user config dir).
 */
project_count: number
/**
 * Stores with recorded history (the rows in `projects`); excludes
 * enabled-but-empty stores, which are still counted in `project_count`.
 */
tracked_count: number
/**
 * Stores that failed to parse and were skipped (corrupt or newer-schema).
 */
unreadable_count: number
totals: CrossRepoTotals
projects: CrossRepoProjectEntry[]
}
/**
 * Grand totals across every tracked project (including repos whose directory no
 * longer exists on disk: their past wins still count toward lifetime impact).
 */
export interface CrossRepoTotals {
resolved_total: number
suppressed_total: number
containment_count: number
/**
 * Sum of whole-project issue totals across projects that have a full-run
 * baseline, as of EACH project's last full `fallow` run (not a simultaneous
 * snapshot).
 */
project_wide_issues: number
projects_with_baseline: number
}
/**
 * One project's row in the cross-repo roll-up.
 */
export interface CrossRepoProjectEntry {
/**
 * Stable, non-reversible project key (the store filename stem); the
 * cross-tool/cross-run JOIN key. NEVER a path.
 */
project_key: string
/**
 * Repo basename for display (never a full path). Absent on pre-v5 stores
 * (the row falls back to the short key).
 */
label?: (string | null)
/**
 * Timestamp of the project's most recent recorded run (changed-file or
 * whole-project), for the LAST RUN column and the default `recent` sort.
 */
last_recorded?: (string | null)
report: ImpactReport
}
/**
 * The `fallow security --format json` envelope. `FallowOutput` discriminates it
 * by the `kind: "security"` tag; the optional `gate` block is additive and is
 * not part of that discrimination.
 */
export interface SecurityOutput {
schema_version: SecuritySchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
config: SecurityOutputConfig
/**
 * Security-specific rule and field metadata, emitted with `--explain`.
 */
_meta?: (Meta | null)
/**
 * Gate verdict, present only when `--gate <mode>` was set (issue #886).
 * Emitted on pass too (`verdict: "pass"`, `new_count: 0`) so consumers
 * distinguish "gate ran and passed" from "gate did not run" (absent).
 */
gate?: (SecurityGate | null)
/**
 * Security candidates. Paths are project-root-relative, forward-slash.
 */
security_findings: SecurityFinding[]
/**
 * Opt-in attack-surface inventory from untrusted entry points to reachable
 * sinks. Present only when `--surface` was requested.
 */
attack_surface?: (SecurityAttackSurfaceEntry[] | null)
/**
 * In-band blind spot: number of `"use client"` files whose transitive
 * import cone contains a dynamic `import()` the reachability BFS could not
 * follow. A leak hidden behind such an edge would not be reported, so a
 * zero finding count with a non-zero value here is NOT a clean bill.
 */
unresolved_edge_files: number
/**
 * In-band blind spot: number of sink-shaped nodes the catalogue detector
 * could not flatten to a static callee path (dynamic dispatch, computed
 * members, aliased bindings). A zero finding count with a non-zero value
 * here is NOT a clean bill.
 */
unresolved_callee_sites: number
/**
 * Bounded diagnostics for unresolved callee blind spots.
 */
unresolved_callee_diagnostics?: (SecurityUnresolvedCalleeDiagnostics | null)
}
/**
 * Allowlisted config context for `fallow security --format json`.
 */
export interface SecurityOutputConfig {
rules: SecurityOutputRulesConfig
/**
 * `security.categories.include` from config. `null` means unset, `[]`
 * means explicitly empty.
 */
categories_include: (string[] | null)
/**
 * `security.categories.exclude` from config. `null` means unset, `[]`
 * means explicitly empty.
 */
categories_exclude: (string[] | null)
}
export interface SecurityOutputRulesConfig {
security_client_server_leak: SecurityRuleSeverityConfig
security_sink: SecurityRuleSeverityConfig
}
export interface SecurityRuleSeverityConfig {
configured: Severity
effective: Severity
}
/**
 * The `gate` block on `SecurityOutput`, present only when `--gate <mode>` ran.
 * Invariant: `verdict == Fail  IFF  exit code 8  IFF  new_count > 0`.
 */
export interface SecurityGate {
mode: SecurityGateMode
verdict: SecurityGateVerdict
/**
 * Number of candidates matching the selected gate mode.
 */
new_count: number
}
/**
 * A local security CANDIDATE for downstream agent verification, NOT a verified
 * vulnerability. Emitted only by `fallow security`, never under bare `fallow`
 * or the `audit` gate. There is deliberately no `confidence` or
 * `signal_strength` field: fallow does not prove exploitability, so the trace
 * (its hops and length) is the only honest signal.
 */
export interface SecurityFinding {
/**
 * Stable per-finding correlation id, identical across runs for the same
 * rule + anchor path + line. An autonomous agent that triaged this
 * candidate on a prior run uses it to correlate the candidate after a
 * rebase. Equal to the SARIF `partialFingerprints` value for the same
 * finding (one shared helper computes both).
 */
finding_id: string
kind: SecurityFindingKind
/**
 * The catalogue category id (e.g. `"dangerous-html"`). `Some` for
 * `TaintedSink`. For `ClientServerLeak` this is `None` for the secret-leak
 * finding, and `Some("server-only-import")` when a `"use client"` cone
 * reaches server-only code.
 */
category?: (string | null)
/**
 * The CWE number declared by the matched catalogue entry. `None` for
 * `ClientServerLeak`; never fabricated beyond the catalogue's value.
 */
cwe?: (number | null)
/**
 * File the finding is anchored on (the client boundary). Absolute
 * internally; JSON strips the project root via `serde_path::serialize`.
 */
path: string
/**
 * 1-based line number of the anchor.
 */
line: number
/**
 * 0-based byte column offset of the anchor.
 */
col: number
/**
 * Agent/human-readable evidence (e.g. the named env var the chain reaches).
 */
evidence: string
/**
 * Whether the sink argument was associated with a known untrusted source by
 * the intra-module source-to-sink back-trace (issue #859): a local binding
 * referenced in the argument was sourced from a catalogue source path
 * (`req.query`, `process.argv`, message-event `data`, etc.). `true` ranks
 * the candidate higher and annotates the evidence; `false` does NOT
 * suppress the finding (the association is conservative, never a proof, and
 * fallow prefers false-negatives over false-positives). Always `false` for
 * `ClientServerLeak`. Skipped from JSON when `false` for output stability.
 */
source_backed?: boolean
severity: SecuritySeverity
/**
 * Structural import-hop trace from the client boundary to the secret source.
 * The hop count is the uncalibrated signal; fallow does not prove the path
 * is exploitable.
 */
trace: TraceHop[]
/**
 * Machine-actionable next steps. Always emitted (possibly empty for
 * forward-compat). For security candidates this is a single file-level
 * suppress hint (`auto_fixable: false`); there is no auto-fix because
 * verification is the agent's job, not fallow's.
 */
actions: IssueAction[]
/**
 * Dead-code cross-link when the same sink candidate sits in code fallow also
 * reports as removable. Agents should verify the dead-code finding and delete
 * the code instead of hardening the sink when deletion is safe.
 */
dead_code?: (SecurityDeadCodeContext | null)
/**
 * Graph-derived reachability ranking signal (issues #860 and #885). `None`
 * until the post-detection ranking pass fills it; additive on the wire
 * (skipped when absent). Drives the order findings are emitted in:
 * runtime-reachable candidates sort first, followed by source-backed and
 * source-reachable candidates, then wider blast radius.
 */
reachability?: (SecurityReachability | null)
candidate: SecurityCandidate
/**
 * Source-to-sink taint-flow triple, present only when an untrusted source
 * is import-reachable to this sink. Absent (skipped) otherwise.
 */
taint_flow?: (SecurityTaintFlow | null)
/**
 * Production runtime coverage context for the function enclosing this
 * security sink. Present only when `fallow security --runtime-coverage`
 * runs and the candidate is a `tainted-sink`.
 */
runtime?: (SecurityRuntimeContext | null)
/**
 * Internal projection used by `fallow security --surface`. The CLI strips
 * this from per-finding JSON and promotes it to the top-level
 * `attack_surface` field only when requested.
 */
attack_surface?: (SecurityAttackSurfaceEntry | null)
}
/**
 * One hop in a security finding's structural trace. Stored as an absolute path
 * internally; JSON serialization strips the project root via
 * `serde_path::serialize`.
 */
export interface TraceHop {
/**
 * File on this hop of the import chain.
 */
path: string
/**
 * 1-based line number. Import-chain hops point at the import site; the
 * terminal secret-source hop points at the source module when extraction
 * does not carry a more precise member-access span.
 */
line: number
/**
 * 0-based byte column offset.
 */
col: number
role: TraceHopRole
}
/**
 * Dead-code cross-link attached to a security candidate when fallow's dead-code
 * pass reports the same anchor as removable code.
 */
export interface SecurityDeadCodeContext {
kind: SecurityDeadCodeKind
/**
 * Unused export name when `kind` is `unused-export`.
 */
export_name?: (string | null)
/**
 * Dead-code finding line when available.
 */
line?: (number | null)
/**
 * Agent-facing guidance for deciding between deletion and hardening.
 */
guidance: string
}
/**
 * Graph-derived reachability ranking signal for a security candidate. Computed
 * from the existing module graph after detection, never proven exploitable.
 * Used to surface candidates that sit on a request/runtime-reachable surface,
 * receive same-module source evidence, or are import-reachable from an
 * untrusted-source module above isolated helpers or scripts.
 *
 * This is a relative-ordering signal, NOT a `confidence` or `signal_strength`
 * score: fallow does not prove the path is exploitable.
 */
export interface SecurityReachability {
/**
 * Whether the anchor module is reachable from a runtime/application entry
 * point (route handlers, server entry, framework runtime roots), the
 * closest graph proxy for an external/request input surface. Code reachable
 * only from test entry points does not count.
 */
reachable_from_entry: boolean
/**
 * Whether the anchor module is reachable over value imports from a module
 * that reads a known untrusted input source. Module-level only: this does
 * not prove a specific source value reaches the sink argument.
 */
reachable_from_untrusted_source?: boolean
/**
 * Structured tier of the untrusted-source association: `arg-level` when the
 * sink argument traces to a same-module source read (strong), `module-level`
 * when only the module is import-reachable from a source (weak). Present
 * exactly when `reachable_from_untrusted_source` is true, so a consumer can
 * separate strong from weak candidates from this field alone without parsing
 * the `evidence` string. Not an exploitability proof.
 */
taint_confidence?: (TaintConfidence | null)
/**
 * Number of value-import hops from the untrusted-source module to the sink
 * module when `reachable_from_untrusted_source` is true.
 */
untrusted_source_hop_count?: (number | null)
/**
 * Module-level import path from the untrusted-source module to the sink
 * anchor. Empty when no source module reaches this candidate. The path is a
 * ranking explanation, not a value-flow proof.
 */
untrusted_source_trace?: TraceHop[]
/**
 * Number of distinct modules that transitively depend on the anchor module
 * (fan-in via the graph's reverse-dependency index). A higher value means a
 * wider surface: more call sites could route untrusted input into the sink.
 */
blast_radius: number
/**
 * Whether the anchor module participates in an architecture-boundary
 * violation found in the same run (as the importing or imported file).
 * Optional pairing: a candidate that also crosses a declared boundary is a
 * stronger review target.
 */
crosses_boundary: boolean
}
/**
 * An agent-actionable candidate record on a [`SecurityFinding`]. fallow fills
 * `source_kind`, `sink`, and `boundary`. The exploitability IMPACT is
 * deliberately NOT a field: `severity` on the parent finding is only a
 * review-priority tier, while deciding exploitability remains the consuming
 * agent's job. A perpetually-null `impact` key would only train consumers to
 * ignore it. The agent reads this record, then writes its own impact verdict
 * downstream.
 */
export interface SecurityCandidate {
/**
 * The kind of untrusted input that reaches the sink, as a stable catalogue
 * source id (`"http-request-input"`, `"process-env"`, `"process-argv"`,
 * `"message-event-data"`, `"location-input"`, ...). `None`/absent when no
 * untrusted source was matched (always `None` for `client-server-leak`).
 * This is an OPEN string set, driven by the data-driven source catalogue; a
 * consumer should treat an unknown id as "untrusted source of unknown kind"
 * and never drop the candidate on that basis.
 */
source_kind?: (string | null)
sink: SecurityCandidateSink
boundary: SecurityCandidateBoundary
/**
 * Network-destination context, present only on `secret-to-network` (#890)
 * candidates: the host the secret-bearing call targets, so an agent can
 * triage exfil from intended auth. Absent for every other category.
 */
network?: (SecurityNetworkContext | null)
}
/**
 * The sink slot of a [`SecurityCandidate`]: a self-contained description of the
 * matched sink site. Echoes the finding's own span (`path`/`line`/`col`) plus
 * the catalogue `category`/`cwe` and the captured `callee`, so an agent can act
 * on `candidate.sink` in isolation (e.g. after fanning a finding out to a
 * sub-agent) without reading the parent finding.
 */
export interface SecurityCandidateSink {
/**
 * File of the sink site. Absolute internally; JSON strips the project root
 * via `serde_path::serialize`.
 */
path: string
/**
 * 1-based line of the sink site.
 */
line: number
/**
 * 0-based byte column of the sink site.
 */
col: number
/**
 * Catalogue category id of the sink (e.g. `"dangerous-html"`). For
 * `client-server-leak` this is `None` for the secret-leak finding, and
 * `Some("server-only-import")` when a `"use client"` cone reaches
 * server-only code.
 */
category?: (string | null)
/**
 * CWE number declared by the catalogue entry. `None` for
 * `client-server-leak`; never fabricated beyond the catalogue's value.
 */
cwe?: (number | null)
/**
 * The sink callee (the dangerous function or member path, e.g.
 * `"el.innerHTML"`, `"child_process.exec"`) captured by the catalogue match.
 * `None` for `client-server-leak` and matches that name no callee.
 */
callee?: (string | null)
/**
 * URL construction shape for SSRF and open-redirect style candidates when
 * fallow can classify whether the origin is fixed or dynamic. Absent for
 * non-URL sinks and unclassified URL expressions.
 */
url_shape?: (SecurityUrlShape | null)
}
/**
 * The boundary slot of a [`SecurityCandidate`]: which structural boundaries the
 * candidate's flow crosses. A flow that crosses a client/server or module
 * boundary is a stronger review target than a self-contained one; the boundary
 * is fallow's structural signal over a pure source-sink match.
 *
 * Two further boundary kinds are RESERVED for a follow-up and are deliberately
 * absent here rather than emitted as always-false: `export_visibility` (is the
 * sink on a publicly-exported symbol?) and a package boundary (does the flow
 * cross an npm-package edge?). Both need new graph derivation that does not
 * exist today; emitting them as `false` would misreport "we checked and it does
 * not cross" when fallow has not checked at all.
 */
export interface SecurityCandidateBoundary {
/**
 * Whether the finding crosses a client/server boundary (a `"use client"`
 * file appears in the trace). True only for `client-server-leak` today;
 * `tainted-sink` candidates carry no client/server marker.
 */
client_server: boolean
/**
 * Whether an untrusted source reaches the sink across one or more
 * value-import (module) hops. Derived from the reachability hop count.
 */
cross_module: boolean
/**
 * The architecture-zone crossing when the anchor participates in a declared
 * boundary-rule violation in the same run. `None` when it crosses no
 * declared zone boundary.
 */
architecture_zone?: (SecurityZoneCrossing | null)
}
/**
 * A declared architecture-zone crossing, recovered by correlating a finding's
 * anchor against the run's architecture-boundary violations.
 */
export interface SecurityZoneCrossing {
/**
 * Zone the importing side belongs to.
 */
from: string
/**
 * Zone the imported side belongs to.
 */
to: string
}
/**
 * Network-destination context for a `secret-to-network` candidate (#890): where
 * the secret-bearing network call sends its data. Present only on
 * network-category candidates. A consuming agent uses it to triage exfil
 * (dynamic / untrusted destination) from intended auth (a literal provider
 * host) without re-reading source.
 */
export interface SecurityNetworkContext {
/**
 * The network call's destination as a static URL string literal, or absent
 * when the destination is DYNAMIC (not a literal). A dynamic destination is
 * the higher-signal exfil case; a literal provider host is usually intended
 * auth.
 */
destination?: (string | null)
}
/**
 * A source-to-sink taint-flow triple, emitted only when an untrusted source is
 * import-reachable to the sink (`reachability.reachable_from_untrusted_source`).
 * The `{ source, sink, path }` shape matches the model agent SAST tooling
 * expects (cf. Semgrep `taint_source` / `taint_sink`, SARIF `threadFlows`).
 */
export interface SecurityTaintFlow {
source: TaintEndpoint
sink: TaintEndpoint
path: TaintPath
}
/**
 * One endpoint (source or sink node) of a [`SecurityTaintFlow`].
 */
export interface TaintEndpoint {
/**
 * File of the endpoint. Absolute internally; JSON strips the project root.
 */
path: string
/**
 * 1-based line of the endpoint.
 */
line: number
/**
 * 0-based byte column of the endpoint.
 */
col: number
}
/**
 * Compact taint-flow path shape. The ordered per-hop trace is NOT duplicated
 * here: it lives on [`SecurityReachability::untrusted_source_trace`]. This
 * carries only the flow's structural summary (intra-module flow plus the
 * cross-module hop count) so consumers do not parse two copies of the hops.
 */
export interface TaintPath {
/**
 * Whether the source and sink sit in the same module (no import hop between
 * them); the source-to-sink association is intra-module.
 */
intra_module: boolean
/**
 * Number of value-import hops from the untrusted-source module to the sink
 * module. Zero for an intra-module flow.
 */
cross_module_hops: number
}
/**
 * Runtime coverage context attached to a security candidate when
 * `fallow security --runtime-coverage` is supplied.
 */
export interface SecurityRuntimeContext {
state: SecurityRuntimeState
/**
 * Enclosing function name from static extraction.
 */
function: string
/**
 * 1-based line where the enclosing function starts.
 */
line: number
/**
 * Observed invocation count when the runtime report provides it.
 */
invocations?: (number | null)
/**
 * Runtime coverage stable function id, when available.
 */
stable_id?: (string | null)
/**
 * Short candidate-framed explanation of the runtime evidence.
 */
evidence?: (string | null)
}
/**
 * One untrusted entry to reachable sink path for `fallow security --surface`.
 */
export interface SecurityAttackSurfaceEntry {
source: TaintEndpoint
sink: SecurityCandidateSink
/**
 * Ordered source to sink path. Same shape as the reachability trace so
 * consumers can reuse existing path handling.
 */
path: TraceHop[]
defensive_boundary: SecurityDefensiveBoundary
}
/**
 * Agent-facing defensive-boundary verification context for one surface path.
 */
export interface SecurityDefensiveBoundary {
/**
 * Known controls detected along this path.
 */
controls: SecurityDefensiveControl[]
/**
 * Verification question for the consuming agent. It is a prompt, not a
 * missing-guard verdict.
 */
verification_prompt: string
}
/**
 * Defensive control found on an attack-surface path.
 */
export interface SecurityDefensiveControl {
kind: SecurityControlKind
/**
 * File of the control site. Absolute internally; JSON strips the project root.
 */
path: string
/**
 * 1-based line of the control site.
 */
line: number
/**
 * 0-based byte column of the control site.
 */
col: number
/**
 * Flattened callee path or a stable synthetic guard name.
 */
callee: string
}
/**
 * Bounded unresolved-callee diagnostics for `fallow security --format json`.
 */
export interface SecurityUnresolvedCalleeDiagnostics {
/**
 * Deterministic sample rows, capped by `sample_limit`.
 */
sampled: SecurityUnresolvedCalleeSample[]
/**
 * Files with the most unresolved callees, capped by `top_files_limit`.
 */
top_files: SecurityUnresolvedCalleeTopFile[]
/**
 * Full count by unresolved-callee reason, sorted by count then reason.
 */
by_reason: SecurityUnresolvedCalleeReasonCount[]
/**
 * Maximum number of sample rows emitted.
 */
sample_limit: number
/**
 * Maximum number of top-file rows emitted.
 */
top_files_limit: number
}
/**
 * One sampled unresolved-callee row.
 */
export interface SecurityUnresolvedCalleeSample {
/**
 * Project-relative source path.
 */
path: string
/**
 * 1-based source line.
 */
line: number
/**
 * 0-based byte column.
 */
col: number
reason: SkippedSecurityCalleeReason
expression_kind: SkippedSecurityCalleeExpressionKind
}
/**
 * Count of unresolved callees in one file.
 */
export interface SecurityUnresolvedCalleeTopFile {
/**
 * Project-relative source path.
 */
path: string
/**
 * Number of unresolved callees in this file.
 */
count: number
}
/**
 * Count of unresolved callees for one reason.
 */
export interface SecurityUnresolvedCalleeReasonCount {
reason: SkippedSecurityCalleeReason
/**
 * Number of unresolved callees with this reason.
 */
count: number
}
/**
 * Compact `fallow security --summary --format json` payload. Uses the same
 * `kind: "security"` discriminator as the full payload, but omits candidate
 * arrays and exposes only aggregate counts.
 */
export interface SecuritySummaryOutput {
schema_version: SecuritySchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
config: SecurityOutputConfig
/**
 * Security-specific rule and field metadata, emitted with `--explain`.
 */
_meta?: (Meta | null)
/**
 * Gate verdict, present only when `--gate <mode>` was set.
 */
gate?: (SecurityGate | null)
summary: SecuritySummary
}
/**
 * Aggregate counts for `fallow security --summary --format json`.
 */
export interface SecuritySummary {
/**
 * Number of security candidates after all filters, gates, and scopes.
 */
security_findings: number
by_severity: SecuritySeverityCounts
/**
 * Finding counts by catalogue category, or by kind for findings without a
 * catalogue category.
 */
by_category: {
[k: string]: number
}
by_reachability: SecurityReachabilityCounts
by_runtime_state: SecurityRuntimeStateCounts
/**
 * Number of client files whose dynamic imports could not be followed.
 */
unresolved_edge_files: number
/**
 * Number of sink-shaped callees that could not be statically flattened.
 */
unresolved_callee_sites: number
/**
 * Number of attack-surface entries included in the prepared full output.
 */
attack_surface_entries: number
}
/**
 * Fixed severity counters for summary JSON.
 */
export interface SecuritySeverityCounts {
high: number
medium: number
low: number
}
/**
 * Fixed reachability counters for summary JSON.
 */
export interface SecurityReachabilityCounts {
entry_reachable: number
untrusted_source_reachable: number
arg_level: number
module_level: number
crosses_boundary: number
source_backed: number
}
/**
 * Fixed runtime coverage counters for summary JSON.
 */
export interface SecurityRuntimeStateCounts {
runtime_hot: number
runtime_cold: number
never_executed: number
low_traffic: number
coverage_unavailable: number
runtime_unknown: number
not_collected: number
}
/**
 * Bare `fallow --format json` envelope.
 */
export interface CombinedOutput {
schema_version: SchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
_meta?: (CombinedMeta | null)
check?: (CheckOutput | null)
dupes?: (DupesReportPayload | null)
health?: (HealthReport | null)
/**
 * Read-only follow-up commands aggregated across the combined run's
 * findings. See [`CheckOutput::next_steps`] for the contract.
 */
next_steps?: NextStep[]
}
export interface CombinedMeta {
check?: (Meta | null)
dupes?: (Meta | null)
health?: (Meta | null)
telemetry?: (TelemetryMeta | null)
}
/**
 * Single CodeClimate-compatible issue inside [`CodeClimateOutput`].
 */
export interface CodeClimateIssue {
type: CodeClimateIssueKind
check_name: string
description: string
categories: string[]
severity: CodeClimateSeverity
fingerprint: string
location: CodeClimateLocation
}
/**
 * Location block inside [`CodeClimateIssue::location`].
 */
export interface CodeClimateLocation {
/**
 * File path relative to the analysed root.
 */
path: string
lines: CodeClimateLines
}
/**
 * `lines.begin` for [`CodeClimateLocation`].
 */
export interface CodeClimateLines {
/**
 * 1-based start line.
 */
begin: number
}

/**
 * Inner complexity-violation payload, flattened into `HealthFinding`
 * on the wire via `#[serde(flatten)]`. Exposed here because
 * json-schema-to-typescript dedupes definitions whose property set is
 * fully subsumed by a flattening parent; the schema definition exists
 * in `docs/output-schema.json` but the TS interface is suppressed.
 * Consumers that need to type just the inner payload should use this
 * alias; consumers that need the full envelope (with `actions` and
 * optional `introduced`) should use `HealthFinding` directly.
 */
export type ComplexityViolation = Omit<HealthFinding, "actions" | "introduced">;

/**
 * Inner hotspot payload, flattened into `HotspotFinding` on the wire
 * via `#[serde(flatten)]`. Exposed here for the same reason as
 * `ComplexityViolation`: jstt dedupes the inner because its property
 * set is fully subsumed by the wrapper. Consumers that want only the
 * inner shape should use this alias; consumers that need the full
 * envelope with `actions` should use `HotspotFinding` directly.
 * Unlike `HealthFinding`, the wrapper does not carry `introduced`
 * because hotspot ranking does not run through audit attribution.
 */
export type HotspotEntry = Omit<HotspotFinding, "actions">;

/**
 * Inner refactoring-target payload, flattened into
 * `RefactoringTargetFinding` on the wire via `#[serde(flatten)]`.
 * Exposed here for the same reason as `ComplexityViolation`: jstt
 * dedupes the inner because its property set is fully subsumed by
 * the wrapper. Consumers that want only the inner shape should use
 * this alias; consumers that need the full envelope with `actions`
 * should use `RefactoringTargetFinding` directly. Unlike
 * `HealthFinding`, the wrapper does not carry `introduced` because
 * refactoring targets do not run through audit attribution.
 */
export type RefactoringTarget = Omit<RefactoringTargetFinding, "actions">;

//
// =============================================================================
// Backwards-compat aliases
// =============================================================================
//
// The aliases below map pre-#384 / #408 / #409 bare names to their typed
// envelope wrappers. The wire shape is byte-identical: each wrapper flattens
// the bare finding's fields via `#[serde(flatten)]` and adds `actions[]`
// (and, where the wrapper participates in `fallow audit` attribution, the
// optional `introduced` flag). Per-alias rationale lives in each alias's
// JSDoc below.
//
// Why these aliases exist: `json-schema-to-typescript` drops the orphan
// inner definitions for `#[serde(flatten)]` wrappers (even with
// `unreachableDefinitions: true`), so the bare names disappear from this
// generated `.d.ts` without an explicit alias. External consumers that
// import the bare names from `fallow/types` would break at upgrade time
// otherwise.
//
// Stability commitment: these aliases ship as part of fallow's v2.x stable
// surface. They are scheduled for removal alongside the kind-tagged
// `FallowOutput` major bump (see https://github.com/fallow-rs/fallow/issues/413),
// with a one-minor-cycle deprecation window (`@deprecated` JSDoc +
// CHANGELOG headline) preceding the removal. New code should prefer the
// `*Finding` wrapper names. Full public-consumer policy:
// https://github.com/fallow-rs/fallow/blob/main/docs/backwards-compatibility.md
//

/**
 * Backwards-compat alias for the pre-#409 bare clone-group name.
 * jstt dedupes the bare interface because every field is fully
 * subsumed by `CloneGroupFinding` (the wrapper flattens the bare
 * `CloneGroup` via `#[serde(flatten)]`). Aliased to the full
 * wrapper (not `Omit<>`-stripped) because the pre-migration wire
 * always carried `actions[]` on every clone group via the legacy
 * `inject_dupes_actions` post-pass, so the bare alias matching the
 * wrapper shape is the byte-faithful continuation. Consumers that
 * imported `CloneGroup` from `fallow/types` pre-migration continue
 * to work via this alias; new code should prefer `CloneGroupFinding`.
 */
export type CloneGroup = CloneGroupFinding;

/**
 * Backwards-compat alias for the pre-#409 bare clone-family name.
 * jstt dedupes the bare interface because every field is subsumed
 * by `CloneFamilyFinding`. The wrapper's `groups[]` items are
 * `CloneGroupFinding` rather than bare `CloneGroup`, which matches
 * the pre-migration wire shape (the legacy `inject_dupes_actions`
 * post-pass injected `actions[]` on every nested group too).
 * Consumers that imported `CloneFamily` from `fallow/types`
 * pre-migration continue to work via this alias; new code should
 * prefer `CloneFamilyFinding`.
 */
export type CloneFamily = CloneFamilyFinding;

/**
 * Backwards-compat alias for the pre-#409 bare attributed-clone-group
 * name (`fallow dupes --group-by` per-bucket attribution).
 * Consumers that imported `AttributedCloneGroup` from `fallow/types`
 * pre-migration continue to work via this alias; new code should
 * prefer `AttributedCloneGroupFinding`.
 */
export type AttributedCloneGroup = AttributedCloneGroupFinding;

/**
 * Backwards-compat alias for the pre-#409 `DuplicationReport` name.
 * The wire shape is byte-identical between the two: the typed
 * `DupesReportPayload` mirrors `DuplicationReport` field-for-field
 * with `clone_groups[]` / `clone_families[]` carrying typed
 * `CloneGroupFinding` / `CloneFamilyFinding` wrappers instead of
 * bare findings (the pre-migration wire ALSO carried `actions[]`
 * on each item via the legacy `inject_dupes_actions` post-pass).
 * Consumers that imported `DuplicationReport` from `fallow/types`
 * pre-migration continue to work via this alias; new code should
 * prefer `DupesReportPayload`.
 */
export type DuplicationReport = DupesReportPayload;

/**
 * Backwards-compat alias for the pre-#384 bare `BoundaryViolation` name.
 * The wire shape is byte-identical: `BoundaryViolationFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `BoundaryViolation` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `BoundaryViolationFinding`.
 */
export type BoundaryViolation = BoundaryViolationFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `CircularDependency` name.
 * The wire shape is byte-identical: `CircularDependencyFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `CircularDependency` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `CircularDependencyFinding`.
 */
export type CircularDependency = CircularDependencyFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `DuplicateExport` name.
 * The wire shape is byte-identical: `DuplicateExportFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `DuplicateExport` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `DuplicateExportFinding`.
 */
export type DuplicateExport = DuplicateExportFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `EmptyCatalogGroup` name.
 * The wire shape is byte-identical: `EmptyCatalogGroupFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `EmptyCatalogGroup` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `EmptyCatalogGroupFinding`.
 */
export type EmptyCatalogGroup = EmptyCatalogGroupFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `MisconfiguredDependencyOverride` name.
 * The wire shape is byte-identical: `MisconfiguredDependencyOverrideFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `MisconfiguredDependencyOverride` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `MisconfiguredDependencyOverrideFinding`.
 */
export type MisconfiguredDependencyOverride = MisconfiguredDependencyOverrideFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `PrivateTypeLeak` name.
 * The wire shape is byte-identical: `PrivateTypeLeakFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `PrivateTypeLeak` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `PrivateTypeLeakFinding`.
 */
export type PrivateTypeLeak = PrivateTypeLeakFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `ReExportCycle` name.
 * The wire shape is byte-identical: `ReExportCycleFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `ReExportCycle` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `ReExportCycleFinding`.
 */
export type ReExportCycle = ReExportCycleFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `TestOnlyDependency` name.
 * The wire shape is byte-identical: `TestOnlyDependencyFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `TestOnlyDependency` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `TestOnlyDependencyFinding`.
 */
export type TestOnlyDependency = TestOnlyDependencyFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `TypeOnlyDependency` name.
 * The wire shape is byte-identical: `TypeOnlyDependencyFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `TypeOnlyDependency` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `TypeOnlyDependencyFinding`.
 */
export type TypeOnlyDependency = TypeOnlyDependencyFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `UnlistedDependency` name.
 * The wire shape is byte-identical: `UnlistedDependencyFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `UnlistedDependency` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `UnlistedDependencyFinding`.
 */
export type UnlistedDependency = UnlistedDependencyFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `UnresolvedCatalogReference` name.
 * The wire shape is byte-identical: `UnresolvedCatalogReferenceFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `UnresolvedCatalogReference` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `UnresolvedCatalogReferenceFinding`.
 */
export type UnresolvedCatalogReference = UnresolvedCatalogReferenceFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `UnresolvedImport` name.
 * The wire shape is byte-identical: `UnresolvedImportFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `UnresolvedImport` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `UnresolvedImportFinding`.
 */
export type UnresolvedImport = UnresolvedImportFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `UnusedCatalogEntry` name.
 * The wire shape is byte-identical: `UnusedCatalogEntryFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `UnusedCatalogEntry` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `UnusedCatalogEntryFinding`.
 */
export type UnusedCatalogEntry = UnusedCatalogEntryFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `UnusedDependencyOverride` name.
 * The wire shape is byte-identical: `UnusedDependencyOverrideFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `UnusedDependencyOverride` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `UnusedDependencyOverrideFinding`.
 */
export type UnusedDependencyOverride = UnusedDependencyOverrideFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `UnusedExport` name.
 * The wire shape is byte-identical: `UnusedExportFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `UnusedExport` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `UnusedExportFinding`.
 */
export type UnusedExport = UnusedExportFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `UnusedFile` name.
 * The wire shape is byte-identical: `UnusedFileFinding` flattens the bare
 * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
 * the optional audit-mode `introduced` flag. Consumers that imported
 * `UnusedFile` from `fallow/types` pre-migration continue to work via
 * this alias; new code should prefer `UnusedFileFinding`.
 */
export type UnusedFile = UnusedFileFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `UnusedDependency` union name.
 * Maps to the union of typed wrappers (`UnusedDependencyFinding`, `UnusedDevDependencyFinding`, `UnusedOptionalDependencyFinding`)
 * that replaced the pre-migration bare union. The wire shape per variant
 * is byte-identical (each wrapper flattens its bare payload and adds
 * `actions[]` plus optional `introduced`). Consumers that imported
 * `UnusedDependency` from `fallow/types` pre-migration continue to work via
 * this alias; new code should narrow on the specific wrapper variant.
 */
export type UnusedDependency = UnusedDependencyFinding | UnusedDevDependencyFinding | UnusedOptionalDependencyFinding;

/**
 * Backwards-compat alias for the pre-#384 bare `UnusedMember` union name.
 * Maps to the union of typed wrappers (`UnusedClassMemberFinding`, `UnusedEnumMemberFinding`, `UnusedStoreMemberFinding`)
 * that replaced the pre-migration bare union. The wire shape per variant
 * is byte-identical (each wrapper flattens its bare payload and adds
 * `actions[]` plus optional `introduced`). Consumers that imported
 * `UnusedMember` from `fallow/types` pre-migration continue to work via
 * this alias; new code should narrow on the specific wrapper variant.
 */
export type UnusedMember = UnusedClassMemberFinding | UnusedEnumMemberFinding | UnusedStoreMemberFinding;
