fallow
Version:
Codebase intelligence for TypeScript and JavaScript: health, complexity, duplication, architecture, styling drift, and unused code from one graph. CLI, LSP, and MCP server. Zero config for over 100 frameworks.
18,100 lines • 590 kB
TypeScript
/**
* AUTOGENERATED FILE. DO NOT EDIT.
*
* Generated from `docs/output-schema.json` by
* `editors/vscode/scripts/codegen-contracts.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. Find the Rust owner through `derived_definition_names()` and its
* imports in `crates/cli/src/bin/schema_emit.rs`, then edit that type.
* Rust is the runtime source of truth for the JSON output.
* 2. Regenerate `docs/output-schema.json` with
* `cargo run -p fallow-cli --features schema-emit --bin fallow-schema-emit`.
* The schema-emit drift tests compare the committed schema against the
* Rust-derived definitions.
* 3. Run `pnpm --filter fallow-vscode codegen:contracts` from anywhere.
* 4. Commit the regenerated schema and generated contract files together.
*
* CI runs `pnpm --filter fallow-vscode check:contracts` 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. Current kind values: `audit`, `explain`, `inspect_target`, `trace`, `trace-error`, `review-envelope`, `review-reconcile`, `coverage-setup`, `coverage-analyze`, `list-boundaries`, `list-workspaces`, `health`, `dupes`, `dead-code-grouped`, `impact`, `impact-cross-repo`, `security`, `security-survivors`, `security-blind-spots`, `dead-code`, `combined`, `feature-flags`, `audit-brief`, `decision-surface`, `review-walkthrough-guide`, `review-walkthrough-validation`, `suppression-inventory`, `doctor`, `type-aware-status`, `similar-code`, `similar-code-inspect`, `similar-code-review`. Consumers should branch on `kind` instead of probing for unique field presence. `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. `ErrorOutput` is the `--format json` failure document, emitted on stdout with a non-zero exit; it carries no `kind` and is discriminated by the `error: true` field.
*/
export type FallowJsonOutput = (FallowOutput | CodeClimateOutput | ErrorOutput)
/**
* 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 wire shape 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.
*
* 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"
}) | (InspectOutput & {
kind: "inspect_target"
}) | ((ExportTrace | ClassMemberTrace | FileTrace | DependencyTrace | CloneTrace | ImpactClosureTrace | ImportPathTrace | SymbolChainTrace | SemanticSymbolTrace) & {
kind: "trace"
}) | (ErrorTrace & {
kind: "trace-error"
}) | (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 | SemanticSymbolImpact) & {
kind: "impact"
}) | (CrossRepoImpactReport & {
kind: "impact-cross-repo"
}) | ((SecurityOutput | SecuritySummaryOutput) & {
kind: "security"
}) | (SecuritySurvivorsOutput & {
kind: "security-survivors"
}) | (SecurityBlindSpotsOutput & {
kind: "security-blind-spots"
}) | (CheckOutput & {
kind: "dead-code"
}) | (CombinedOutput & {
kind: "combined"
}) | (FeatureFlagsOutput & {
kind: "feature-flags"
}) | (ReviewBriefWireOutput & {
kind: "audit-brief"
}) | (DecisionSurfaceOutput & {
kind: "decision-surface"
}) | (WalkthroughGuide & {
kind: "review-walkthrough-guide"
}) | (WalkthroughValidation & {
kind: "review-walkthrough-validation"
}) | (SuppressionInventoryOutput & {
kind: "suppression-inventory"
}) | (DoctorOutput & {
kind: "doctor"
}) | (TypeAwareStatusOutput & {
kind: "type-aware-status"
}) | (SimilarCodeOutput & {
kind: "similar-code"
}) | (SimilarCodeInspectOutput & {
kind: "similar-code-inspect"
}) | (SimilarCodeReviewOutput & {
kind: "similar-code-review"
}))
/**
* Schema projection for the audit envelope's exact version.
*/
export type AuditSchemaVersion = 12
/**
* 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
/**
* Audit command singleton carried by [`AuditOutput`].
*/
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
/**
* Value of `audit.gate`: which findings drive the `fallow audit` verdict.
*/
export type AuditGate = ("new-only" | "all")
/**
* What a gate concluded on this run.
*
* Four-valued rather than a boolean because audit's verdict has a warn tier
* (`crates/cli/src/cli_report.rs` maps it onto three conclusions) and because
* a gate can stand down without passing. Widening a published boolean later
* would retype a required field and bump every carrying envelope, so the width
* is decided here.
*/
export type GateStatus = ("pass" | "warn" | "fail" | "skipped")
/**
* Analysis mode stored with baselines, snapshots, audit sides, and impact data.
*/
export type SemanticAnalysisMode = ("syntactic" | "type-aware")
/**
* Semantic capabilities that can share one TypeScript Program session.
*/
export type SemanticCapability = ("symbol-use" | "symbol-trace" | "api-surface" | "symbol-impact" | "type-coupling")
/**
* Whether the semantic backend answered every requested query safely.
*/
export type SemanticCompleteness = ("complete" | "partial" | "unavailable")
/**
* Effective policy applied when semantic evidence is incomplete.
*/
export type SemanticCompletenessRequirement = ("best-effort" | "complete")
/**
* Stable reason why semantic evidence is partial or unavailable.
*/
export type SemanticGapReason = ("no-project" | "ambiguous-project" | "blocking-diagnostics" | "svelte-virtual-module-exports" | "unknown-symbol" | "unknown-entry-point" | "evidence-limit" | "dynamic-behavior" | "virtual-dispatch" | "dynamic-member-access" | "decorated-declaration" | "optional-contract" | "accessor-pair" | "overload-set" | "attached-comment" | "abstract-declaration" | "incomplete-project-coverage" | "framework-contract-provenance" | "capacity" | "unsupported-syntax")
/**
* Value or type namespace for one exact declaration or reference.
*/
export type SemanticNamespace = ("value" | "type")
/**
* Conservative outcome for one existing Fallow dead-code candidate.
*/
export type SemanticCandidateDecisionKind = ("confirmed-used" | "contract-preserved" | "confirmed-no-static-references" | "retained-abstained" | "retained-unresolved")
/**
* How a class member participates in an inherited or implemented relation.
*/
export type SemanticContractRelation = ("interface-implementation" | "abstract-implementation" | "override" | "optional-contract")
/**
* Heritage relation used by a framework-owned class-member contract.
*/
export type SemanticFrameworkRelation = ("extends" | "implements")
/**
* Confidence of exact-symbol impact analysis after known dynamic gaps.
*/
export type SemanticImpactConfidence = ("high" | "bounded" | "unavailable")
/**
* How a TypeScript project was selected for semantic refinement.
*/
export type TypeAwareProjectSource = ("auto" | "explicit")
/**
* Outcome of semantic refinement for one TypeScript project.
*/
export type TypeAwareProjectStatus = ("refined" | "abstained" | "complete" | "unavailable")
/**
* How a persistent semantic snapshot was refreshed.
*/
export type TypeAwareInvalidationKind = ("full" | "incremental" | "none")
/**
* Closed set of reasons for retaining a candidate without semantic scanning.
*/
export type TypeAwareAbstentionReason = ("no-project" | "ambiguous-project" | "blocking-diagnostics")
/**
* Schema projection for the dead-code envelope's exact version.
*/
export type CheckSchemaVersion = 10
/**
* 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 and the source is
* `pnpm-workspace.yaml`. When a workspace package still pins a hardcoded
* version of the same package, `fallow fix` skips the entry to avoid
* breaking `pnpm install`. Bun `package.json` catalog entries are also
* emitted with `auto_fixable: false` because the current fixer is
* YAML-only.
* - `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" | "move-to-prod" | "refactor-cycle" | "refactor-re-export-cycle" | "refactor-boundary" | "export-type" | "migrate-deprecated-export" | "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" | "wire-server-action" | "provide-inject" | "use-load-data" | "render-component" | "use-component-prop" | "review-component-prop" | "emit-component-event" | "wire-svelte-event" | "resolve-route-collision" | "resolve-dynamic-segment-name-conflict" | "add-suppression-reason" | "remove-stale-suppression")
/**
* 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. Duplication findings carry one carve-out: a clone
* group whose structural key is new but whose instances contain no added line
* from the diff (a group re-shaped by removing duplication elsewhere) is
* demoted to inherited and serializes `false` (issue #2164). Such demoted
* groups additionally carry a `demotion_reason` field naming the rule, and
* are counted in `attribution.duplication_demoted` (issue #2220).
*
* 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
/**
* Gate severity of one finding after rule resolution.
*
* It is the severity that `rules` and the matching `overrides[].rules` give
* the finding for its path. `--fail-on-issues` raises `warn` to `error`. A
* finding whose rule is `off` is not reported, so there is no `off` value.
* The type is separate from the health `severity` band, which ranks a
* finding and does not gate it.
*
* The `fallow dead-code` findings gate fails when a finding is `error`.
* Other gates (regression, stale baseline) decide on their own inputs. Two
* commands differ: the `fallow audit` `new-only` gate fails only on introduced
* findings, so an inherited `error` finding does not fail the audit, and the
* combined command (`fallow` without a subcommand) exits 0 for machine
* formats unless `--fail-on-issues` or `--ci` is set.
*
* Complexity findings carry the same type. The `complexity-cyclomatic`,
* `complexity-cognitive` and `complexity-crap` rules set it, and the
* `fallow health` findings gate and the audit verdict read it.
*/
export type EffectiveSeverity = ("error" | "warn")
/**
* A per-finding caveat on a dead-code verdict that a file this run never
* fully analyzed can distort.
*
* Advisory provenance, in the same spirit as the fix path's
* `low_confidence_off_graph` / `low_confidence_unresolved_imports` skip
* reasons: a caveat NEVER withholds, reorders, downgrades, or re-severities
* the finding, and never changes an exit code. It records that the verdict
* was computed over an import graph fallow already knows is incomplete, so a
* reader who sees the finding also sees the caveat instead of having to
* notice a diagnostic at the other end of the envelope.
*
* Deliberately NOT named `confidence`: `health --targets` already emits a
* `confidence` key holding an enum string, and a shared consumer helper that
* met both would see the same key change type. Emitted on every finding type
* that registers it: the reachability arrays (`unused_files[]`,
* `unused_exports[]`, `unused_types[]`), the member arrays
* (`unused_enum_members[]`, `unused_class_members[]`, `unused_store_members[]`),
* and the three dependency arrays. Sorted and deduplicated, absent from the
* wire when empty. The set is open in the same sense
* `workspace_diagnostics[].kind` is: treat an unrecognised value as "some
* caveat" rather than as an error.
*/
export type ReachabilityCaveat = ("incomplete-file-analysis" | "incomplete-import-graph")
/**
* How a consumer references a deprecated export.
*/
export type DeprecatedConsumerKind = ("named-import" | "default-import" | "namespace-import" | "re-export" | "dynamic-import" | "side-effect-import")
/**
* 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" | "banned-effect" | "banned-export" | "gdp-proof-producer")
/**
* 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)
/**
* Human-authored reason after `--`, when present.
*/
reason?: (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
/**
* Human-authored reason after `--`, when present.
*/
reason?: (string | null)
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. The active package
* manager may fail at install time or silently no-op on these entries;
* surfacing them statically catches the issue first.
*/
export type DependencyOverrideMisconfigReason = ("unparsable-key" | "empty-value")
/**
* Which advisory a loaded baseline earned on this run.
*
* Mirrors `fallow_engine::baseline::BaselineStalenessWarning` so a consumer can
* render the same distinction the stderr warning makes, instead of inferring it
* from counts.
*/
export type BaselineStalenessAdvisory = ("none" | "zero-overlap" | "partial")
/**
* One channel that narrowed a run to part of the project.
*
* Serialized as kebab-case inside `scope_reasons` and published as an OPEN
* set, the same tolerate-unknown contract `gate_outcomes` keys carry: a name
* this build does not emit means "some narrowing", not an error.
*
* Which names a command can emit differs per command, because the three
* narrowing predicates see different state. `dead-code` reads the flags
* themselves and can name every channel. `dupes` and `health` see an already
* resolved changed-file set and report `changed-files`, because at that point
* the flag that produced it is gone. `health` reports `workspace` for both
* `--workspace` and `--changed-workspaces` for the same reason. A consumer
* must therefore not assume a given command emits a given name.
*/
export type ScopeReason = ("diff" | "changed-since" | "package-baselines" | "changed-files" | "workspace" | "changed-workspaces" | "scope" | "file" | "issue-type-filter" | "production" | "include-entry-exports")
/**
* One reason why a missing id does not prove that the finding is gone.
*
* Serialized as kebab-case inside `inconclusive_reasons`. The set is OPEN: a
* name this build does not emit means "some reason", not an error, and the
* query stays inconclusive.
*/
export type FindingIdQueryReason = ("diff" | "changed-since" | "package-baselines" | "changed-files" | "workspace" | "changed-workspaces" | "scope" | "file" | "issue-type-filter" | "production" | "include-entry-exports" | "baseline" | "rule-off" | "filtered")
/**
* Status of a regression-check pass.
*/
export type RegressionStatus = ("pass" | "exceeded" | "skipped")
/**
* Interpretation of [`RegressionResult::tolerance`].
*/
export type RegressionToleranceKind = ("absolute" | "percentage")
/**
* What became of one request on this run.
*
* Two-valued today. The value set is OPEN so a later `partial` needs no bump,
* and it is deliberately not added now: nothing emits it, and a permanently
* unused value reads as a measurement nobody takes.
*/
export type RequestStatus = ("applied" | "not-applied")
/**
* What a request governs, and therefore what its failure means.
*
* Published on every entry so a consumer selects on the class rather than on
* a name list. Without it the one sentence a consumer can write for the whole
* object ("the report is wider than requested") is false for any request that
* does not narrow, which is how a failed `--sarif-file` write came to be
* reported as an unscoped run. A request name added later carries its own
* class, so a consumer written today keeps saying the right thing about it.
*
* The value set is OPEN, like the names and the statuses: read a class this
* build does not recognise as "some request", not as an error, and do not read
* it as `scope`.
*/
export type RequestEffect = ("scope" | "artifact")
/**
* 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
/**
* True when this diagnostic reports a run whose RESULTS are degraded:
* something the user installed, wrote, or expected did not reach the
* analysis. Projected from [`WorkspaceDiagnosticKind::warns_on_stderr`],
* which is the same classification that decides whether the CLI prints a
* stderr line, so a CI log built from this field and a local non-quiet run
* say the same thing.
*
* Omitted when false, which is what keeps every clean run byte-identical.
* The two unconfigured-check kinds answer false on purpose: they fire in
* the product's default state on every project that never opted into
* boundaries or rule packs, so warning on them would warn forever. So does
* `excluded-by-default-ignore`, which is designed behavior on generated
* output; the alarm for that case is `no-source-files-analyzed`.
*
* Read this instead of hardcoding a kind allowlist: a degrading kind added
* in a later release then reaches an unchanged consumer.
*/
degrades_analysis?: boolean
} & 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"
} | {
/**
* YAML parse error text.
*/
error: string
kind: "malformed-pnpm-workspace-yaml"
} | {
/**
* 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"
} | {
kind: "skipped-source-dotdir"
} | {
/**
* Filesystem or UTF-8 decoding error from `read_to_string`.
*/
error: string
kind: "source-read-failure"
} | {
/**
* Number of parser diagnostics reported for the file.
*/
error_count: number
/**
* `true` when the parser abandoned the file instead of recovering, so
* the extracted module is a fragment at best.
*/
panicked: boolean
kind: "source-parse-degraded"
} | {
kind: "bun-lockb-override-resolution-skipped"
} | {
kind: "bun-lock-override-resolution-skipped"
} | {
kind: "pnpm-lock-override-resolution-skipped"
} | {
kind: "npm-lock-override-resolution-skipped"
} | {
kind: "bun-resolutions-shadowed-by-overrides"
} | {
cause: PnpmWorkspaceOverridesIgnoredCause
kind: "pnpm-workspace-overrides-ignored"
} | {
kind: "node-modules-missing"
} | {
kind: "boundaries-not-configured"
} | {
kind: "rule-packs-not-configured"
} | {
/**
* The built-in glob that matched, verbatim (for example
* `** /build/**`).
*/
pattern: string
/**
* Candidate source files this pattern excluded in this walk, across
* every directory it matched, not just the one `path` anchors at.
* Exact: the walk counts each excluded candidate once.
*/
file_count: number
/**
* Distinct directories this pattern matched at, `path` included, and
* not the number of directories that held the files. A
* directory-shaped pattern (`** /dist/**`) matches at the directory it
* names, so an excluded subtree counts once however many nested
* directories inside it held source: a `dist/` holding files in three
* sub-directories reports `1`. A file-shaped pattern (`** /*.min.js`)
* has no directory to collapse to and counts each matched file's own
* parent. Exact either way, and anything above `1` says `path` names
* one matched location out of several.
*/
directory_count: number
kind: "excluded-by-default-ignore"
} | {
/**
* Candidate source files the built-in ignore patterns removed from
* this walk, summed across every pattern. `0` when the walk found no
* candidate to exclude in the first place.
*/
excluded_file_count: number
kind: "no-source-files-analyzed"
} | {
/**
* Scoring error text.
*/
error: string
kind: "file-scores-unavailable"
} | {
/**
* Which input stopped it, as a kebab-case token: `not-a-repository`,
* `no-commits`, `invalid-since` or `churn-file-unreadable`. The set is
* open.
*
* The cause decides the remedy, which is why it is on the wire: a run
* outside a repository is fixed by running fallow inside one, a
* branch without a commit by committing, a malformed `--since` by
* respelling the flag, and a churn file that changed under the run by
* rerunning it. A consumer reading only the kind would offer the first
* remedy for all four.
*/
cause: string
kind: "hotspots-skipped"
} | {
/**
* `true` when the run also asked for ownership attribution, which a
* shallow clone skews further by inflating single-author dominance.
*/
ownership_requested: boolean
kind: "shallow-clone"
} | {
kind: "unpinned-clock"
} | {
/**
* Which input failed, as a kebab-case token: `invalid-bot-pattern` or
* `codeowners-parse-failed`. The set is open.
*/
cause: string
/**
* Underlying error text.
*/
error: string
kind: "ownership-unavailable"
} | {
/**
* Filesystem or JSON error text.
*/
error: string
kind: "trend-snapshot-unreadable"
} | {
/**
* Why the groups have no baseline, as a kebab-case token:
* `snapshot-has-no-groups` or `grouped-by-mismatch`. The set is open.
*/
cause: string
kind: "trend-group-baseline-unavailable"
} | {
/**
* The plugin that read the config, as it labels itself:
* `module-federation` for a standalone `module-federation.config.*`,
* or the bundler plugin (`webpack`, `rspack`, `rsbuild`, `vite`) that
* read the same options inline from its own config.
*/
plugin: string
/**
* The config key that was present and not fully readable (`exposes`,
* `remotes`), or the Module Federation runtime function whose
* argument was not readable (`registerRemotes`, `loadRemote`, `init`,
* `createInstance`). The set is open.
*/
key: string
/**
* Why it could not be read, as a kebab-case token:
* `not-object-literal`, `array-form`, `spread`,
* `unreadable-entries`, `unrecognized-call`,
* `import-target-unreadable` or `dynamic-argument`. The set is open.
*
* The reason decides the remedy, which is why it is on the wire: a
* value that is not an object literal is fixed by writing one, while
* unreadable entries are fixed by naming those entries in the config
* option the message points at.
*/
reason: string
kind: "plugin-config-unreadable"
} | {
/**
* The plugin that read the config, as it labels itself (`nuxt`).
*/
plugin: string
/**
* The config key whose effect is not modeled (`components`,
* `imports`), or the virtual module a file reads (`#components`,
* `#imports`). The set is open.
*/
key: string
/**
* Why the effect is not modeled, as a kebab-case token:
* `key-effect-not-modeled` when the key's own value is the reason,
* `config-property-unreadable` when a top-level property of the same
* config file could not be read statically, so no surface in it can
* be classified at all. The set is open.
*/
reason: string
kind: "plugin-effect-not-modeled"
} | {
kind: "coverage-auto-detected"
} | {
kind: "flag-age-shallow-clone"
} | {
/**
* Why no history is available, as a kebab-case token:
* `not-a-repository` or `no-commits`. The set is open.
*/
cause: string
kind: "flag-age-unavailable"
} | {
/**
* The `ignoreDependencies` entry, as written in the config.
*/
pattern: string
kind: "ignore-dependencies-glob-unmatched"
} | {
/**
* The `ignoreFindings` entry, as written in the config.
*/
pattern: string
kind: "ignore-findings-pattern-unmatched"
})
/**
* Why the declared pnpm version ignores the `overrides` section of
* `pnpm-workspace.yaml`.
*/
export type PnpmWorkspaceOverridesIgnoredCause = ("package-json-overrides" | "pnpm-version")
/**
* Discriminant for [`CloneGroupAction::kind`]. Mirrors the action types
* emitted by the legacy `build_clone_group_actions` walker.
*/
export type CloneGroupActionType = ("extract-shared" | "suppress-line")
/**
* Why the audit new-only gate demoted an introduced clone group to
* inherited. Serializes as a kebab-case string on the wire (for example
* `"no-added-lines"`).
*
* Further variants may be added in later releases; consumers should treat an
* unknown value as "some demotion reason" rather than failing.
*/
export type CloneDemotionReason = "no-added-lines"
/**
* 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" | "await" | "then")
/**
* 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")
/**
* Input format of the measured coverage behind `CoverageModel::Istanbul`.
*/
export type CoverageInputFormat = ("istanbul" | "v8")
/**
* Whether CRAP findings in the report used one coverage-source kind or a mix.
*/
export type CoverageSourceConsistency = ("uniform" | "mixed")
/**
* One health section that a run produced.
*
* A section is in [`HealthReport::sections`] when the run computed it and
* the report carries its result, also when that result is empty. The value
* set is OPEN: a later release can add a section, so a consumer must accept
* a token that it does not know.
*/
export type HealthSection = ("complexity" | "vital-signs" | "score" | "file-scores" | "coverage-gaps" | "hotspots" | "targets" | "trend" | "runtime-coverage" | "css")
/**
* Lifecycle state for a configured threshold override.
*/
export type ThresholdOverrideStatus = ("active" | "stale" | "insufficient" | "no_match")
/**
* Which threshold dimension a `thresholdOverrides` state row describes.
*
* One configured override produces one row per dimension it participates in,
* because the complexity ceilings and the CRAP ceiling are evaluated
* independently: raising `maxCyclomatic` says nothing about whether the unit
* still breaches `maxCrap`.
*/
export type ThresholdOverrideDimension = ("complexity" | "crap")
/**
* 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")
/**
* Encoding applied to a [`ContributorEntry::identifier`].
*/
export type ContributorIdentifierFormat = ("raw" | "handle" | "anonymized" | "hash")
/**
* Ownership lifecycle state of a file.
*/
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"
/**
* Where the run's reference epoch came from.
*
* Churn recency weighting and ownership staleness are measured against one
* instant. `head_commit` and `environment` resolve to the same value on every
* run over the same commit; `wall_clock` does not.
*/
export type ClockSource = ("environment" | "head_commit" | "wall_clock")
/**
* 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")
/**
* The per-call cost that `optimization_target.cost_score` multiplies with
* `invocations`.
*/
export type RuntimeCoverageCostBasis = ("inner_iterations" | "cognitive")
/**
* 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")
/**
* Detector status codes for framework health observability.
*/
export type FrameworkHealthDetectorStatus = ("active" | "disabled_by_config" | "abstained" | "not_checked")
/**
* Discriminant for [`CssCandidateAction::kind`].
*/
export type CssCandidateActionType = ("verify-unused" | "verify-undefined" | "consolidate" | "replace-with-token" | "standardize" | "simplify-selector")
/**
* Discriminant for [`UnusedAtRule::kind`].
*/
export type UnusedAtRuleKind = ("property-registration" | "layer")
/**
* The surface through which a design token is consumed. The `theme-var` /
* `css-var` / `utility` / `apply` kinds are Tailwind v4 `@theme` consumption; the
* `js-member` / `js-call` kinds are CSS-in-JS consumption (member access through a
* same-file or imported StyleX/vanilla-extract token binding, a StyleX
* theme-group call, or a PandaCSS token-path call). The kind is the disjoint origin signal that
* distinguishes a Tailwind token entry from a CSS-in-JS token entry in the
* shared `token_consumers` list.
*/
export type ConsumerKind = ("theme-var" | "css-var" | "utility" | "apply" | "js-member" | "js-call")
/**
* Trust level for a [`StylingHealth`] grade. TWO variants (not the three-tier
* `high`/`medium`/`low` of [`crate::Confidence`] / `FeatureFlagConfidence`) ON
* PURPOSE: styling confidence is binary (the grade is either reliable for the
* analyzed surface or it is not), not three distinct evidence tiers, so a
* never-emitted `Medium` would be dead surface. Serializes lowercase (`"high"` /
* `"low"`), matching the sibling confidence enums' vocabulary.
*/
export type StylingHealthConfidence = ("high" | "low")
/**
* Effective configured severity for a styling finding.
*/
export type StylingFindingSeverity = ("warn" | "error")
/**
* Confidence hint for a [`StylingFinding`].
*/
export type StylingFindingConfidence = ("high" | "low")
/**
* Agent handling hint for a [`StylingFinding`].
*/
export type StylingAgentDisposition = ("fix-confidently" | "verify-first")
/**
* `target` block of [`InspectOutput`], tagged by `type`.
*/
export type InspectTargetDescriptor = ({
/**
* File path relative to the analysed root.
*/
file: string
type: "file"
} | {
/**
* File path relative to the analysed root.
*/
file: string
/**
* Name of the inspected export.
*/
export_name: string
type: "symbol"
})
/**
* `identity` block of [`InspectOutput`]; shape follows the target type.
*/
export type InspectIdentity = (InspectFileIdentity | InspectSymbolIdentity)
/**
* Status of an [`InspectEvidenceSection`].
*/
export type InspectSectionStatus = ("ok" | "partial" | "unavailable" | "error")
/**
* Granularity an [`InspectEvidenceSection`] payload covers.
*/
export type InspectEvidenceScope = ("symbol" | "file" | "project_filtered_to_file")
/**
* Wire-version discriminator for [`ImportPathTrace`]. Independent from the
* global `SchemaVersion`: the import-path payload versions on its own cadence,
* like the other independently-versioned envelopes. Serializes as a string
* `const` so JSON consumers can switch on it.
*/
export type ImportPathTraceSchemaVersion = "1"
/**
* Best-effort classification of why a callee did not resolve to an edge.
*/
export type UnresolvedReason = ("local-or-global" | "member-or-dynamic")
/**
* Wire-version discriminator for [`ErrorTrace`]. Independent from the global
* `SchemaVersion` and from the other trace payloads, like
* [`crate::trace::ImportPathTraceSchemaVersion`]. Serializes as a string
* `const` so JSON consumers can switch on it.
*/
export type ErrorTraceSchemaVersion = "1"
/**
* Where a frame's source location sits relative to the analysed project.
*/
export type FrameOrigin = ("in_project" | "node_modules" | "out_of_corpus")
/**
* What the project graph could say about a frame's identifier.
*
* `not_attempted` is not a softer `not_found`: it records that the graph was
* never consulted, because the frame does not point at project source. Keeping
* them apart is what lets `resolved + ambiguous + not_found + not_attempted`
* equal the frame count without any of the four lying about what it measured.
*/
export type FrameResolution = ("resolved" | "ambiguous" | "not_found" | "not_attempted")
/**
* 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" | "fallow-review-envelope/v3")
/**
* 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")
/**
* Stable identifier used to isolate independent review integrations on the
* same pull or merge request.
*/
export type ReviewId = string
/**
* Schema-version discriminator for the review reconcile envelope.
*/
export type ReviewReconcileSchema = "fallow-review-reconcile/v1"
/**
* Schema-version discriminator for [`CoverageSetupOutput`].
*/
export type CoverageSetupSchemaVersion = "1"
/**
* Framework detected during coverage setup; drives which instrumentation
* guidance is emitted.
*/
export type CoverageSetupFramework = ("nextjs" | "nestjs" | "nuxt" | "sveltekit" | "astro" | "remix" | "vite" | "plain_node" | "unknown")
/**
* Package manager detected from the project's lockfile.
*/
export type CoverageSetupPackageManager = ("npm" | "pnpm" | "yarn" | "bun")
/**
* Runtime environment coverage capture must instrument.
*/
export type CoverageSetupRuntimeTarget = ("node" | "browser")
/**
* Schema-version discriminator for [`CoverageAnalyzeOutput`].
*/
export type CoverageAnalyzeSchemaVersion = ("1" | "2")
/**
* Discovery outcome for a [`LogicalGroup`].
*/
export type LogicalGroupStatus = ("ok" | "empty" | "invalid_path")
/**
* Exact schema version for [`HealthOutput`].
*/
export type HealthSchemaVersion = 11
/**
* 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")
/**
* What the group trend compared, for one group.
*
* The value set is open: read an unknown value as "no trend for this group".
*/
export type GroupTrendStatus = ("compared" | "new_group" | "no_group_baseline")
/**
* Schema projection for the duplication envelope's CLI and programmatic
* version lineages.
*/
export type DupesSchemaVersion = (4 | 10)
/**
* 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" | "2")
/**
* 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" | "2")
/**
* 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" | "8")
/**
* 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")
/**
* Family of a control pattern observed in a file on an import trace.
*/
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")
/**
* The `fallow security survivors --format json` schema version.
*/
export type SecuritySurvivorsSchemaVersion = "2"
/**
* Verifier verdict status accepted by `fallow security survivors`.
*/
export type SecurityVerifierVerdictStatus = ("survivor" | "dismissed" | "needs-human-review")
/**
* The `fallow security blind-spots --format json` schema version.
*/
export type SecurityBlindSpotsSchemaVersion = "1"
/**
* Schema projection for the combined envelope's exact version.
*/
export type CombinedSchemaVersion = 13
/**
* Schema projection for the feature-flags envelope's exact version.
*/
export type FeatureFlagsSchemaVersion = 8
/**
* Feature flag kind values emitted in JSON.
*/
export type FeatureFlagKind = ("environment_variable" | "sdk_call" | "config_object")
/**
* Feature flag confidence values emitted in JSON.
*/
export type FeatureFlagConfidence = ("high" | "medium" | "low")
/**
* Feature flag action discriminants.
*/
export type FeatureFlagActionType = ("investigate-flag" | "suppress-line")
/**
* How the report measures the age of a flag.
*/
export type FlagAgeMode = ("blame" | "pickaxe" | "off")
/**
* How a retirement row's flag was detected.
*/
export type RetirementFlagKind = ("environment_variable" | "sdk_call" | "config_object" | "constant" | "vendor_export")
/**
* What a site does with the flag.
*/
export type FlagSiteRole = ("read" | "definition")
/**
* Why a flag is a retirement candidate.
*/
export type RetirementReason = ("single-read-site" | "test-only" | "literal-constant" | "identical-branches" | "empty-branch" | "guards-dead-code" | "defined-never-read" | "fully-rolled-out" | "archived-in-vendor" | "missing-in-vendor" | "vendor-only")
/**
* Action discriminants for a retirement row.
*/
export type RetirementActionType = "review-retirement"
/**
* State of a flag in a `--flag-state` vendor export.
*/
export type VendorFlagState = ("on" | "off" | "rolled_out" | "archived" | "experiment")
/**
* Independently-versioned wire-version newtype for the brief envelope.
* Serializes as the integer `REVIEW_BRIEF_SCHEMA_VERSION`.
*/
export type ReviewBriefSchemaVersion = 12
/**
* The exactly-three shippable decision categories (the SOLID-3). No cut category
* (abstraction / deletion / convention / irreversibility) is representable: this
* enum is the structural guarantee that confirmed-noise categories never ship.
*/
export type DecisionCategory = ("coupling-boundary" | "public-api-contract" | "dependency")
/**
* Coarse risk classification for a changeset, a pure function of the change
* size (file count plus, once threaded, net lines).
*/
export type RiskClass = ("low" | "medium" | "high")
/**
* Suggested reviewer effort, a pure function of [`RiskClass`].
*/
export type ReviewEffort = ("glance" | "review" | "deep_dive")
/**
* The focus label for a review unit. `Skip` is the SAFE explicit-skip label and
* is runtime-backed ONLY: it is producible solely on the paid runtime path
* and solely for a unit runtime-proves cold with zero risk signals. Free mode
* (no runtime evidence) emits only `ReviewHere` / `NotPrioritized`, never `Skip`
* (the build-focus-map labeller cannot reach the `Skip` arm without runtime
* input), so the free-tier "rank but never skip" stance holds by construction.
*/
export type FocusLabel = ("review-here" | "not-prioritized" | "skip")
/**
* A per-unit confidence flag. The EXACT panel-decided strings: a dynamically-
* wired or re-export-heavy unit carries one so its static-reachability signal is
* not trusted as complete (the anti-silent-de-prioritization guard). The flag
* NEVER lowers the score; it is advisory provenance.
*/
export type ConfidenceFlag = ("dynamic-dispatch" | "re-export-indirection")
/**
* The category of a single weakening signal.
*/
export type WeakeningKind = ("test-weakened" | "threshold-lowered" | "suppression-added" | "security-check-removed")
/**
* Where a cognitive-complexity improvement came from.
*/
export type CognitiveAttribution = ("nesting-reset" | "fewer-branch-points" | "mixed")
/**
* Independently-versioned wire-version newtype. Serializes as the integer
* [`DECISION_SURFACE_SCHEMA_VERSION`].
*/
export type DecisionSurfaceSchemaVersion = number
/**
* The discriminated action kinds a decision can carry.
*/
export type DecisionActionType = ("ask-expert" | "suppress")
/**
* Whether a changed source unit has a test file importing it, and whether that
* test moved with the change. A direct-importer fact from the graph, not a
* coverage claim: `untouched` says a test exists and was not edited, which is
* the verification question the reviewer asks the author, never an answer.
*/
export type TestAdjacency = ("none" | "untouched" | "changed")
/**
* The `fallow suppressions --format json` schema version. Independently
* versioned from the main contract, mirroring `SecuritySchemaVersion`.
*/
export type SuppressionInventorySchemaVersion = "1"
/**
* Suppression marker scope.
*/
export type SuppressionInventoryLevel = ("file" | "line")
/**
* How a suppression in the inventory was authored.
*/
export type SuppressionInventoryOrigin = "comment"
/**
* Schema projection for the exact doctor envelope version.
*/
export type DoctorSchemaVersion = 2
/**
* Schema projection for `.` as the privacy-safe diagnosed project root.
*/
export type DoctorProjectRoot = "."
/**
* Aggregate readiness outcome.
*/
export type DoctorStatus = ("pass" | "warn" | "fail")
/**
* Stable identifier for a doctor check. Declaration order is output order.
*/
export type DoctorCheckId = ("root" | "config" | "workspaces" | "plugins" | "type-aware" | "dependencies" | "cache" | "graph-cache")
/**
* Stable category for a doctor check.
*/
export type DoctorCheckCategory = ("project" | "configuration" | "workspace" | "plugin" | "companion" | "cache")
/**
* Per-check readiness outcome.
*/
export type DoctorCheckStatus = ("pass" | "warn" | "fail" | "skipped")
/**
* Schema projection for the type-aware status envelope's exact version.
*/
export type TypeAwareStatusSchemaVersion = 8
/**
* Version singleton for raw similar-code output.
*/
export type SimilarCodeSchemaVersion = "1"
/**
* Provider families admitted by the version 1 public contract.
*/
export type SimilarCodeProvider = "official-local-companion"
/**
* Stable, coarse interpretation of a candidate score.
*/
export type SimilarCodeSimilarityBand = ("moderate" | "high" | "very-high")
/**
* Verification state of a raw semantic candidate.
*/
export type SimilarCodeVerificationStatus = "unverified"
/**
* Explicit availability state for one optional enrichment source.
*/
export type SimilarCodeEnrichmentState = ("available" | "unavailable" | "not-requested")
/**
* Read-only actions supported by the candidate workflow.
*/
export type SimilarCodeActionType = ("inspect" | "review")
/**
* Overall trustworthiness of an emitted result set.
*/
export type SimilarCodeCompletionStatus = ("complete" | "partial")
/**
* Bounded phase names in the similar-code pipeline.
*/
export type SimilarCodePhase = ("discovery" | "extraction" | "cache" | "embedding" | "validation" | "comparison" | "enrichment")
/**
* Completion state for one generation phase.
*/
export type SimilarCodePhaseStatus = ("complete" | "partial" | "skipped" | "timed-out")
/**
* Stable reasons why admitted work was skipped or truncated.
*/
export type SimilarCodeSkipReason = ("below-minimum-lines" | "unsupported-function" | "generated-source" | "function-too-large" | "input-limit" | "source-bytes-limit" | "vector-memory-limit" | "comparison-limit" | "candidate-limit" | "neighbor-limit" | "timeout" | "provider-failure" | "token-truncation" | "enrichment-unavailable")
/**
* Vector cache outcome for a run.
*/
export type SimilarCodeCacheStatus = ("disabled" | "hit" | "miss" | "mixed")
/**
* Non-severity diagnostic domain for similar-code generation.
*/
export type SimilarCodeDiagnosticDomain = ("workspace" | "extraction" | "provider" | "cache" | "enrichment" | "review")
/**
* Version singleton for a similar-code inspect packet.
*/
export type SimilarCodeInspectSchemaVersion = "1"
/**
* Conservative syntactic side-effect hint for an inspected function.
*/
export type SimilarCodeSideEffectHint = ("pure-looking" | "may-have-side-effects" | "unknown")
/**
* Version singleton for reviewed similar-code output.
*/
export type SimilarCodeReviewSchemaVersion = "1"
/**
* Domain outcome assigned by review, never by candidate generation.
*/
export type SimilarCodeDomainOutcome = ("same-responsibility" | "related-but-distinct" | "intentional-duplication" | "unrelated" | "needs-human-review")
/**
* How review matched an external verdict to the current candidate.
*/
export type SimilarCodeVerdictMatch = ("candidate-id" | "review-key" | "unverified" | "ambiguous-review-key")
/**
* 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: AuditSchemaVersion
version: ToolVersion
command: AuditCommand
verdict: AuditVerdict
/**
* Number of changed files in the audit scope.
*/
changed_files_count: number
/**
* Git ref the change was diffed against.
*/
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)
/**
* Commit SHA of the audited head tree, when resolvable.
*/
head_sha?: (string | null)
elapsed_ms: ElapsedMs
/**
* True when base-snapshot analysis was skipped, so new-vs-inherited
* attribution could not run.
*/
base_snapshot_skipped?: (boolean | null)
summary: AuditSummary
attribution: AuditAttribution
/**
* The verdict of every gate this run evaluated, keyed by name. The CLI
* always emits it, with the command's default exit rule in it also when
* no flag armed a gate, so a CI integration reads the verdict instead of
* guessing from a process status it usually cannot see. A gate fails the
* build when `status` is `fail` AND `enforced` is true. The typed
* programmatic API runs no CLI gate and leaves it absent. See
* [`crate::GateOutcomes`].
*/
gate_outcomes?: (GateOutcomes | null)
/**
* `_meta` block with metric / rule definitions, when `--explain` was
* passed.
*/
_meta?: (Meta | null)
/**
* Dead-code findings scoped to the audit changeset.
*/
dead_code?: (CheckOutput | null)
/**
* Duplication findings scoped to the audit changeset.
*/
duplication?: (DupesReportPayload | null)
/**
* Complexity findings scoped to the audit changeset.
*/
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 {
/**
* Total dead-code issues reported for the changed files.
*/
dead_code_issues: number
/**
* Whether any reported dead-code issue has error severity.
*/
dead_code_has_errors: boolean
/**
* Total complexity findings reported for the changed files.
*/
complexity_findings: number
/**
* Highest cyclomatic complexity among the findings; `None` when there
* are no complexity findings.
*/
max_cyclomatic?: (number | null)
/**
* Clone groups touching the changed files.
*/
duplication_clone_groups: number
}
/**
* New-vs-inherited issue counts for audit.
*/
export interface AuditAttribution {
gate: AuditGate
/**
* Dead-code findings absent from the base snapshot.
*/
dead_code_introduced: number
/**
* Dead-code findings already present in the base snapshot.
*/
dead_code_inherited: number
/**
* Complexity findings absent from the base snapshot.
*/
complexity_introduced: number
/**
* Complexity findings already present in the base snapshot.
*/
complexity_inherited: number
/**
* Clone groups absent from the base snapshot.
*/
duplication_introduced: number
/**
* Clone groups already present in the base snapshot.
*/
duplication_inherited: number
styling_introduced: number
styling_inherited: number
duplication_demoted: number
}
/**
* The verdict of every gate a run evaluated, keyed by name.
*
* A gate is armed by a flag or by config. The default exit rule of a command
* is always in the object, also when no flag armed a gate:
* `error-severity-findings` on `dead-code`, `check` and the combined run
* (with `health-findings` when the combined run analyzed health),
* `health-findings` on `health`, `security-advisory` on `security` and
* `audit-verdict` on `audit`. A reader of the JSON sees a failing run without
* the exit code. `dupes` has no default exit rule, so a `dupes` run that armed
* no gate carries no object and always exits 0.
*
* An empty object is never emitted. The typed programmatic API runs no CLI
* gate and leaves the object absent.
*
* The names this build can emit are `error-severity-findings`, `regression`,
* `stale-baseline`, `baseline-growth`, `duplication-threshold`,
* `duplication-findings`, `health-min-score`,
* `health-min-severity`, `health-findings`, `health-coverage-gaps`,
* `health-runtime-coverage`, `security`, `security-advisory`, `audit-verdict`,
* `type-aware-require` and `parse-error`. The set is OPEN: a name a consumer does not
* recognise means "some gate", not an error.
*/
export interface GateOutcomes {
[k: string]: GateOutcome
}
/**
* One gate's verdict on one run.
*
* `status` and `enforced` answer different questions and legitimately
* disagree. `status` is what the rule concluded; `enforced` is whether a
* `fail` from this gate would make the run exit non-zero. A
* `health --report-only` run is an explicit request never to fail, so a
* failing gate there reports `status: fail` with `enforced: false`, and a
* stale-baseline verdict published without `--fail-on-stale-baseline` reports
* the same pair.
*
* **A gate fails the build when `status` is `fail` AND `enforced` is true.**
* Neither member decides it alone: `enforced` is true on every armed gate,
* including the ones that passed, so gating on it by itself fails every run
* that armed anything. Read `status` on its own to decide what to say, and
* remember that `warn` and `skipped` are neither a pass nor a failure.
*/
export interface GateOutcome {
status: GateStatus
/**
* True when a `fail` from this gate makes the run exit non-zero. False
* when the verdict is published for information only: the gate was never
* armed, the run was told never to fail, or the combined machine formats
* exit 0 for the gate (bare `fallow` without `--fail-on-issues`).
*/
enforced: boolean
/**
* The measured value the gate compared, when there is one: the duplication
* percentage, the health score, the number of findings at or above the
* severity floor, the number of `error` findings of `health-findings` and
* of `error-severity-findings`, or
* the number of files in `files`. Whole numbers are
* carried as JSON numbers, so a count of three reads as `3.0`. Absent for
* gates that compare no number.
*/
observed?: (number | null)
/**
* The configured limit `observed` was compared against, when there is one.
* Absent for gates that compare no number.
*/
threshold?: (number | null)
/**
* How the limit was spelled, for a gate whose `threshold` number does not
* carry its own unit. `health-min-severity` sets it to the severity floor
* (`moderate`, `high` or `critical`); `health-findings` and
* `error-severity-findings` set it to `error`, the rule severity they
* count; `regression` sets it to the
* tolerance as the user wrote it (`"50%"` or `"5"`), because `threshold`
* there is the allowance in issues and the percentage would otherwise be
* unrecoverable on the grouped envelope, which carries no `regression`
* object. Absent for gates whose numbers speak for themselves.
*/
threshold_label?: (string | null)
/**
* The files the gate judged, for a gate that judges files rather than a
* number. Only `parse-error` sets it: one item per file that did not
* parse cleanly, sorted by path. Absent when the list is empty.
*/
files?: GateFile[]
}
/**
* One file a file-judging gate names, with the reason the gate counted it.
*
* Today only `parse-error` emits it. The item carries the same facts as the
* `source-parse-degraded` entry in `workspace_diagnostics[]` for that file,
* so a consumer can act on the gate without a join.
*/
export interface GateFile {
/**
* The file path, relative to the project root, with `/` separators.
*/
path: string
/**
* The number of parser errors for the file.
*/
error_count: number
/**
* True when the parser stopped in the file instead of recovering, so the
* analysis saw only the part before the error.
*/
panicked: boolean
}
/**
* 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)
/**
* Provenance for the opt-in TypeScript semantic analysis pass.
*/
type_aware?: (TypeAwareMeta | 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)
}
/**
* Bounded provenance emitted when the opt-in type-aware pass runs.
*/
export interface TypeAwareMeta {
/**
* Compatibility identity used by audit, baselines, snapshots, and stores.
*/
identity?: (SemanticAnalysisIdentity | null)
/**
* Effective CLI or repository policy for incomplete semantic evidence.
*/
required_completeness?: (SemanticCompletenessRequirement | null)
/**
* Compact status for every requested semantic query.
*/
queries?: SemanticQuerySummary[]
/**
* Bounded decision and evidence for each semantic dead-code candidate.
*/
candidate_decisions?: SemanticCandidateDecision[]
/**
* Checker-backed trace evidence requested by focused symbol queries.
*/
symbol_traces?: SemanticSymbolTrace[]
/**
* Package-public surface and confirmed private type leaks.
*/
api_surface?: (ApiSurfaceResult | null)
/**
* Exact-symbol blast radius and targeted-test recommendations.
*/
symbol_impacts?: SemanticSymbolImpact[]
/**
* Advisory project-local public-signature coupling.
*/
type_coupling?: (TypeCouplingReport | null)
/**
* Whether the semantic companion executed at least one query.
*/
executed: boolean
/**
* Version of Fallow's backend-neutral sidecar protocol.
*/
protocol_version: number
/**
* Version of the sidecar package that executed the query.
*/
sidecar_version?: (string | null)
/**
* Semantic backend capability identifier.
*/
backend: string
/**
* Backend compiler or engine version that executed the query.
*/
backend_version?: (string | null)
/**
* TypeScript project configs selected for candidate files.
*/
selected_tsconfigs: string[]
/**
* Number of candidate findings sent to the sidecar.
*/
candidate_count: number
/**
* Number of candidates confirmed as used and removed.
*/
confirmed_used_count: number
/**
* Number of candidates preserved because they implement or override a contract.
*/
contract_preserved_count: number
/**
* Number of candidates with complete, closed-world no-static-reference evidence.
*/
no_static_references_count: number
/**
* Number of retained class members eligible for a guarded type-aware fix.
*/
fix_eligible_count: number
/**
* Number of candidates retained because semantic use was unresolved.
*/
unresolved_count: number
/**
* Number of candidates retained because semantic analysis abstained.
*/
abstained_count: number
abstention_reasons: TypeAwareAbstentionCounts
/**
* Per-project semantic refinement status and evidence.
*/
projects: TypeAwareProjectMeta[]
/**
* Number of bounded warnings returned by the sidecar.
*/
warning_count: number
/**
* Bounded semantic warnings. Findings mentioned here were retained.
*/
warnings: string[]
/**
* Semantic pass duration as reported by the sidecar.
*/
elapsed_ms: number
phase_timings_ms: TypeAwarePhaseTimings
}
/**
* Compatibility identity for comparing two analysis results.
*/
export interface SemanticAnalysisIdentity {
mode: SemanticAnalysisMode
/**
* Version of the semantic result schema, independent of tool versions.
*/
semantic_schema_version: number
/**
* Sorted capability set requested for the analysis.
*/
capabilities: SemanticCapability[]
/**
* Hash of normalized project ownership and compiler configuration.
*/
project_config_hash: string
/**
* Backend family, such as `typescript-go`.
*/
backend_family: string
completeness: SemanticCompleteness
}
/**
* Compact per-query status embedded in run metadata.
*/
export interface SemanticQuerySummary {
/**
* Stable query identifier within this analysis run.
*/
query_id: number
capability: SemanticCapability
/**
* Operation-specific assertion, never a generic compiler verdict.
*/
assertion: string
status: SemanticCompleteness
/**
* Stable primary gap reason when partial or unavailable.
*/
reason_code?: (SemanticGapReason | null)
/**
* Evidence count before bounding.
*/
total_evidence_count: number
/**
* Whether evidence or payload arrays were truncated.
*/
truncated: boolean
/**
* Counted omissions.
*/
omissions?: SemanticOmission[]
/**
* Plain next actions.
*/
actions?: string[]
}
/**
* Counted omission attached to a partial or unavailable result.
*/
export interface SemanticOmission {
reason_code: SemanticGapReason
/**
* Number of omitted items or relations.
*/
count: number
}
/**
* Bounded, inspectable decision record for one semantic dead-code candidate.
*/
export interface SemanticCandidateDecision {
/**
* Query identifier used to correlate low-level query metadata.
*/
query_id: number
subject: SemanticSymbol
decision: SemanticCandidateDecisionKind
status: SemanticCompleteness
/**
* Every selected TypeScript project that owns the declaration.
*/
owning_projects: string[]
/**
* Bounded checker-resolved reference or uncertainty evidence.
*/
evidence?: SemanticReference[]
/**
* Inherited contract evidence, when present.
*/
contract?: (SemanticContractEvidence | null)
/**
* Framework-owned contract evidence, distinct from TypeScript contracts.
*/
framework_contract?: (SemanticFrameworkContractEvidence | null)
/**
* Whether this exact decision may enable a guarded class-member fix.
*/
closed_world_eligible: boolean
/**
* Exact declaration guard required before a source edit.
*/
edit_guard?: (SemanticEditGuard | null)
/**
* Primary reason when the candidate remains unresolved or abstained.
*/
reason_code?: (SemanticGapReason | null)
/**
* Concise explanation suitable for dry-run and agent output.
*/
explanation: string
/**
* Plain next actions.
*/
actions?: string[]
/**
* Evidence count before bounding.
*/
total_evidence_count: number
/**
* Whether evidence was truncated.
*/
truncated: boolean
/**
* Counted omissions.
*/
omissions?: SemanticOmission[]
}
/**
* Stable identity for a declaration sent to or returned by the backend.
*/
export interface SemanticSymbol {
/**
* Project-root-relative declaration path.
*/
path: string
namespace: SemanticNamespace
/**
* Stable declaration kind, such as `function` or `class-method`.
*/
declaration_kind: string
/**
* Name exposed to consumers.
*/
exported_name: string
/**
* Local declaration name.
*/
local_name: string
/**
* Optional owning class or namespace.
*/
owner?: (string | null)
/**
* One-based declaration line.
*/
line: number
/**
* Zero-based UTF-8 byte column.
*/
col: number
}
/**
* Located semantic reference evidence.
*/
export interface SemanticReference {
/**
* Project-root-relative reference path.
*/
path: string
/**
* One-based source line.
*/
line: number
/**
* Zero-based UTF-8 byte column.
*/
col: number
/**
* Reference role, such as `read`, `type`, `alias`, or `re-export`.
*/
role: string
namespace: SemanticNamespace
/**
* Alias and re-export hops between the reference and declaration.
*/
via?: SemanticAliasHop[]
}
/**
* One alias or re-export hop in semantic provenance.
*/
export interface SemanticAliasHop {
/**
* Project-root-relative hop path.
*/
path: string
/**
* Name before this hop.
*/
from_name: string
/**
* Name exposed after this hop.
*/
to_name: string
/**
* Relation, such as `import-alias` or `re-export`.
*/
relation: string
}
/**
* Exact declaration evidence for an inherited or implemented class-member relation.
*/
export interface SemanticContractEvidence {
relation: SemanticContractRelation
declaration: SemanticSymbol
/**
* Whether the source contract marks this member optional.
*/
optional: boolean
}
/**
* Checker-validated evidence that a framework contract preserves a member.
*/
export interface SemanticFrameworkContractEvidence {
/**
* Fallow plugin that supplied the contract.
*/
framework: string
/**
* Exact package that owns the heritage declaration.
*/
package: string
relation: SemanticFrameworkRelation
declaration: SemanticSymbol
}
/**
* Exact source span and content hash used to guard semantic source edits.
*/
export interface SemanticEditGuard {
/**
* Zero-based UTF-8 byte offset where the declaration starts.
*/
start: number
/**
* Exclusive zero-based UTF-8 byte offset where the declaration ends.
*/
end: number
/**
* Lowercase SHA-256 digest of the exact declaration text.
*/
declaration_sha256: string
}
/**
* Typed semantic trace attached to an existing syntactic trace.
*/
export interface SemanticSymbolTrace {
target: SemanticSymbol
identity: SemanticAnalysisIdentity
/**
* TypeScript project selected for this symbol.
*/
selected_project: string
/**
* Concrete assertion, such as `references-found`.
*/
assertion: string
status: SemanticCompleteness
/**
* Bounded reference evidence.
*/
references: SemanticReference[]
/**
* Count before evidence bounding.
*/
total_reference_count: number
/**
* Exact reference locations found by the TypeScript checker.
*/
checker_evidence_count: number
/**
* Alias and re-export hops derived from the semantic graph.
*/
graph_evidence_count: number
/**
* Whether reference evidence was truncated.
*/
truncated: boolean
/**
* Counted omissions.
*/
omissions?: SemanticOmission[]
/**
* Plain next actions for a user or automation consumer.
*/
actions?: string[]
}
/**
* Package API surface result shared by inspect and private-leak analysis.
*/
export interface ApiSurfaceResult {
/**
* Concrete assertion, such as `leak-confirmed`.
*/
assertion: string
status: SemanticCompleteness
/**
* Public API entries.
*/
entries: ApiSurfaceEntry[]
/**
* Confirmed private type leaks.
*/
private_type_leaks: SemanticPrivateTypeLeak[]
/**
* Counted omissions.
*/
omissions?: SemanticOmission[]
/**
* Plain next actions.
*/
actions?: string[]
}
/**
* One package-public API entry described by the semantic backend.
*/
export interface ApiSurfaceEntry {
exposed: SemanticSymbol
origin: SemanticSymbol
/**
* Stable normalized signature fingerprint.
*/
signature_fingerprint: string
/**
* Project-local types referenced by the signature.
*/
referenced_types: PublicTypeReference[]
}
/**
* One project-local type referenced by a public signature.
*/
export interface PublicTypeReference {
declaration: SemanticSymbol
/**
* Signature relation, such as return type or generic constraint.
*/
relation: string
}
/**
* Exact semantic evidence for a private type leak.
*/
export interface SemanticPrivateTypeLeak {
exposed: SemanticSymbol
private_declaration: SemanticSymbol
/**
* Signature relation through which the type is exposed.
*/
relation: string
/**
* Stable TypeScript diagnostic code used as supporting evidence.
*/
diagnostic_code?: (number | null)
}
/**
* Exact-symbol impact and targeted-test recommendation.
*/
export interface SemanticSymbolImpact {
target: SemanticSymbol
identity: SemanticAnalysisIdentity
/**
* TypeScript project selected for this symbol.
*/
selected_project: string
/**
* Concrete assertion, such as `consumers-found`.
*/
assertion: string
status: SemanticCompleteness
/**
* Files that reference the exact symbol directly.
*/
direct_consumers: SemanticImpactPath[]
/**
* Direct consumer count before evidence bounding.
*/
total_direct_consumer_count: number
/**
* Transitively affected production files.
*/
affected_files: SemanticImpactPath[]
/**
* Transitive affected-file count before evidence bounding.
*/
total_affected_file_count: number
/**
* Directly relevant test entry points.
*/
targeted_tests: SemanticImpactPath[]
/**
* Targeted-test count before evidence bounding.
*/
total_targeted_test_count: number
confidence: SemanticImpactConfidence
/**
* Counted omissions, including dynamic behavior.
*/
omissions?: SemanticOmission[]
/**
* Plain next actions.
*/
actions?: string[]
}
/**
* One production file or test reached by exact-symbol impact analysis.
*/
export interface SemanticImpactPath {
/**
* Project-root-relative affected path.
*/
path: string
/**
* Relation to the target, such as `direct-value-consumer`.
*/
relation: string
/**
* Shortest graph distance from the target.
*/
distance: number
/**
* Located provenance path.
*/
via?: string[]
}
/**
* Advisory project-local public-signature coupling report.
*/
export interface TypeCouplingReport {
identity: SemanticAnalysisIdentity
/**
* Concrete assertion, such as `coupling-found`.
*/
assertion: string
status: SemanticCompleteness
/**
* Project summary. Absent when analysis is unavailable, never a fake zero.
*/
summary?: (TypeCouplingSummary | null)
/**
* Per-file coupling details.
*/
files: TypeCouplingFile[]
/**
* Highest-degree files contributing to project coupling.
*/
top_contributors?: TypeCouplingFile[]
/**
* Located project-local type cycles.
*/
cycles?: TypeCouplingCycle[]
/**
* Counted omissions.
*/
omissions?: SemanticOmission[]
/**
* Plain next actions.
*/
actions?: string[]
}
/**
* Project summary for advisory type coupling.
*/
export interface TypeCouplingSummary {
/**
* Measurement boundary, currently project-local public signatures.
*/
scope: string
/**
* Edge direction, currently directed.
*/
direction: string
/**
* Distinct project files in the selected TypeScript projects.
*/
project_size: number
/**
* Distinct project files included in the denominator.
*/
files_analyzed: number
/**
* Files participating in at least one project-local type edge.
*/
distinct_coupled_files: number
/**
* Project-local public-signature edge count before evidence bounding.
*/
edge_count: number
/**
* Percentage of analyzed files participating in a type edge.
*/
coupled_file_pct: number
/**
* Median distinct-file type connections.
*/
p50_distinct_connections: number
/**
* P90 distinct-file type connections.
*/
p90_distinct_connections: number
/**
* P95 incoming distinct-file type coupling.
*/
p95_public_types_used_by: number
/**
* P95 outgoing distinct-file type coupling.
*/
p95_public_api_depends_on: number
/**
* Percentage of files above the adaptive high-coupling threshold.
*/
high_coupling_pct: number
/**
* Share of edge endpoints represented by the top contributors.
*/
concentration: number
/**
* Number of project-local public-signature cycles.
*/
cycle_count: number
}
/**
* Per-file project-local public-signature coupling.
*/
export interface TypeCouplingFile {
/**
* Project-root-relative file path.
*/
path: string
/**
* Distinct files this file's public API depends on.
*/
public_api_depends_on: number
/**
* Located project files this file's public API depends on.
*/
public_api_depends_on_files?: string[]
/**
* Distinct files whose public types use this file.
*/
public_types_used_by: number
/**
* Located project files whose public types use this file.
*/
public_types_used_by_files?: string[]
/**
* Located public-signature edges.
*/
edges: TypeCouplingEdge[]
}
/**
* One project-local public-signature type edge.
*/
export interface TypeCouplingEdge {
source: SemanticSymbol
target: SemanticSymbol
/**
* Signature relation.
*/
relation: string
evidence: SemanticSourceLocation
/**
* Scope, such as `module-export` or `package-public`.
*/
scope: string
}
/**
* One project-root-relative source location used as semantic evidence.
*/
export interface SemanticSourceLocation {
/**
* Project-root-relative source path.
*/
path: string
/**
* One-based source line.
*/
line: number
/**
* Zero-based UTF-8 byte column.
*/
col: number
}
/**
* One project-local cycle through public-signature type dependencies.
*/
export interface TypeCouplingCycle {
/**
* Ordered project-root-relative files, ending at the start file.
*/
files: string[]
}
/**
* Closed abstention reason counts for stable machine consumption.
*/
export interface TypeAwareAbstentionCounts {
/**
* Candidates not contained by a selected TypeScript project.
*/
no_project: number
/**
* Candidates contained by more than one explicit TypeScript project.
*/
ambiguous_project: number
/**
* Candidates retained because structural diagnostics block scanning.
*/
blocking_diagnostics: number
/**
* Candidates retained because the raw TypeScript-Go host cannot expose
* named exports from Svelte virtual modules.
*/
svelte_virtual_module_exports: number
/**
* Candidates whose exact declaration identity could not be resolved.
*/
unknown_symbol: number
/**
* Candidates using declaration syntax unsupported by the semantic backend.
*/
unsupported_syntax: number
/**
* Candidates retained because the bounded semantic request reached capacity.
*/
capacity: number
}
/**
* Bounded provenance for one TypeScript project handled by the sidecar.
*/
export interface TypeAwareProjectMeta {
/**
* Project config relative to the analysis root, or `<inferred>`.
*/
config: string
source: TypeAwareProjectSource
status: TypeAwareProjectStatus
/**
* Candidates assigned to this project.
*/
candidate_count: number
/**
* Candidates confirmed as used and removed.
*/
confirmed_used_count: number
/**
* Candidates retained because they implement or override a contract.
*/
contract_preserved_count: number
/**
* Candidates with complete no-static-reference evidence.
*/
no_static_references_count: number
/**
* Candidates eligible for a guarded class-member fix.
*/
fix_eligible_count: number
/**
* Candidates whose exact semantic outcome remained unresolved.
*/
unresolved_count: number
/**
* Candidates retained without scanning because the project was unsafe.
*/
abstained_count: number
/**
* Config, program, syntactic, and bind diagnostics that block scanning.
*/
blocking_diagnostic_count: number
/**
* Source files loaded into this TypeScript program.
*/
source_file_count: number
/**
* Whether this Program served more than one semantic query in the batch.
*/
program_reused?: (boolean | null)
/**
* Whether this Program served more than one query in the current batch.
*/
program_shared_across_queries?: (boolean | null)
/**
* Whether the root-bound semantic session reused the prior snapshot.
*/
program_reused_from_previous_snapshot?: (boolean | null)
/**
* Monotonic revision within the root-bound semantic session.
*/
snapshot_revision?: (number | null)
/**
* Full, incremental, or no invalidation before this query.
*/
invalidation_kind?: (TypeAwareInvalidationKind | null)
/**
* Stable project-level gap reason.
*/
reason_code?: (SemanticGapReason | null)
abstain_reason?: TypeAwareAbstentionReason
}
/**
* Semantic sidecar timings, separated from Fallow's syntactic pipeline.
*/
export interface TypeAwarePhaseTimings {
/**
* TypeScript API construction and project snapshot selection.
*/
project_setup: number
/**
* TypeScript diagnostics collected before any candidate refinement.
*/
diagnostics: number
/**
* Batched symbol lookup and exact declaration matching.
*/
symbol_scan: number
}
/**
* 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: CheckSchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
/**
* Total findings across all issue arrays; excludes `next_steps`.
*/
total_issues: number
/**
* Entry-point totals per source, when the analysis recorded them.
*/
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[]
/**
* Exports marked `@deprecated` that still have at least one consumer in
* a reachable file. Wrapped in [`DeprecatedExportInUseFinding`]. Opt-in: the
* `deprecated-exports-in-use` rule defaults to `off`.
*/
deprecated_exports_in_use?: DeprecatedExportInUseFinding[]
/**
* 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[]
/**
* devDependencies imported by production (non-test, non-config) source code
* via a runtime/value import; they should be promoted to dependencies.
* The promote-side mirror of [`TestOnlyDependencyFinding`]. Wrapped in
* [`DevDependencyInProductionFinding`].
*/
dev_dependencies_in_production?: DevDependencyInProductionFinding[]
/**
* 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[]
/**
* Dependency cycles between workspace packages, built from resolved
* cross-package imports. Wrapped in [`PackageCycleFinding`] so each
* entry carries a typed `actions` array natively.
*/
package_cycles?: PackageCycleFinding[]
/**
* 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, imports, and catalogue-derived effects 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 package manager catalog sections not referenced by any
* workspace package via the catalog: protocol. Supports
* `pnpm-workspace.yaml` catalogs and Bun root `package.json` catalogs.
* Wrapped in [`UnusedCatalogEntryFinding`] so each entry carries a typed
* `actions` array natively, with per-instance `auto_fixable` derived
* from `hardcoded_consumers` and the catalog source file.
*/
unused_catalog_entries?: UnusedCatalogEntryFinding[]
/**
* Named groups under package manager catalogs sections 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. The package
* manager 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, package.json's
* pnpm.overrides block, npm or Bun's top-level overrides object, or Bun's
* top-level resolutions object,
* whose target package is not declared by any workspace package and is
* not present in pnpm-lock.yaml, package-lock.json, npm-shrinkwrap.json,
* or bun.lock. Default severity is warn because projects without a
* readable lockfile fall back to manifest-only checks; the hint field
* flags those conservative cases. When the only lockfile is bun's binary
* bun.lockb, resolution cannot be read and the check emits nothing.
* Wrapped in [`UnusedDependencyOverrideFinding`].
*/
unused_dependency_overrides?: UnusedDependencyOverrideFinding[]
/**
* Package-manager override or resolution entries whose key or value does
* not parse in the declaration source's grammar (empty key, empty value,
* malformed selector, unbalanced parent matcher). The package manager may
* reject or ignore these at install time. 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`, Svelte 5 `$props()`, and React props
* referenced nowhere in their own component. Wrapped in
* [`UnusedComponentPropFinding`] so each entry carries a typed `actions`
* array natively. Default severity is `warn`.
*/
unused_component_props?: UnusedComponentPropFinding[]
/**
* Used optional component inputs absent from inspected reachable callers. Off by default.
*/
absent_component_props?: AbsentComponentPropFinding[]
/**
* 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[]
/**
* Angular `@Input()` / signal `input()` / `model()` inputs read nowhere in
* their own component (neither the template nor the class body). Wrapped in
* [`UnusedComponentInputFinding`] so each entry carries a typed `actions`
* array natively. Default severity is `warn`.
*/
unused_component_inputs?: UnusedComponentInputFinding[]
/**
* Angular `@Output()` / signal `output()` outputs emitted nowhere in their
* own component (no `this.<output>.emit(...)`). Wrapped in
* [`UnusedComponentOutputFinding`] so each entry carries a typed `actions`
* array natively. Default severity is `warn`.
*/
unused_component_outputs?: UnusedComponentOutputFinding[]
/**
* Svelte components dispatching a custom event via `createEventDispatcher()`
* whose event name is listened to nowhere project-wide (cross-file
* dead-output direction). Wrapped in [`UnusedSvelteEventFinding`] so each
* entry carries a typed `actions` array natively. Default severity is
* `warn`.
*/
unused_svelte_events?: UnusedSvelteEventFinding[]
/**
* 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[]
/**
* Count deltas against the matched baseline, in baseline runs.
*/
baseline_deltas?: (BaselineDeltas | null)
/**
* Which baseline snapshot was matched, in baseline runs.
*/
baseline?: (BaselineMatch | null)
/**
* This run's view of the loaded baseline, present only in baseline runs.
* Carries the staleness counts, the advisory verdict and `gate_trips`, the
* same boolean `--fail-on-stale-baseline` exits on, so a CI integration
* reads one field instead of restating the rule. Read `change_scoped`
* before dividing `matched_entries` by `baseline_entries`: a narrowed run
* can report `matched_entries: 0` on a healthy baseline.
*/
baseline_staleness?: (BaselineStaleness | null)
/**
* The answer to `--finding-id`, present only when the run received one
* or more `--finding-id` values. The report then holds only the
* requested findings. Read `missing` as resolved only when `conclusive`
* is true; a scope, a baseline or a filter can hide a finding that still
* exists. See [`crate::FindingIdQuery`].
*/
finding_id_query?: (FindingIdQuery | null)
/**
* Regression verdict against the baseline, in `--fail-on-regression` runs.
*/
regression?: (RegressionResult | null)
/**
* The verdict of every gate this run evaluated, keyed by name. The CLI
* always emits it, with the command's default exit rule in it also when
* no flag armed a gate, so a CI integration reads the verdict instead of
* guessing from a process status it usually cannot see. A gate fails the
* build when `status` is `fail` AND `enforced` is true. The typed
* programmatic API runs no CLI gate and leaves it absent. See
* [`crate::GateOutcomes`].
*/
gate_outcomes?: (GateOutcomes | null)
/**
* Every narrowing or shaping request this run RECEIVED, keyed by name,
* absent when it was asked for nothing. An entry whose `status` is not
* `applied` means the run could not do what it was asked and reported
* something WIDER instead, so what follows is a valid report of a scope
* nobody requested. Honoured requests are published too, with
* `status: "applied"`, so an absent object means "nothing was asked for",
* never "nothing failed". See [`crate::RequestOutcomes`].
*/
request_outcomes?: (RequestOutcomes | null)
/**
* Applied Git refs for exact workspace packages. Absent when no package
* baselines were selected, including runs with a global changed-since ref.
*/
package_baselines?: PackageBaselineStatus[]
/**
* `_meta` block with docs and rule definitions, when `--explain` was
* passed.
*/
_meta?: (Meta | null)
/**
* Non-fatal diagnostics about the project itself, from all three stages
* that record them (issue #473):
*
* - workspace discovery, at config load: `undeclared-workspace`,
* `malformed-package-json`, `glob-matched-no-package-json`,
* `malformed-tsconfig`, `tsconfig-reference-dir-missing`;
* - source discovery, during the file walk: `skipped-large-file`,
* `skipped-minified-file`, `skipped-source-dotdir`,
* `excluded-by-default-ignore`, `source-read-failure`,
* `source-parse-degraded`;
* - dead-code analysis, from the dependency-catalog and override
* detectors: `malformed-pnpm-workspace-yaml`,
* `bun-lockb-override-resolution-skipped`;
* - framework plugins, while they read their own build configs:
* `plugin-config-unreadable`, `plugin-effect-not-modeled`;
* - the dead-code result, for config patterns that matched nothing:
* `ignore-dependencies-glob-unmatched`,
* `ignore-findings-pattern-unmatched`.
*
* Analysis-stage and plugin-stage kinds therefore reach only the envelopes
* whose run includes a dead-code analyze pass, never a standalone
* `fallow dupes --format json`. `path` is project-root-relative with
* forward slashes; the array is omitted when empty. 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.
*
* A diagnostic here is advisory and never withholds a finding. Where an
* entry reports a source file this run never fully analyzed
* (`source-parse-degraded`, `source-read-failure`, `skipped-large-file`,
* `skipped-minified-file`, `skipped-source-dotdir`) it can distort a
* verdict, so the affected `unused_files[]`, `unused_exports[]`, and
* dependency entries additionally carry the caveat themselves in their own
* optional `reachability_caveats[]` array, and a reader who never scrolls
* back up to this list still sees it. `fallow fix` reads the same array
* and withholds the removal while a caveat stands.
*
* `excluded-by-default-ignore` is the one source-discovery kind that
* reports unseen files WITHOUT raising a caveat. It names a built-in
* ignore pattern (`** /dist/**`, `** /build/**`, `** /coverage/**`, or one
* of the four minified-bundle globs) that removed candidate source files
* from the walk, which is designed behavior on generated output rather
* than a degraded run, so it is advisory only and no finding inherits it.
* One entry per pattern, never per file, so the array stays bounded on a
* project of any size. Gitignored trees are pruned before the walk sees
* them and count zero, and `** /node_modules/**` is never reported:
* installed dependencies are not the first-party source the kind is
* about.
*/
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
/**
* Exports marked `@deprecated` that are still referenced.
*/
deprecated_exports_in_use: 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, Svelte, or React props referenced nowhere inside their own component.
*/
unused_component_props?: number
/**
* Optional consumed props omitted by known reachable callers, for manual review.
*/
absent_component_props?: number
/**
* Vue `<script setup>` emits emitted nowhere inside their own SFC.
*/
unused_component_emits?: number
/**
* Angular `@Input()` bindings referenced nowhere inside their own component.
*/
unused_component_inputs?: number
/**
* Angular `@Output()` bindings emitted nowhere inside their own component.
*/
unused_component_outputs?: number
/**
* Svelte components dispatching a custom event via `createEventDispatcher`
* whose name is listened to nowhere in the project.
*/
unused_svelte_events?: 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
/**
* devDependencies imported by production source code with a runtime/value
* import (should be promoted to dependencies).
*/
dev_dependencies_in_production: 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
/**
* Dependency cycles between workspace packages.
*/
package_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, imports, and catalogue-derived effects 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
/**
* Package-manager overrides whose target package is not declared by any
* workspace package and not present in the active readable lockfile.
*/
unused_dependency_overrides: number
/**
* Package-manager overrides 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (string | null)
/**
* 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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
/**
* Advisory caveats on the reachability verdict behind this finding.
* Sorted, deduplicated, and omitted from the wire when empty, so a run
* that analyzed every discovered file is byte-identical. Never gates the
* finding or the `delete-file` action, though `fallow fix` does withhold
* the removal of a caveated finding as low confidence.
*/
reachability_caveats?: ReachabilityCaveat[]
}
/**
* 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` and catalog
* source file, 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.
*
* One flip is RUN-level rather than finding-level: a dead-code finding
* carrying `reachability_caveats` reports `false` here, because a file
* this run never fully read may hold the reference that credits it. Every
* mutation surface honours the same gate, so a plan built from this flag
* never expects a write `fallow fix`, the MCP fix tools, or the LSP quick
* fix will refuse. The action stays in the array at the same position and
* names the reason in [`Self::note`].
*/
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
/**
* Whether the export's leading JSDoc carries `@deprecated`. Absent from
* the wire when false.
*/
deprecated?: boolean
/**
* Plain-text message of the `@deprecated` tag, capped at
* [`DEPRECATED_REASON_MAX_CHARS`] characters. Absent when the export is
* not deprecated or the tag carries no text.
*/
deprecated_reason?: (string | null)
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (string | null)
/**
* Suggested next steps. Always emitted (possibly empty for
* forward-compat).
*/
actions: IssueAction[]
/**
* Type-aware evidence for this exact candidate when requested.
*/
semantic?: (SemanticCandidateDecision | null)
/**
* Set by the audit pass when this finding is introduced relative to
* the merge-base.
*/
introduced?: (AuditIntroduced | null)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
/**
* Advisory caveats on the reachability verdict behind this finding.
* Sorted, deduplicated, and omitted from the wire when empty. Never gates
* the finding or the `remove-export` action, though `fallow fix` does
* withhold the removal of a caveated export as low confidence.
*/
reachability_caveats?: ReachabilityCaveat[]
}
/**
* 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
/**
* Whether the export's leading JSDoc carries `@deprecated`. Absent from
* the wire when false.
*/
deprecated?: boolean
/**
* Plain-text message of the `@deprecated` tag, capped at
* [`DEPRECATED_REASON_MAX_CHARS`] characters. Absent when the export is
* not deprecated or the tag carries no text.
*/
deprecated_reason?: (string | null)
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (string | null)
/**
* Suggested next steps. Always emitted (possibly empty for
* forward-compat).
*/
actions: IssueAction[]
/**
* Type-aware evidence for this exact candidate when requested.
*/
semantic?: (SemanticCandidateDecision | null)
/**
* Set by the audit pass when this finding is introduced relative to
* the merge-base.
*/
introduced?: (AuditIntroduced | null)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
/**
* Advisory caveats on the reachability verdict behind this finding.
* A type export rests on exactly the reachability test an
* `unused_exports[]` entry does, and the LSP offers the same
* remove-the-`export`-keyword quick fix for both, so the two must render
* with the same confidence. Sorted, deduplicated, omitted when empty.
*/
reachability_caveats?: ReachabilityCaveat[]
}
/**
* 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
/**
* Exact checker-backed provenance when type-aware analysis confirmed the
* package-public leak across files or re-exports.
*/
semantic?: (SemanticPrivateTypeLeak | null)
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
}
/**
* Wire-shape envelope for a [`DeprecatedExportInUse`] finding. Carries a
* manual `migrate-deprecated-export` primary action plus a `suppress-line`
* secondary. Never auto-fixable: fallow does not rewrite consumers.
*/
export interface DeprecatedExportInUseFinding {
/**
* File that declares the deprecated export.
*/
path: string
/**
* Name of the deprecated 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 of the export.
*/
col: number
/**
* Byte offset of the export in the source file.
*/
span_start: number
/**
* Plain-text message of the `@deprecated` tag, capped at
* [`DEPRECATED_REASON_MAX_CHARS`] characters. Absent when the tag
* carries no text.
*/
deprecated_reason?: (string | null)
/**
* Exact number of distinct consumers: reference sites in reachable
* files, one per path, line, column and kind. `consumers` holds the
* first [`DEPRECATED_CONSUMER_SAMPLE_CAP`] of them, so the sample is
* complete when this count is at most the cap.
*/
consumer_count: number
/**
* Consumer sample sorted by path, line, column and kind, capped at
* [`DEPRECATED_CONSUMER_SAMPLE_CAP`] entries.
*/
consumers: DeprecatedExportConsumer[]
/**
* True when the export is part of the public API: it lives in an entry
* point, or a re-export chain reaches an entry point. External consumers
* are not visible, so the finding makes no removal claim.
*/
public_api: boolean
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
}
/**
* One file location that references a deprecated export.
*/
export interface DeprecatedExportConsumer {
/**
* File that references the deprecated export.
*/
path: string
/**
* 1-based line number of the import or re-export statement.
*/
line: number
/**
* 0-based byte column offset of the import or re-export statement.
*/
col: number
kind: DeprecatedConsumerKind
}
/**
* 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[]
/**
* Workspace roots whose package.json declares this package for the files
* that import it. Only a root finding fills this field: these imports use
* the nearer workspace declaration, so the root declaration stays unused.
*/
declared_and_imported_in?: string[]
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
/**
* Advisory caveats on the verdict behind this finding. A dependency is
* reported unused when NO module in the project imports its specifier,
* so a module that parsed with errors can hide the import that would
* have credited the package. Sorted, deduplicated, and omitted from the
* wire when empty. Never gates the finding, though `fallow fix`
* withholds the `remove-dependency` write while a caveat stands.
*/
reachability_caveats?: ReachabilityCaveat[]
}
/**
* 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[]
/**
* Workspace roots whose package.json declares this package for the files
* that import it. Only a root finding fills this field: these imports use
* the nearer workspace declaration, so the root declaration stays unused.
*/
declared_and_imported_in?: string[]
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
/**
* Advisory caveats on the verdict behind this finding. A dependency is
* reported unused when NO module in the project imports its specifier,
* so a module that parsed with errors can hide the import that would
* have credited the package. Sorted, deduplicated, and omitted from the
* wire when empty. Never gates the finding, though `fallow fix`
* withholds the `remove-dependency` write while a caveat stands.
*/
reachability_caveats?: ReachabilityCaveat[]
}
/**
* 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[]
/**
* Workspace roots whose package.json declares this package for the files
* that import it. Only a root finding fills this field: these imports use
* the nearer workspace declaration, so the root declaration stays unused.
*/
declared_and_imported_in?: string[]
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
/**
* Advisory caveats on the verdict behind this finding. A dependency is
* reported unused when NO module in the project imports its specifier,
* so a module that parsed with errors can hide the import that would
* have credited the package. Sorted, deduplicated, and omitted from the
* wire when empty. Never gates the finding, though `fallow fix`
* withholds the `remove-dependency` write while a caveat stands.
*/
reachability_caveats?: ReachabilityCaveat[]
}
/**
* 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
/**
* Advisory caveats on the verdict behind this finding. A member's usage
* is collected by walking the member accesses of every module the run
* parsed, so a member whose only reference lives in a file the run never
* read reads as unused exactly like an export does. Sorted,
* deduplicated, and omitted from the wire when empty. Never gates the
* finding; it does withhold the `remove-enum-member` mutation.
*/
reachability_caveats?: ReachabilityCaveat[]
}
/**
* 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (string | null)
/**
* Suggested next steps. Always emitted (possibly empty for
* forward-compat).
*/
actions: IssueAction[]
/**
* Type-aware evidence for this exact candidate when requested.
*/
semantic?: (SemanticCandidateDecision | null)
/**
* Set by the audit pass when this finding is introduced relative to
* the merge-base.
*/
introduced?: (AuditIntroduced | null)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
/**
* Advisory caveats on the verdict behind this finding. A class member's
* usage is collected by the same reachability-free member-access walk an
* enum member's is, so it takes the enum-member rule unchanged: any module
* this run analyzed incompletely can hold the access that credits it.
* Sorted, deduplicated, and omitted from the wire when empty. Never gates
* the finding; it does withhold the `remove-class-member` mutation that
* the type-aware pass would otherwise open.
*/
reachability_caveats?: ReachabilityCaveat[]
}
/**
* 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
/**
* Advisory caveats on the verdict behind this finding. A store member's
* usage is collected by the same reachability-free member-access walk a
* class member's is, so it takes the member rule unchanged: any module
* this run analyzed incompletely can hold the access that credits it.
* Sorted, deduplicated, and omitted from the wire when empty. There is no
* mutation here to withhold, because a store member offers none on any
* surface; this is disclosure only, so a reader deciding by hand is told
* what the run did not see.
*/
reachability_caveats?: ReachabilityCaveat[]
}
/**
* 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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[]
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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[]
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
}
/**
* Wire-shape envelope for a [`DevDependencyInProduction`] finding. Carries a
* `move-to-prod` primary (the promote-side mirror of
* [`TestOnlyDependencyFinding`]'s `move-to-dev`) plus the standard
* `ignoreDependencies` config suppress.
*/
export interface DevDependencyInProductionFinding {
/**
* devDependency imported at runtime from production code, consider moving
* to dependencies.
*/
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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
}
/**
* Wire-shape envelope for a [`PackageCycle`] finding. Mirrors
* [`CircularDependencyFinding`]: flattens the bare finding and carries a
* typed `actions` array (`refactor-cycle` primary plus `suppress-line`
* secondary).
*/
export interface PackageCycleFinding {
/**
* Workspace package labels in cycle order. The first entry is the
* lexicographically smallest label; the last entry imports the first.
* A label is the package name. When two or more workspace packages
* share a name, the label is `name (root)` with the project-relative
* package root, so that each label names one package.
*/
packages: string[]
/**
* Package root directories in cycle order: `package_roots[i]` is the
* root of `packages[i]`.
*/
package_roots: string[]
/**
* Number of packages in the cycle.
*/
length: number
/**
* One example import per hop, in cycle order: `edges[i]` goes from
* `packages[i]` to `packages[(i + 1) % length]`.
*/
edges: PackageCycleEdge[]
/**
* True when the group of packages that holds this cycle has more
* cycles than fallow lists. The listing stops at 20 cycles per group,
* or earlier on a very dense package graph. Break a listed cycle and
* run again to see the rest.
*/
group_truncated: boolean
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
}
/**
* One package hop in a [`PackageCycle`]: `from_package` imports
* `to_package`, and `path` holds one example import for that hop.
*
* The example import is the first runtime import by `(path, line)`. When
* every import on the hop is type-only, it is the first type-only import.
*/
export interface PackageCycleEdge {
/**
* Label of the importing workspace package, as in
* [`PackageCycle::packages`].
*/
from_package: string
/**
* Label of the imported workspace package, as in
* [`PackageCycle::packages`].
*/
to_package: string
/**
* File in `from_package` that holds the example import.
*/
path: string
/**
* File in `to_package` that the example import resolves to.
*/
target_path: string
/**
* 1-based line number of the example import.
*/
line: number
/**
* 0-based byte column offset of the example import.
*/
col: number
/**
* True when every import from `from_package` to `to_package` is
* type-only. A type-only hop has no runtime effect, but it can still
* force a build order (for example with declaration builds).
*/
type_only: boolean
}
/**
* 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. When the import
* goes through a re-export chain, this is the origin module that
* declares the imported symbol, not the barrel.
*/
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
/**
* The barrel file that the source file imports directly, when the
* violation comes from a re-export chain. Absent for a direct import.
*/
via_path?: (string | null)
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
}
/**
* Wire-shape envelope for a [`PolicyViolation`] finding. Carries actions for
* replacing the banned call, import, or effect, or suppressing it with a scoped
* `policy-violation:<pack>/<rule-id>` token.
*/
export interface PolicyViolationFinding {
/**
* The source file containing the banned call, import, or effectful usage.
*/
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`), the raw import specifier for `banned-import` (e.g.
* `moment/locale/nl`), `<effect>: <callee>` for `banned-effect`, or the
* exported name for `banned-export`. For `gdp-proof-producer`, the canonical
* factory with its JSON-quoted literal label or `...` for a dynamic label.
*/
matched: string
severity: PolicyViolationSeverity
/**
* The rule's author-provided message, when set.
*/
message?: (string | null)
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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
/**
* True when `rules.require-suppression-reason` reported a suppression
* comment or tag that has no reason.
*/
missing_reason?: boolean
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (string | null)
/**
* Suggested next steps. Always emitted.
*/
actions: IssueAction[]
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
}
/**
* Wire-shape envelope for an [`UnusedCatalogEntry`] finding. Per-instance
* `auto_fixable` flips to `false` when `hardcoded_consumers` is non-empty or
* the source is not `pnpm-workspace.yaml`.
*/
export interface UnusedCatalogEntryFinding {
/**
* Package name declared in the catalog (e.g. `"react"`, `"@scope/lib"`).
*/
entry_name: string
/**
* Catalog group: `"default"` for the default catalog map, or the named
* catalog key for entries declared under `catalogs.<name>`.
*/
catalog_name: string
/**
* Path to the catalog source file, relative to the analyzed root.
*/
path: string
/**
* 1-based line number of the catalog entry within the source file.
*/
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[]
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
}
/**
* Wire-shape envelope for an [`EmptyCatalogGroup`] finding. Carries a
* `remove-empty-catalog-group` primary. YAML-sourced findings also include a
* YAML-comment suppress action.
*/
export interface EmptyCatalogGroupFinding {
/**
* Catalog group name declared under the `catalogs` map.
*/
catalog_name: string
/**
* Path to the catalog source file, relative to the analyzed root.
*/
path: string
/**
* 1-based line number of the empty group header within the source file.
*/
line: number
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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 catalog source 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[]
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (string | null)
/**
* 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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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 the package manager 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)
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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). Actions are manual remediation guidance
* plus 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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). Actions are manual remediation
* guidance plus a line-level suppress.
*/
export interface UnrenderedComponentFinding {
/**
* The component file that is reachable but rendered nowhere.
*/
path: string
/**
* The component name. For `"vue"` / `"svelte"` / `"astro"` this is the SFC
* file stem (PascalCase); for `"angular"` it is the component class name; for
* `"lit"` it is the registered custom-element TAG (e.g. `x-foo`), not a file
* stem. Use `path` to anchor the file across all frameworks.
*/
component_name: string
/**
* Which framework this component belongs to: `"vue"`, `"svelte"`, `"astro"`,
* `"angular"`, or `"lit"`.
*/
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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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). Actions are manual
* remediation guidance plus a line-level suppress at the prop declaration.
*/
export interface UnusedComponentPropFinding {
/**
* The component file declaring the unused prop.
*/
path: string
/**
* The component name.
*/
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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
}
/**
* Wire-shape envelope for an [`AbsentComponentProp`] 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). Actions are manual
* remediation guidance plus a line-level suppress at the prop declaration.
*/
export interface AbsentComponentPropFinding {
/**
* Source path of the input declaration.
*/
path: string
/**
* Semantic declaration name, or SFC filename stem.
*/
component_name: string
/**
* Public framework token.
*/
framework: string
/**
* Public optional input name.
*/
prop_name: string
/**
* 1-based declaration line.
*/
line: number
/**
* 0-based declaration byte column.
*/
col: number
/**
* Whether omission has a declared default.
*/
has_default: boolean
/**
* Every inspected reachable caller, deterministically ordered.
*/
inspected_call_sites: ComponentPropCallSite[]
/**
* Manual-review meaning and limits of static evidence.
*/
explanation: string
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
}
/**
* One inspected reachable component invocation.
*/
export interface ComponentPropCallSite {
/**
* Caller source path.
*/
path: string
/**
* 1-based opening-tag line.
*/
line: number
/**
* 0-based original-source byte column.
*/
col: number
}
/**
* 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). Actions are manual
* remediation guidance plus 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
}
/**
* Wire-shape envelope for an [`UnusedComponentInput`] finding. There is no safe
* auto-fix: removing a declared input is judgement-bearing (the input may be
* part of a deliberately-stable public component API). The only action is a
* line-level suppress at the input declaration.
*/
export interface UnusedComponentInputFinding {
/**
* The Angular component/directive `.ts` file declaring the unused input.
*/
path: string
/**
* The component name (the `.ts` file stem).
*/
component_name: string
/**
* The declared input name that is never read.
*/
input_name: string
/**
* 1-based line number of the input declaration.
*/
line: number
/**
* 0-based byte column offset of the input declaration.
*/
col: number
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
}
/**
* Wire-shape envelope for an [`UnusedComponentOutput`] finding. There is no safe
* auto-fix: removing a declared output 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 output declaration.
*/
export interface UnusedComponentOutputFinding {
/**
* The Angular component/directive `.ts` file declaring the unused output.
*/
path: string
/**
* The component name (the `.ts` file stem).
*/
component_name: string
/**
* The declared output name that is never emitted.
*/
output_name: string
/**
* 1-based line number of the output declaration.
*/
line: number
/**
* 0-based byte column offset of the output declaration.
*/
col: number
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | null)
}
/**
* Wire-shape envelope for an [`UnusedSvelteEvent`] finding. There is no safe
* auto-fix: removing a dispatched event is judgement-bearing (the event may be
* part of a deliberately-stable public component API, or a listener may be
* added later). Actions are manual remediation guidance plus a line-level
* suppress at the `dispatch` call.
*/
export interface UnusedSvelteEventFinding {
/**
* The `.svelte` component dispatching the unlistened event.
*/
path: string
/**
* The component name (the `.svelte` file stem).
*/
component_name: string
/**
* The dispatched event name that is listened to nowhere.
*/
event_name: string
/**
* 1-based line number of the `dispatch('<name>')` call.
*/
line: number
/**
* 0-based byte column offset of the `dispatch('<name>')` call.
*/
col: number
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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). Actions are manual remediation guidance plus 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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. Actions are manual remediation guidance plus 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)
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Gate severity of this finding after `rules` and `overrides[].rules`
* resolve for its path. CI formats read it for the annotation, SARIF
* and CodeClimate level. Absent in output from older versions. Not
* part of the finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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[]
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Rule severity of this finding. This type never gates the run, so the
* value does not change the exit code. `fallow report --from` reads it
* for the SARIF level, so the level does not depend on the config at
* render time. Absent in output from older versions. Not part of the
* finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Rule severity of this finding. This type never gates the run, so the
* value does not change the exit code. `fallow report --from` reads it
* for the SARIF level, so the level does not depend on the config at
* render time. Absent in output from older versions. Not part of the
* finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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[]
/**
* Stable id of this finding: `dc1:<rule>:<16 hex digits>`, with a
* `~<k>` suffix when several findings of one type share an identity.
* Line and column are not inputs, so the id survives line shifts,
* reformats and reorders. A rename of the file or the symbol gives a
* new id. Absent in output from older versions.
*/
finding_id?: (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)
/**
* Rule severity of this finding. This type never gates the run, so the
* value does not change the exit code. `fallow report --from` reads it
* for the SARIF level, so the level does not depend on the config at
* render time. Absent in output from older versions. Not part of the
* finding identity, baseline keys or fingerprints.
*/
effective_severity?: (EffectiveSeverity | 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
}
/**
* One run's machine-readable view of a loaded baseline.
*
* `stale` and `gate_trips` answer different questions and legitimately
* disagree. `stale` mirrors the unasked-for stderr advisory, which stays silent
* below a quarter of the baseline and on a run that produced no findings at
* all, because a cleaned project and a rotted baseline look identical from
* there. `gate_trips` mirrors the opt-in `--fail-on-stale-baseline` rule, which
* a repository asks for precisely to catch those cases, so it fires on any
* stale entry. A rotted baseline on a cleaned project reports
* `stale: false` with `gate_trips: true`; that is the contract, not a defect.
*
* `change_scoped` is the member a consumer must read before dividing
* `matched_entries` by `baseline_entries`. A run narrowed to part of the
* project compares a whole-project baseline against a slice of it and can
* report `matched_entries: 0` while the baseline is perfectly healthy, so both
* `stale` and `gate_trips` are false there by construction. The remedy for a
* tripped gate is always the same: re-save the baseline from a whole-project
* run with `--save-baseline`.
*/
export interface BaselineStaleness {
/**
* Entries carried by the loaded baseline file. On health these are the
* complexity and CRAP finding entries; runtime-coverage suppressions and
* refactoring target keys carried by the same file are not counted.
*/
baseline_entries: number
/**
* Entries that matched a current finding on this run and were filtered out
* of the report. On health this includes entries matched through a
* followed file move.
*/
matched_entries: number
/**
* Entries that matched no current finding on this run:
* `baseline_entries - matched_entries`.
*/
stale_entries: number
/**
* Findings this run produced before the baseline filtered them. Zero means
* there was nothing to compare, either because the project is clean or
* because the scope was empty, which is why `stale` stays false there even
* when every entry went unmatched.
*/
current_findings: number
/**
* Health only: the number of functions above a complexity threshold that
* the baseline does not accept. This is a count of functions, like
* `summary.functions_above_threshold`, not a count of baseline entries,
* and it is the value before `--top`. A run that does not list the
* complexity findings (for example `--score`) still reports it, so a
* reader can tell the new functions from the accepted ones.
*
* `dead-code` and `dupes` do not emit it. An envelope from a fallow
* version before this member does not carry it either.
*/
remaining_findings?: (number | null)
/**
* True when this run analyzed only part of the project, so a whole-project
* baseline matches less of it for reasons that are not rot. The channels
* differ per command and include a diff, a base ref, `--changed-since`,
* `--workspace`, `--changed-workspaces`, `--scope`, `--file`, an
* issue-type filter, and production mode. Both `stale` and `gate_trips`
* are false whenever this is true. `scope_reasons` names the channels
* that fired.
*/
change_scoped: boolean
/**
* True exactly when the advisory stderr warning fired: not change-scoped,
* at least one current finding before baseline filtering, and either
* nothing matched or `stale_entries` reached a quarter of
* `baseline_entries`.
*/
stale: boolean
warning: BaselineStalenessAdvisory
/**
* True exactly when `unrecognised_format` is true, or
* `!change_scoped && baseline_entries > 0 && matched_entries < baseline_entries`.
* That is the rule `--fail-on-stale-baseline` applies. Deliberately
* stricter than `stale`: any unmatched entry counts, and so does a file
* this command could not read as its own, which protects nothing at all.
* The second half is suppressed by `change_scoped` and the first is not:
* which command wrote a file does not depend on how much of the project
* the run looked at.
*
* It describes the baseline, not the run's exit code: `health
* --report-only` is an explicit request never to fail, so that run exits 0
* and says so on stderr while still reporting `gate_trips: true` here, and
* `fallow audit` never judges a baseline at all, so its
* `gate_outcomes["stale-baseline"]` stands down beside a section that
* reports `true`.
*/
gate_trips: boolean
/**
* Entries that matched only by following a file move. Only `health` can
* follow one, in its identity baseline mode; `dead-code` and `dupes` match
* entries by fingerprint and never classify one as moved, so they report
* `0`. Always `0` in health's count mode too.
*/
moved_entries: number
/**
* True when the loaded file is not a baseline of the command that read it:
* it names another command in its top-level `kind`, or it names none and
* carries no key this command's own format writes. Read this, not
* `baseline_entries == 0`, before telling anyone their baseline is the
* wrong file: a baseline saved from a project that had nothing to record
* is legitimately empty and is not a mistake.
*
* Present only when true, so an envelope from a run that loaded its own
* baseline is unchanged. All three commands set it, including `dead-code`,
* which classifies the file before its required fields could reject it.
* A file with no `kind` is the reading a baseline saved before that member
* existed gets, which is why the keys remain the fallback.
*/
unrecognised_format?: boolean
/**
* The command that saved the loaded file, when `unrecognised_format` is
* true and the file names a writer this version knows: `dead-code`,
* `dupes` or `health`, the token the file carries in its top-level `kind`.
*
* Absent, never null, for this command's own baseline, for a file that
* names no writer (an empty file, or a baseline saved before `kind`
* existed) and for a `kind` token this version does not know. The value
* set is OPEN: a later release can add a writer, so treat an unknown value
* as "another command". The CLI computes it once per loaded baseline and
* uses the same value for its stderr note.
*/
saved_by?: string
/**
* `legacy` when the loaded dead-code baseline has no `identity`, so its
* entries use the old key forms and some of them hold a line. The run
* still applies the file. `--save-baseline` rewrites it with line-free
* keys. Absent for a current baseline and on `dupes` and `health`. The
* value set is OPEN.
*/
format?: string
/**
* Which channels narrowed this run, present and non-empty exactly when
* `change_scoped` is true. Both members are derived from one function, so
* the boolean and the array cannot disagree.
*
* Read it to decide whether the narrowing is removable: a run narrowed
* only by `diff`, `changed-since`, `changed-files`, `scope`, `file` or
* `issue-type-filter` can be repeated unscoped to judge the baseline,
* while `production`, `workspace` and `changed-workspaces` are the
* caller's own choice about what to analyze and an unscoped repeat would
* contradict it.
*
* The name set is OPEN and the names a command can emit differ per
* command; see [`ScopeReason`].
*/
scope_reasons?: ScopeReason[]
}
/**
* The result of a `--finding-id` query, present only when the run received
* one or more `--finding-id` values.
*
* A requested id that is missing from a conclusive run means "fixed,
* suppressed, or ignored by config", never "unknown": an inline suppression
* comment or an `ignoreFindings` entry is a choice a person made to hide the
* finding, so it counts as absent. A missing id in a run that is not
* conclusive is unknown, never resolved.
*
* Every list keeps the order of `requested`. `found` and `missing` partition
* `requested`. `filtered` is a subset of `missing`.
*/
export interface FindingIdQuery {
/**
* The requested ids, without duplicates, in the order of the arguments.
*/
requested: string[]
/**
* The requested ids that this report contains.
*/
found: string[]
/**
* The requested ids that this report does not contain. When `conclusive`
* is true, a missing id is fixed, suppressed, or ignored by config.
* Otherwise its state is unknown.
*/
missing: string[]
/**
* The missing ids that the analysis still found before a filter of this
* run (scope, baseline, issue-type filter) removed them. Such a finding
* still exists.
*/
filtered: string[]
/**
* True when no option of this run can hide a finding without a fix, and
* no requested id was filtered. Only then does a missing id mean that
* the analysis no longer reports the finding.
*/
conclusive: boolean
/**
* Why the query is not conclusive, sorted. Empty exactly when
* `conclusive` is true.
*/
inconclusive_reasons: FindingIdQueryReason[]
/**
* A stable hash (`af1:<16 hex digits>`) of every input other than the
* source code that decides which findings the run reports:
* - the fallow version;
* - the merged config after `extends` (without keys that only shape other
* commands), the loaded external plugins and rule packs;
* - production mode, `includeEntryExports`, the effective rules, the
* type-aware mode, requirement and project list, the file size limit;
* - the root-relative path and content of each repository `.gitignore`,
* `.ignore` and `.git/info/exclude`, each `package.json`, each
* `tsconfig*.json` and `jsconfig*.json` with the files its `extends`
* names, and each file that matches a built-in or external plugin
* config pattern (for example `vite.config.ts`).
*
* File content is normalized (CRLF to LF, trailing newlines removed).
* Known exclusions: the global git excludes file and other machine
* environment outside the `FALLOW_*` variables. Store the fingerprint
* with a verdict. A later query with another fingerprint is unknown, even
* when `conclusive` is true. An edit to a source file keeps it; an edit
* to a manifest or project config changes it, also when the edit fixes a
* dependency finding.
*/
analysis_fingerprint: string
}
/**
* 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)
}
/**
* Every narrowing or shaping request a run RECEIVED, keyed by name.
*
* Received, not failed: a request the run honoured is published with
* `status: "applied"`, so a consumer can say "scoped to the change"
* positively. Read an absent object as "nothing was asked for", never as
* "nothing failed".
*
* Absent from an envelope whenever it is empty, so a run that was asked for
* nothing is byte-identical to one produced before this object existed. An
* empty object is never emitted: it would assert that something was asked and
* all of it applied, which is a different and false claim.
*
* The names this build can emit are `changed-since`, `diff-filter`,
* `package-baselines`, `sarif-file` and `group-filter`. The reasons are `git-missing`,
* `not-a-repository`, `git-failed` and `invalid-ref` for `changed-since`,
* `unknown-workspace`, `git-missing`, `not-a-repository` and `git-failed`
* for `package-baselines`, `oversize`,
* `unreadable`, `not-utf8`, `foreign-namespace` and `ambiguous-base` for
* `diff-filter`, and `directory-create-failed`, `write-failed` and
* `serialize-failed` for `sarif-file`. Every set is OPEN: a name a consumer
* does not recognise means "some request", not an error.
*
* `sarif-file` reports a SECONDARY artifact rather than the scope of the
* report it travels in, and it is in the same object for the same reason the
* others are: the run was asked to do something and did something else, and
* nothing in the primary report says so. Which of the two an entry is, every
* entry says for itself: `affects` is `scope` for the narrowing requests and
* `artifact` for this one. Select on it. A consumer that instead assumes the
* whole object narrows the report tells its reader an unwritten SARIF file
* widened the analysis, which is what `affects` exists to prevent.
*
* `scope_size` is emitted for `diff-filter`, in added lines, for
* `changed-since`, in changed files that the run analyzed, and for
* `group-filter`, in groups that the run kept. `package-baselines`
* and `sarif-file` measure no scope; the applied package refs travel in
* `package_baselines`. A consumer reads the unit off the name, so a name that
* starts to measure its own scope in a later release needs no change here.
*
* `invalid-ref` is reachable only through the programmatic API. The
* `--changed-since` flag validates its value before a run starts and fails
* with exit 2 and an error document, which is the right side to err on: a
* malformed ref is invalid input rather than a report of the wrong scope.
*/
export interface RequestOutcomes {
[k: string]: RequestOutcome
}
/**
* One request's fate on one run.
*
* `reason` and `message` are present exactly when `status` is not `applied`,
* and absent otherwise, so a consumer that only wants to know whether a
* report is scoped reads `status` alone.
*/
export interface RequestOutcome {
status: RequestStatus
affects: RequestEffect
/**
* What was asked, as the user spelled it: the git ref for
* `changed-since`, the diff source label (`--diff-file pr.diff`,
* `--diff-stdin`, `$FALLOW_DIFF_FILE build/pr.diff`, or
* `diffFile pr.diff` for the programmatic option) for `diff-filter`,
* `workspaces.changedSince` for `package-baselines`, the target path for
* `sarif-file`, the comma-joined selector patterns for `group-filter`.
* Echoed rather than normalised, so a
* consumer must not join it to the project root the way it joins every
* other path-shaped field.
*/
requested: string
/**
* How much this request left in scope, in the request's own unit, when the
* run applied it AND measured that scope. Absent otherwise, including on
* every unapplied entry: a request that stood down narrowed nothing, so a
* number there would describe a scope nobody applied.
*
* The unit belongs to the name. `diff-filter` counts added lines, which is
* what its filter keeps a finding for. `changed-since` counts changed
* files that the run analyzed: a changed file that discovery or an ignore
* rule dropped does not count, so a change to a README only gives `0`. A
* combined run counts a file that any of its analyses kept, because a
* per-analysis `production` setting can give its analyses different
* files. `group-filter` counts the groups that the run kept.
* Read the unit off the name the entry is keyed under, never across names,
* and read an absent member as "not measured" rather than as zero.
*
* The count is what the run INDEXED rather than the true total:
* `diff-filter` indexes at most one million added lines and reports that
* cap for a larger diff, so read any non-zero value as a lower bound.
*
* `0` is the case this member exists for: a request that applied over an
* EMPTY scope. Every finding then filters out and the report reads clean,
* so a consumer that sees no findings beside `scope_size: 0` learns that
* nothing was analyzable rather than that the code is clean.
*/
scope_size?: (number | null)
/**
* Why the request was not applied, as a kebab-case token. Present exactly
* when `status` is not `applied`. The set is open per request name; the
* names this build can emit are listed on [`RequestOutcomes`].
*/
reason?: (string | null)
/**
* One sentence naming what was asked, what happened instead, and the next
* step. Byte-identical to the stderr line for the same case, so a
* consumer that renders this never contradicts a log a human read.
* Present exactly when `status` is not `applied`.
*/
message?: (string | null)
}
/**
* One applied baseline for an exact, project-relative workspace root.
*/
export interface PackageBaselineStatus {
/**
* Workspace package root, relative to the analysis root with `/` separators.
*/
workspace_root: string
/**
* Git ref used to select changed files in this package.
*/
reference: string
}
/**
* 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 the `DupesOutput` envelope 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. The typed envelope replaced
* the legacy JSON post-pass injection; a guard test in
* `crates/cli/src/report/json.rs` rejects any reintroduced post-pass.
*/
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
/**
* Lowest all-pairs similarity for a near-miss clone group. Exact clone
* groups omit this field.
*/
similarity?: 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
/**
* Maximum directory-tree or same-file line distance between instances.
*/
spread: number
/**
* 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)
/**
* Set only by `fallow audit` under `--gate new-only`, on groups whose
* `introduced` flag the gate demoted to `false`: why the demotion
* happened. `None` everywhere else, including `fallow dupes
* --format json` (issue #2220).
*/
demotion_reason?: (CloneDemotionReason | 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.
*
* Omitted from JSON when the caller asked for a location-only payload
* (`fallow dupes --no-fragments`, and the MCP `find_dupes` default). The
* five location fields above address the same text, so a consumer that
* wants the source reads it from the file.
*/
fragment?: string
/**
* Whether the file path is a symlink, or lies under a symlinked
* directory, inside the project root. Omitted when `false`.
*
* A clone with a symlinked instance can be the same file under two
* paths, not copied code. `duplicates.ignoreSymlinks` (or
* `fallow dupes --ignore-symlinks`) removes these instances.
*/
is_symlink?: boolean
}
/**
* Per-action wire shape attached to each `CloneGroupFinding` and
* `AttributedCloneGroupFinding` (see `crates/api/src/dupes_output.rs`):
* `extract-shared` plus `suppress-line`. The typed wrappers replaced the
* legacy JSON post-pass injection that used to live in the CLI report layer.
*/
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.
*/
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 in redundant clone copies, excluding one retained copy per group.
*/
duplicated_tokens: number
/**
* Number of clone groups the scoped corpus contains after filtering.
* `--top` does not change it; compare it with `clone_groups_shown` on the
* envelope to see how much of the corpus the array carries.
*/
clone_groups: number
/**
* Number of clone families the scoped corpus contains after filtering.
* `--top` truncates `clone_families[]` along with `clone_groups[]` but
* does not change this counter; compare it with `clone_families_shown` on
* the envelope to see how much of the corpus the array carries.
*/
clone_families: number
/**
* Total clone instances across the scoped corpus after filtering.
* `--top` does not change it.
*/
clone_instances: number
/**
* Percentage of duplicated lines (0.0 to 100.0). `--top` does not change
* this scoped corpus metric.
*/
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.
* This counter covers only the minimum-occurrence filter.
*/
clone_groups_below_min_occurrences?: number
/**
* Number of clone groups hidden by `duplicates.ignoredClones`.
*/
clone_groups_ignored?: number
/**
* Near-miss candidate comparisons skipped by bounded-work limits.
*/
near_candidates_skipped?: 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
/**
* The sections that this run produced, in a fixed order. A renderer
* reads it to tell an empty section from a section that the run did not
* produce: `findings` is the complexity list only when `complexity` is
* in this array. The value set is OPEN (see [`HealthSection`]). Absent
* in an envelope from a fallow version before this member, and on a
* report that no health run built.
*/
sections?: (HealthSection[] | null)
/**
* 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.
*
* Set whenever the run measured churn, which needs readable git history;
* `--hotspots` adds the per-file [`hotspots`](Self::hotspots) listing
* beside it rather than gating this summary.
*/
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)
/**
* Optional framework-specific detector coverage. Present only when the
* health run already needed the dead-code analysis output.
*/
framework_health?: (FrameworkHealthDiagnostics | null)
/**
* Structural CSS analytics (specificity hotspots, `!important` density,
* over-complex selectors, deep nesting). Present only with `--css`.
*/
css_analytics?: (CssAnalyticsReport | null)
/**
* Styling-health score and letter grade: a SECOND health axis derived from
* the CSS analytics (the design-system axis), orthogonal to the JS/TS code
* `health_score`. Present only with `--css` (the same condition as
* `css_analytics`), so a plain `fallow health` run is byte-unchanged. The
* code score is never affected by this field.
*/
styling_health?: (StylingHealth | null)
/**
* Advisory STYLING FINDINGS: the graduation of the descriptive css
* candidates into first-class, severity-aware, suppressible findings
* surfaced in `fallow audit`. Verdict-neutral by default (the rule defaults
* to `warn`); the styling domain's OWN findings collection, not the dead-code
* `AnalysisResults`. Present only with `--css`; empty is skipped so a plain
* run is byte-unchanged.
*/
styling_findings?: StylingFinding[]
}
/**
* Wire envelope for a single complexity finding.
*/
export interface HealthFinding {
/**
* File path relative to the project root.
*/
path: string
/**
* Function name, or a synthesized name for anonymous functions.
*/
name: string
/**
* 1-based line the function starts on.
*/
line: number
/**
* 1-based column the function starts on.
*/
col: number
/**
* Cyclomatic complexity of the function.
*/
cyclomatic: number
/**
* Cognitive complexity of the function.
*/
cognitive: number
/**
* Lines of code in the function body.
*/
line_count: number
/**
* Number of declared parameters.
*/
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
/**
* Gate severity after the `complexity-*` rules and their
* `overrides[].rules` entries: `error` fails the run, `warn` does not.
* The most severe rule of the kinds in `exceeded` wins. It is separate
* from the band in `severity`, which ranks the finding and does not gate
* it. Absent in reports from older versions.
*/
effective_severity?: (EffectiveSeverity | null)
/**
* CRAP score (change risk anti-pattern), when coverage data exists.
*/
crap?: (number | null)
/**
* Test coverage percentage (0-100) backing the CRAP score.
*/
coverage_pct?: (number | null)
/**
* Coverage tier bucket.
*
* Derived from `coverage_pct` when coverage was measured. When
* `coverage_source` is estimated, `coverage_pct` is absent and the tier
* describes the static estimate behind the CRAP score rather than an
* observation, so read the two fields together.
*/
coverage_tier?: (CoverageTier | null)
/**
* Provenance of the coverage signal.
*/
coverage_source?: (CoverageSource | null)
/**
* Component file the inherited coverage estimate came from, for
* component-inherited coverage.
*/
inherited_from?: (string | null)
/**
* Aggregate of the enclosing component's findings, when rolled up.
*/
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)
}
/**
* Component-level aggregate attached to a template complexity finding,
* pairing the template's scores with the worst class-side function.
*/
export interface ComponentRollup {
/**
* Component name.
*/
component: string
/**
* Name of the worst-scoring function in the component class.
*/
class_worst_function: string
/**
* Cyclomatic complexity of that worst class function.
*/
class_cyclomatic: number
/**
* Cognitive complexity of that worst class function.
*/
class_cognitive: number
/**
* Template file path relative to the project root.
*/
template_path: string
/**
* Cyclomatic complexity of the template.
*/
template_cyclomatic: number
/**
* Cognitive complexity of the template.
*/
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 {
/**
* Effective cyclomatic-complexity ceiling for the matched file.
*/
max_cyclomatic: number
/**
* Effective cognitive-complexity ceiling for the matched file.
*/
max_cognitive: number
/**
* Effective CRAP-score ceiling for the matched file.
*/
max_crap: number
/**
* Effective unit-size ceiling (maximum function length in lines) for the
* matched file, after applying any `thresholdOverrides` on top of the
* global `health.maxUnitSize` default.
*/
max_unit_size: 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-output/src/health_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-template-anchor-line`, `above-component-worst-method`, or
* `top-of-template`). Present on `suppress-line` and `suppress-file`
* action variants. `above-template-anchor-line` is used for
* single-file-component markup (`.svelte`, `.vue`, `.astro`), where the
* synthetic `<template>` unit is anchored at its first contributing
* construct rather than at the top of the file, so the comment belongs on
* the line immediately preceding the reported line.
*/
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 included in the health analysis.
*/
files_analyzed: number
/**
* Functions and template units checked for threshold findings across the
* analyzed files. Synthetic module-scope units are excluded. Cyclomatic
* aggregates include module units too; `vital_signs.cyclomatic_population`
* reports the disjoint authored-function, module, and template populations
* behind those aggregates.
*/
functions_analyzed: number
/**
* Functions exceeding at least one complexity or CRAP threshold.
*/
functions_above_threshold: number
/**
* Global cyclomatic-complexity ceiling for this run.
*/
max_cyclomatic_threshold: number
/**
* Global cognitive-complexity ceiling for this run.
*/
max_cognitive_threshold: number
/**
* Global CRAP-score ceiling for this run.
*/
max_crap_threshold: number
/**
* Effective global unit-size ceiling (`health.maxUnitSize`, maximum
* function length in lines) for this run. Sits alongside the other three
* `max_*_threshold` siblings so a consumer reading the summary sees every
* configured threshold. Per-file `thresholdOverrides` are not reflected
* here; this is the global default.
*/
max_unit_size_threshold: number
/**
* Files with a computed maintainability score; absent when file scoring
* did not run.
*/
files_scored?: (number | null)
/**
* Mean maintainability index over scored files (0-100).
*/
average_maintainability?: (number | null)
/**
* Coverage model behind the CRAP scores, when coverage was used.
*/
coverage_model?: (CoverageModel | null)
/**
* Input format of the measured coverage (`istanbul` or `v8`). Present
* only with `coverage_model: "istanbul"`.
*/
coverage_input_format?: (CoverageInputFormat | null)
/**
* Whether CRAP findings mix coverage sources.
*/
coverage_source_consistency?: (CoverageSourceConsistency | null)
/**
* Functions matched against the Istanbul coverage file, in Istanbul mode.
*/
istanbul_matched?: (number | null)
/**
* Functions in the Istanbul coverage file, in Istanbul mode.
*/
istanbul_total?: (number | null)
/**
* Analyzed files the Istanbul coverage file carried an entry for.
* Read against `istanbul_files_total`, this separates a coverage file
* that did not join from code the coverage file says nothing ran in.
*/
istanbul_files_matched?: (number | null)
/**
* Files described by the Istanbul coverage file, joined or not.
*/
istanbul_files_total?: (number | null)
/**
* Findings with critical severity.
*/
severity_critical_count: number
/**
* Findings with high severity.
*/
severity_high_count: number
/**
* Findings with moderate severity.
*/
severity_moderate_count: number
/**
* Baseline staleness data, present only when a baseline was loaded.
*/
baseline_staleness?: (BaselineStaleness | null)
}
/**
* Report entry describing whether a threshold override is active, stale, or
* no longer matching any analyzed file or function.
*/
export interface ThresholdOverrideState {
status: ThresholdOverrideStatus
/**
* Index of the entry in the configured `thresholdOverrides` array.
* Several rows can share one index when the override participates in more
* than one dimension; group on this to count configured overrides.
*/
override_index: number
dimension: ThresholdOverrideDimension
/**
* Dimensions the matched unit still breaches despite this override,
* whether or not this override configures their ceilings. Non-empty means
* raising the ceiling did not settle the matter: a complexity or CRAP
* finding survived, or the unit is still longer than the resolved
* `maxUnitSize`, which keeps it in the large-function list without
* emitting a finding of its own.
*/
outstanding?: ThresholdOverrideDimension[]
/**
* Matched file path, when the override matched one.
*/
path?: (string | null)
/**
* Matched function name, for function-scoped overrides.
*/
function?: (string | null)
/**
* 1-based line of the matched unit. Absent on `no_match` rows, which
* describe an entry that matched nothing. Name alone is not an identity:
* one file can hold several units sharing a name, so this pairs with
* `col` to keep their rows distinct (issue #2163).
*/
line?: (number | null)
/**
* 0-based byte column of the matched unit. Absent on `no_match` rows.
*/
col?: (number | null)
configured_thresholds: HealthConfiguredThresholds
effective_thresholds: HealthEffectiveThresholds
/**
* Current complexity metrics of the matched code, when matched.
*/
metrics?: (ThresholdOverrideMetrics | null)
/**
* Human-readable explanation of the status.
*/
reason?: (string | null)
}
/**
* Threshold values configured by a single override entry.
*/
export interface HealthConfiguredThresholds {
/**
* Cyclomatic ceiling set by the override, when it sets one.
*/
max_cyclomatic?: (number | null)
/**
* Cognitive ceiling set by the override, when it sets one.
*/
max_cognitive?: (number | null)
/**
* CRAP ceiling set by the override, when it sets one.
*/
max_crap?: (number | null)
/**
* Unit-size ceiling set by the override, when it sets one.
*/
max_unit_size?: (number | null)
}
/**
* Current complexity metrics for a matched threshold override entry.
*/
export interface ThresholdOverrideMetrics {
/**
* Current cyclomatic complexity of the matched function.
*/
cyclomatic: number
/**
* Current cognitive complexity of the matched function.
*/
cognitive: number
/**
* Current CRAP score, when coverage data exists.
*/
crap?: (number | null)
/**
* Measured line count of the matched unit. Present on complexity rows,
* where `maxUnitSize` participates in the dimension; absent on CRAP rows
* and `<component>` rollup rows, which are never scored on unit size.
*/
line_count?: (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 authored functions, module-scope
* units, and template units. See `cyclomatic_population` for the denominator.
*/
avg_cyclomatic: number
/**
* Percentage of complexity units at or above the critical cyclomatic threshold.
* Used by the scale-invariant health score.
*/
critical_complexity_pct?: (number | null)
/**
* 90th percentile cyclomatic complexity across the same unit population.
*/
p90_cyclomatic: number
/**
* Population behind the cyclomatic mean, percentile, and critical share.
* Present on current analyses; absent on older saved snapshots.
*/
cyclomatic_population?: (CyclomaticPopulation | null)
/**
* 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
}
/**
* Disjoint populations feeding the cyclomatic distribution. Counts and sums
* across all three groups reconstruct its weighted mean. Module-scope units
* contribute to aggregates only and do not produce function findings.
*/
export interface CyclomaticPopulation {
functions: CyclomaticUnitPopulation
modules: CyclomaticUnitPopulation
templates: CyclomaticUnitPopulation
}
/**
* Cyclomatic measurements for one kind of complexity unit.
*/
export interface CyclomaticUnitPopulation {
/**
* Number of units measured, including those below finding thresholds.
*/
count: number
/**
* Sum of cyclomatic complexity, before rounding or threshold filtering.
*/
sum: number
/**
* Highest cyclomatic complexity, or null when this population is empty.
*/
max?: (number | null)
}
/**
* 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
/**
* Number of unreachable files.
*/
dead_files: number
/**
* Number of unused exports.
*/
dead_exports: number
/**
* Lines inside detected clones; absent when duplication did not run.
*/
duplicated_lines?: (number | null)
/**
* Lines scanned by duplication; absent when duplication did not run.
*/
total_lines?: (number | null)
/**
* Files with a computed maintainability score; absent when file scoring
* did not run.
*/
files_scored?: (number | null)
/**
* Total declared dependencies across manifest sections.
*/
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
}
/**
* Overall project health score: 100 minus capped per-category penalties.
*/
export interface HealthScore {
/**
* Score formula version; see [`HEALTH_SCORE_FORMULA_VERSION`].
*/
formula_version: number
/**
* Health score in `[0, 100]`; higher is healthier.
*/
score: number
/**
* Letter grade from [`letter_grade`] (A>=85, B>=70, C>=55, D>=40, F<40).
*/
grade: string
penalties: HealthScorePenalties
}
/**
* Per-component penalty breakdown for the health score.
*/
export interface HealthScorePenalties {
/**
* Points subtracted for unreachable files; absent when dead-code data
* was not available.
*/
dead_files?: (number | null)
/**
* Points subtracted for unused exports; absent when dead-code data was
* not available.
*/
dead_exports?: (number | null)
/**
* Points subtracted for overall complexity load.
*/
complexity: number
/**
* Points subtracted for the complexity tail (v1: p90 cyclomatic; v2:
* critical-complexity density).
*/
p90_complexity: number
/**
* Points subtracted for low maintainability-index files; absent when
* file scores were not computed.
*/
maintainability?: (number | null)
/**
* Points subtracted for churn-times-complexity hotspots; absent without
* git history.
*/
hotspots?: (number | null)
/**
* Points subtracted for unused dependencies; absent when dead-code data
* was not available.
*/
unused_deps?: (number | null)
/**
* Points subtracted for circular dependency chains; absent when
* dead-code data was not available.
*/
circular_deps?: (number | null)
/**
* Penalty for oversized functions, computed against fixed calibration
* (very-high-risk bin edge at 60 LOC). Deliberately independent of
* `health.maxUnitSize`, which filters the large-functions findings list
* only; raising that threshold empties the list without moving this
* penalty. `health.ignore` removes files from the score entirely.
*/
unit_size?: (number | null)
/**
* Points subtracted for fan-in coupling concentration; absent when the
* module graph was not available.
*/
coupling?: (number | null)
/**
* Points subtracted for duplicated code; absent when the duplication
* pipeline did not run.
*/
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 {
/**
* File path relative to the project root.
*/
path: string
/**
* Modules importing this file.
*/
fan_in: number
/**
* Modules this file imports.
*/
fan_out: number
/**
* Unused exports as a fraction of the file's exports, in `[0, 1]`.
*/
dead_code_ratio: number
/**
* Total cyclomatic complexity per line of code.
*/
complexity_density: number
/**
* Maintainability index (0-100); higher is healthier.
*/
maintainability_index: number
/**
* Summed cyclomatic complexity over all units, including module and template scope.
*/
total_cyclomatic: number
/**
* Summed cognitive complexity over all units, including module and template scope.
*/
total_cognitive: number
/**
* Complexity units in the file, including synthetic module and template units.
*/
function_count: number
/**
* Lines of code in the file.
*/
lines: number
/**
* Highest CRAP score among the file's functions. Always the raw measured
* value; threshold overrides never rewrite it.
*/
crap_max: number
/**
* Functions whose rounded CRAP score meets or exceeds their effective
* ceiling, resolved from `health.thresholdOverrides` over the global
* `maxCrap` / `--max-crap` value. Zero when CRAP enforcement is disabled
* (global ceiling `0`).
*/
crap_above_threshold: number
/**
* Functions whose rounded CRAP score is at or above the canonical 30.0
* baseline but below their effective ceiling: the count the configuration
* let through. Stays `0` when the effective ceiling is stricter than 30.
* When CRAP enforcement is disabled (global ceiling `0`), counts every
* function at or above the canonical baseline. Omitted when zero.
*/
crap_exempted?: number
/**
* Lowest effective CRAP ceiling among the file's functions, present only
* when it differs from the run global (`summary.max_crap_threshold`).
* Consumers fall back to `summary.max_crap_threshold` when absent.
*/
crap_effective_threshold?: (number | null)
}
/**
* 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 for the current wire contract.
*/
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-output/src/health_coverage_gaps.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-output/src/health_coverage_gaps.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 {
/**
* File path relative to the project root.
*/
path: string
/**
* Churn-times-complexity hotspot score; higher is riskier.
*/
score: number
/**
* Commits touching the file in the analysis window.
*/
commits: number
/**
* Recency-weighted commit count.
*/
weighted_commits: number
/**
* Lines added to the file in the analysis window.
*/
lines_added: number
/**
* Lines deleted from the file in the analysis window.
*/
lines_deleted: number
/**
* Total cyclomatic complexity per line of code.
*/
complexity_density: number
/**
* Modules importing this file.
*/
fan_in: number
trend: ChurnTrend
/**
* Ownership metrics, when ownership analysis ran.
*/
ownership?: (OwnershipMetrics | null)
/**
* True for files matched by test-path patterns; omitted when false.
*/
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[]
}
/**
* Ownership metrics for a hotspot file, derived from git history and
* CODEOWNERS declarations.
*/
export interface OwnershipMetrics {
/**
* Minimum contributors covering half the file's commits.
*/
bus_factor: number
/**
* Distinct contributors touching the file in the window.
*/
contributor_count: number
top_contributor: ContributorEntry
/**
* Contributors active in the recent window; omitted when empty.
*/
recent_contributors?: ContributorEntry[]
/**
* Contributors best positioned to review changes; omitted when empty.
*/
suggested_reviewers?: ContributorEntry[]
/**
* Owner declared in CODEOWNERS, when one matches the file.
*/
declared_owner?: (string | null)
/**
* Whether no owner could be resolved; `null` when ownership resolution
* did not run.
*/
unowned?: (boolean | null)
ownership_state: OwnershipState
/**
* True when recent contributions drift away from the declared ownership.
*/
drift: boolean
/**
* Human-readable explanation of the drift, when drifting.
*/
drift_reason?: (string | null)
}
/**
* One contributor row in ownership metrics.
*/
export interface ContributorEntry {
/**
* Contributor identifier, encoded per `format`.
*/
identifier: string
format: ContributorIdentifierFormat
/**
* Contributor's share of the file's commits, in `[0, 1]`.
*/
share: number
/**
* Days since the contributor's last commit to the file.
*/
stale_days: number
/**
* Contributor's commits touching the file in the window.
*/
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-output/src/health_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)
}
/**
* Scope metadata for the hotspot analysis.
*/
export interface HotspotSummary {
/**
* Start of the churn window, as passed to `git log --since`.
*/
since: string
/**
* Minimum commit count for a file to qualify as a hotspot.
*/
min_commits: number
/**
* Files with churn data in the window.
*/
files_analyzed: number
/**
* Files excluded by test-path and ignore filters.
*/
files_excluded: number
/**
* True when the repository is a shallow clone, so churn counts are
* truncated.
*/
shallow_clone: boolean
/**
* Provenance of the instant every churn and staleness number was measured
* against. Absent only when a caller assembled a summary without one.
*/
clock?: (ClockProvenance | null)
}
/**
* The instant a run measured commit ages and staleness against.
*
* A consumer reading `weighted_commits`, `stale_days`, or anything derived
* from them needs to know whether re-running over the same commit yields the
* same number. The human report says so in a warning that `--quiet` removes,
* which left the JSON consumer, who cannot see stderr at all, with no way to
* find out.
*/
export interface ClockProvenance {
source: ClockSource
/**
* The reference epoch itself, in unix seconds. Pass it back as
* `FALLOW_CLOCK_EPOCH` to reproduce this run's churn-derived numbers.
*/
epoch_secs: number
/**
* False only for `wall_clock`, where the numbers drift between runs.
*/
reproducible: 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[]
/**
* Whether an autonomous agent may act on this report (mirrors
* the cloud runtime-context contract). `false` when the capture
* carries no usable runtime evidence (no tracked functions); then
* `actionability_verdict` is `insufficient_evidence` and
* `actionability_reason` explains. F4: a non-action floor, never a gate on a
* positive verdict.
*/
actionable: boolean
/**
* Why the report is non-actionable; `null` when `actionable` is true.
*/
actionability_reason?: (string | null)
/**
* First-class non-action verdict (`insufficient_evidence`) when not
* actionable; `null` otherwise. Mirrors the cloud runtime-context `verdict`;
* named distinctly from the report-context `verdict` above to avoid a
* collision.
*/
actionability_verdict?: (string | null)
provenance: RuntimeCoverageProvenance
}
/**
* 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
}
/**
* One per-function runtime-coverage finding in `runtime_coverage.findings`.
*/
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[]
/**
* The discriminator inputs that produced this verdict (#321), emitted so an
* agent can reproduce it and see the confidence cap. `None` for findings
* not built from the merge pipeline (e.g. baseline round-trips). Omitted
* from JSON when absent.
*/
discriminators?: (RuntimeCoverageDiscriminators | null)
}
/**
* 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
/**
* `true` when the function is unreachable in the production module graph
* but still referenced from a file that production mode excludes (test,
* spec, story, fixture, or benchmark). Such a function is not dead code:
* removing it breaks the referencing test. `false` when the production
* graph was compared against the full tree and no such reference exists.
* `null` when the report was produced without a production filter, or by
* a surface that carries no second reachability answer.
*/
test_only_reference?: (boolean | null)
/**
* `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
/**
* Human-readable action description.
*/
description: string
/**
* Whether fallow can apply this action automatically.
*/
auto_fixable: boolean
}
/**
* Discriminator inputs that PRODUCED a finding's verdict,
* emitted alongside the verdict so an agent can reproduce it and see the
* minimum-observation confidence cap instead of re-deriving them from scratch.
* F4: these make the EXISTING Fallow-owned discriminators legible; they are not
* a new or external signal and gate nothing. Pairs with `evidence.static_status`
* (the static half of the discriminator set).
*/
export interface RuntimeCoverageDiscriminators {
/**
* Three-state runtime tracking: `called` (invocations > 0), `never_called`
* (V8 tracked it, invocations == 0), or `untracked` (V8 never saw it). The
* ONLY signal that can issue a deletion verdict.
*/
tracking_state: string
/**
* `invocations / trace_count` for this function; `null` when untracked (no
* invocation count). The per-function value behind the low-traffic split.
*/
invocation_ratio?: (number | null)
/**
* Active/low_traffic split ratio in effect (CLI default 0.001). A tracked
* function whose `invocation_ratio` is below this reads `low_traffic`, else
* `active`.
*/
low_traffic_threshold: number
/**
* Total observed invocations across all functions (the `invocation_ratio`
* denominator), echoed per finding so the verdict is self-contained.
*/
trace_count: number
/**
* High-confidence verdict floor (CLI default 5000). When `trace_count` is
* below it, confidence is capped regardless of the per-function signal.
*/
min_observation_volume: number
/**
* `trace_count >= min_observation_volume`: whether the dump cleared the
* confidence floor. `false` means this verdict's confidence is capped.
*/
meets_observation_volume: boolean
}
/**
* One hot function in `runtime_coverage.hot_paths`, ranked by invocations.
*/
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[]
/**
* Per-call cost inputs and the speed-work score for this hot function.
* Omitted when the hot path has no `stable_id` or no static counterpart
* in this checkout.
*/
optimization_target?: (RuntimeCoverageOptimizationTarget | null)
}
/**
* Speed-work inputs for one hot function: how often it runs and how much
* work each call does. `importance` ranks the risk of a change; this block
* ranks where speed work gives the largest gain.
*/
export interface RuntimeCoverageOptimizationTarget {
/**
* `invocations` multiplied by the per-call cost that `cost_basis` names.
* Uncapped integer. Compare it only between hot paths with the same
* `cost_basis`: sort by `cost_basis` first, then by `cost_score`
* descending. On the `cognitive` basis the per-call cost is at least 1.
*/
cost_score: number
cost_basis: RuntimeCoverageCostBasis
/**
* Static cognitive complexity of the function. A static proxy for the
* work per call, not a measurement.
*/
cognitive: number
/**
* Static cyclomatic complexity of the function.
*/
cyclomatic: number
/**
* Number of lines in the function body.
*/
line_count: number
/**
* Peak executions of one block inside the function per call, from V8
* block coverage. `1.0` means no block ran more than once per call. A
* loop body that runs 3 times per call gives `3.0`. Calls to other
* functions do not change the value.
* Omitted when the coverage input has no block counts for the function.
*/
inner_iterations_per_call?: (number | null)
}
/**
* One blast-radius entry in `runtime_coverage.blast_radius`: how far a
* change to the function would ripple.
*/
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
}
/**
* One production-importance entry in `runtime_coverage.importance`, scoring
* how much a function matters in production.
*/
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
}
/**
* Non-fatal diagnostic emitted while merging runtime coverage.
*/
export interface RuntimeCoverageMessage {
/**
* Stable machine-readable warning code.
*/
code: string
/**
* Human-readable warning message.
*/
message: string
}
/**
* Provenance of a runtime-coverage report, mirroring
* the cloud runtime-context `provenance` block so the local-capture and cloud
* surfaces present one portable shape. F4: provenance is context only; it never
* gates a verdict or confidence.
*/
export interface RuntimeCoverageProvenance {
data_source: RuntimeCoverageDataSource
/**
* `true` / `false` / `unknown`. Always `unknown` for a local capture: the
* local path has no deployment-origin signal (the cloud may resolve it).
*/
is_production: string
/**
* Age in whole days of the most recent evidence; `0` for a fresh local
* capture, `null` when no runtime data is present.
*/
freshness_days?: (number | null)
/**
* `functions_untracked / (functions_tracked + functions_untracked)`, in
* `[0, 1]`. High ratios mark a thin / partial capture.
*/
untracked_ratio: number
/**
* Fraction of resolution-attempted functions whose position could not be
* mapped to source, in `[0, 1]`. `0` for a local capture (positions resolve
* natively or via the sidecar).
*/
unresolved_ratio: number
/**
* Whether `freshness_days` exceeds `stale_after_days`.
*/
stale: boolean
/**
* The documented staleness cutoff (days), echoed so the rule travels in-band.
*/
stale_after_days: number
}
/**
* Combined coverage, runtime, complexity, and change-scope verdicts.
*/
export interface CoverageIntelligenceReport {
schema_version: CoverageIntelligenceSchemaVersion
verdict: CoverageIntelligenceVerdict
summary: CoverageIntelligenceSummary
/**
* Combined findings, one per matched unit.
*/
findings: CoverageIntelligenceFinding[]
}
/**
* Aggregate metadata for coverage-intelligence output.
*/
export interface CoverageIntelligenceSummary {
/**
* Total combined findings.
*/
findings: number
/**
* Findings with the risky-change verdict.
*/
risky_changes: number
/**
* Findings with the high-confidence-delete verdict.
*/
high_confidence_deletes: number
/**
* Findings with the review-required verdict.
*/
review_required: number
/**
* Findings with the refactor-carefully verdict.
*/
refactor_carefully: number
/**
* Candidate joins dropped because the cross-surface match was ambiguous.
*/
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
/**
* Ordered evidence signals behind the verdict.
*/
signals: CoverageIntelligenceSignal[]
recommendation: CoverageIntelligenceRecommendation
confidence: CoverageIntelligenceConfidence
/**
* IDs of related findings from other fallow surfaces.
*/
related_ids?: string[]
evidence: CoverageIntelligenceEvidence
/**
* Machine-actionable follow-up actions.
*/
actions: CoverageIntelligenceAction[]
}
/**
* Compact evidence values that led to a recommendation.
*/
export interface CoverageIntelligenceEvidence {
/**
* Test coverage percentage (0-100), when coverage data exists.
*/
coverage_pct?: (number | null)
/**
* CRAP score, when complexity and coverage both exist.
*/
crap?: (number | null)
/**
* Runtime-coverage verdict label, e.g. `hot` or `cold`.
*/
runtime_verdict?: (string | null)
/**
* Observed runtime invocation count.
*/
invocations?: (number | null)
/**
* Static usage status label, e.g. `unused`.
*/
static_status?: (string | null)
/**
* Static test-coverage status label, e.g. `no-test-path`.
*/
test_coverage?: (string | null)
/**
* True when the unit is inside the current change scope; omitted when
* false.
*/
changed?: boolean
/**
* Ownership-drift state label, when ownership analysis ran.
*/
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
/**
* Human-readable action description.
*/
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 {
/**
* File path relative to the project root.
*/
path: string
/**
* Function name, or a synthesized name for anonymous functions.
*/
name: string
/**
* 1-based line the function starts on.
*/
line: number
/**
* Lines of code in the function body.
*/
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-output/src/health_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-output/src/health_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)
/**
* Formula used for the stored score; absent on legacy snapshots.
* A score delta is emitted only when this matches the current score formula.
*/
score_formula_version?: (number | 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"` or `"dead_file_pct"`.
*/
name: string
/**
* Human-readable label, e.g. `"Health Score"` or `"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. `"%"`, `""`, or `"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`
* 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
}
/**
* Framework-specific health detector coverage surfaced for agent consumers.
*/
export interface FrameworkHealthDiagnostics {
/**
* Detected framework IDs, sorted and deduplicated.
*/
detected_frameworks: string[]
/**
* Detector coverage for the detected frameworks.
*/
detectors: FrameworkHealthDetector[]
}
/**
* Status for one framework-specific health detector.
*/
export interface FrameworkHealthDetector {
/**
* Rule or detector ID, matching fallow's stable rule names where possible.
*/
id: string
/**
* Framework ID that made this detector relevant.
*/
framework: string
status: FrameworkHealthDetectorStatus
/**
* Stable reason code for non-active statuses.
*/
reason?: (string | null)
}
/**
* 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[]
/**
* CVA / shadcn variant class strings that repeat the same normalized class
* block in several variant values. Kept separate from CSS declaration-block
* duplication because the source is JS config, not parsed CSS rules.
*/
cva_duplicate_variant_blocks?: CvaDuplicateVariantBlock[]
/**
* CVA / shadcn variant class strings that hardcode a Tailwind arbitrary
* value even though an existing token has the same or nearest comparable
* value. Advisory: variants often encode product semantics, so agents
* should verify intent before replacing.
*/
cva_variant_token_drifts?: CvaVariantTokenDrift[]
/**
* 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[]
/**
* Located raw CSS declaration values that bypass token surfaces (`var()`,
* `token()`, `theme()`) on scale-sensitive axes such as color, font-size,
* line-height, radius, and shadow. Conservative candidates: a raw value can
* be intentional, but introduced raw values are useful audit feedback.
*/
raw_style_values?: RawStyleValue[]
/**
* 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[]
/**
* Tailwind v4 theme tokens whose comparable values are close to another
* token in the same theme dictionary. These are opt-in `--css-deep`
* candidates because they need whole-project token context.
*/
near_duplicate_theme_tokens?: NearDuplicateThemeToken[]
/**
* CSS-in-JS design tokens whose comparable values are close to another
* token from the same project. Covers StyleX, vanilla-extract, PandaCSS,
* styled-components, and Emotion token definitions. These are opt-in
* `--css-deep` candidates because they need whole-project token context.
*/
near_duplicate_css_in_js_tokens?: NearDuplicateThemeToken[]
/**
* A location-aware reverse index of design-token consumers. Tailwind v4
* entries cover `@theme` tokens consumed through `var()` reads, `@apply`
* bodies, or generated utilities. CSS-in-JS entries cover supported StyleX,
* vanilla-extract, PandaCSS, styled-components, and Emotion definitions and
* their member or call consumers. Every entry includes the defining site,
* located consumer samples, and the full `consumer_count` as a static lower
* bound. Tailwind entries use the same gated candidate set as
* `unused_theme_tokens`; CSS-in-JS entries require supported direct imports.
* Partial-scope runs omit the index. Sorted by token and empty when no
* eligible token definitions are found. A zero count is evidence for
* investigation, not deletion proof.
*/
token_consumers?: TokenConsumers[]
/**
* 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 in `files`).
*/
export interface CssAnalyticsSummary {
/**
* Stylesheets analyzed: standard `.css` files, Vue/Svelte SFC `<style>`
* blocks, and (dep-gated) CSS-in-JS, both the tagged-template form and the
* object form (`style({...})` / `stylex.create({...})` / `css({...})`). SCSS
* is skipped. Note: flat atomic object CSS-in-JS (StyleX/Panda) is counted
* here and contributes to these aggregates, but has no notable rules, so its
* files never appear in the per-file `files` list.
*/
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
/**
* Preprocessor stylesheets (`.scss`, `.sass`, `.less`) seen by the styling
* scan. These are parsed textually for local candidates, not compiled.
*/
preprocessor_stylesheets: number
/**
* True when project-wide class reachability was skipped because
* preprocessor stylesheets outnumber plain CSS, making generated classes
* invisible without a Sass/Less compiler.
*/
preprocessor_reachability_abstained: boolean
/**
* Located raw CSS declaration values that bypass token surfaces on
* scale-sensitive axes. Located in `raw_style_values`.
*/
raw_style_values: 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
/**
* Tailwind v4 theme tokens whose comparable values are close to another
* token in the same theme dictionary. Located in
* `near_duplicate_theme_tokens`.
*/
near_duplicate_theme_tokens: number
/**
* CSS-in-JS design tokens whose comparable values are close to another
* token from the same project. Located in
* `near_duplicate_css_in_js_tokens`.
*/
near_duplicate_css_in_js_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 duplicated CVA / shadcn variant class block.
*/
export interface CvaDuplicateVariantBlock {
/**
* Normalized class block shared by several variant values.
*/
value: string
/**
* Number of variant values with this class block.
*/
occurrence_count: number
/**
* First locations of the duplicate values, sorted by path and line.
*/
occurrences: CssBlockOccurrence[]
/**
* Read-only guidance step(s), so consumers can iterate `actions`
* uniformly across every candidate type.
*/
actions: CssCandidateAction[]
}
/**
* A CVA / shadcn variant class value that can reuse an existing styling token.
*/
export interface CvaVariantTokenDrift {
/**
* Tailwind arbitrary-value utility inside the variant class string.
*/
class_token: string
/**
* Normalized value inside the arbitrary utility.
*/
value: string
/**
* Full normalized variant class block containing the token.
*/
variant_classes: string
/**
* Project-root-relative, forward-slash path to the variant definition.
*/
path: string
/**
* 1-based line of the variant class string.
*/
line: number
nearest_token: NearestStylingToken
/**
* Read-only guidance step(s), so consumers can iterate `actions`
* uniformly across every candidate type.
*/
actions: CssCandidateAction[]
}
/**
* A styling token candidate that can replace or explain a finding.
*/
export interface NearestStylingToken {
/**
* Token name, e.g. `--color-brand`.
*/
name: string
/**
* Normalized token value.
*/
value: string
/**
* Project-root-relative, forward-slash definition path.
*/
path: string
/**
* 1-based definition line.
*/
line: number
/**
* Distance from the finding value. Lower is closer; units depend on the
* comparable token namespace.
*/
distance: 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[]
}
/**
* A located raw CSS declaration value on a scale-sensitive styling axis.
*/
export interface RawStyleValue {
/**
* Value axis, e.g. `color`, `font-size`, `line-height`, `radius`, or `shadow`.
*/
axis: string
/**
* CSS property where the raw value appears.
*/
property: string
/**
* Rendered declaration value.
*/
value: string
/**
* Project-root-relative, forward-slash path to the stylesheet.
*/
path: string
/**
* 1-based line of the containing style rule.
*/
line: number
/**
* Concrete token with the same or nearest comparable value, when resolved.
*/
nearest_token?: (NearestStylingToken | null)
/**
* Read-only guidance step(s). Never auto-fixable.
*/
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 Tailwind v4 `@theme` token that appears to duplicate an existing token by
* value. Emitted conservatively for comparable token namespaces, with the
* nearest existing token named so an agent has a concrete reuse target.
*/
export interface NearDuplicateThemeToken {
/**
* The full custom property as authored, including the `--` prefix.
*/
token: string
/**
* The normalized authored token value.
*/
value: string
/**
* Project-root-relative, forward-slash path to the token definition.
*/
path: string
/**
* 1-based line of the token definition inside the `@theme` block.
*/
line: number
nearest_token: NearestStylingToken
/**
* Read-only guidance step(s) before replacing the token reference.
*/
actions: CssCandidateAction[]
}
/**
* A location-aware reverse index of where one design token is consumed, so an
* agent editing the token can see its blast radius before changing or removing
* it. Covers TWO token origins. The always-available discriminator is the `token`
* SHAPE: a Tailwind token is the `--`-prefixed custom property (`--color-brand`),
* a CSS-in-JS token is a dotted access path with no `--` prefix
* (`vars.color.primary`). The per-consumer `kind` also discriminates origin, but
* only when `consumer_count > 0` (a `consumer_count: 0` entry has an empty
* `consumers` array and thus no `kind`), so branch on the `token` prefix for the
* zero-consumer case. The two origins:
*
* - Tailwind v4 `@theme` tokens (kinds `theme-var` / `css-var` / `utility` /
* `apply`), built from the same gated candidate set as `unused_theme_tokens`
* (v4 + non-plugin + non-published + whole-scope), so a `consumer_count: 0`
* corroborates the `unused_theme_tokens` "nothing consumes this" finding.
* - CSS-in-JS tokens (kind `js-member` / `js-call`) from StyleX `defineVars` /
* `unstable_defineVarsNested`, vanilla-extract `createTheme` family definitions,
* and PandaCSS `defineTokens`, consumed via same-file or cross-module member
* access, StyleX theme-group calls, or PandaCSS `token('...')` calls. NOTE:
* CSS-in-JS has NO corroborating dead-token finding (there is no
* `unused_theme_tokens` analogue), so a CSS-in-JS `consumer_count: 0` is a weaker
* signal than the Tailwind case (and unresolved dynamic imports or computed
* accesses are not counted).
*
* This is DESCRIPTIVE context (a blast-radius lookup), not a finding, so it
* deliberately carries no `actions` array (unlike the cleanup-candidate types in
* this module). `consumer_count` is always a STATIC lower bound (a computed class
* name like `bg-${color}`, or a CSS-in-JS access through an unresolved alias
* import, is not counted).
*/
export interface TokenConsumers {
/**
* The token identity. For a Tailwind `@theme` token this is the full custom
* property as authored, INCLUDING the `--` prefix (`--color-brand`). For a
* CSS-in-JS token (kind `js-member` / `js-call`) this is the binding-qualified dotted
* access path, NO `--` prefix (`vars.color.primary`), matching how consumers
* read it. The presence of the `--` prefix distinguishes the two origins.
*/
token: string
/**
* For a Tailwind token, the v4 theme namespace (`color`, `radius`,
* `font-weight`, ...). For a CSS-in-JS token (kind `js-member` / `js-call`), the defining
* export BINDING the token set is accessed through (`vars`), which identifies
* the token set, NOT a semantic group. (The field is thus overloaded by
* origin; branch on `consumers[].kind` or the `token` shape.)
*/
namespace: string
/**
* Project-root-relative, forward-slash path to the declaring stylesheet
* (Tailwind) or the JS/TS token-definition file (CSS-in-JS).
*/
definition_path: string
/**
* 1-based line of the token's definition (inside the `@theme` block for
* Tailwind; the token key inside the `defineVars`/`createTheme` object for
* CSS-in-JS).
*/
definition_line: number
/**
* The FULL number of consumer locations found, a STATIC LOWER BOUND: a
* computed class name (`bg-${color}`), unresolved import, dynamic token
* structure, or computed CSS-in-JS access is not counted. This is the
* aggregate over every consumer, computed BEFORE
* [`consumers`](Self::consumers) is capped to a sample.
*/
consumer_count: number
/**
* A capped, deterministically-sorted sample of consumer locations (at most
* [`TOKEN_CONSUMER_SAMPLE_CAP`]). The full count lives in
* [`consumer_count`](Self::consumer_count); use this list to jump to
* representative consumers, not to enumerate every one.
*/
consumers: TokenConsumerLocation[]
}
/**
* Where one Tailwind or CSS-in-JS design token is consumed, and through which
* surface. One entry in a [`TokenConsumers::consumers`] sample.
*/
export interface TokenConsumerLocation {
/**
* Project-root-relative, forward-slash path to the consuming file.
*/
path: string
/**
* 1-based line of the consuming reference in that file.
*/
line: number
kind: ConsumerKind
}
/**
* 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
}
/**
* Project-level styling-health score: a SECOND health axis computed purely from
* the structural CSS analytics (`CssAnalyticsReport`), orthogonal to the JS/TS
* code-health [`HealthScore`]. Surfaced only alongside the `--css` analytics, so
* a plain `fallow health` run is byte-unchanged. The code score and grade stay
* untouched: styling health is additive, never folded into the code score.
*
* Like [`HealthScore`], the score starts at 100 and subtracts capped per-category
* penalties; the grade reuses the shared [`letter_grade`] thresholds verbatim
* (A>=85, B>=70, C>=55, D>=40, F<40), so the two axes are read on one scale.
*/
export interface StylingHealth {
/**
* Styling formula version; see [`STYLING_HEALTH_FORMULA_VERSION`].
*/
formula_version: number
/**
* Styling-health score in `[0, 100]`; higher is healthier.
*/
score: number
/**
* Letter grade from the shared [`letter_grade`] thresholds.
*/
grade: string
penalties: StylingHealthPenalties
confidence: StylingHealthConfidence
/**
* Human-readable reason the grade is low-confidence: either the declaration
* and stylesheet counts a thin grade was computed from, or that structure is
* not assessable for compile-time-atomic CSS-in-JS. `None` when confidence is
* `High`. Prose, not a stable machine field: gate on `confidence`, not on
* this string.
*/
confidence_reason?: (string | null)
}
/**
* Per-category penalty breakdown for the styling-health score. Each field is the
* number of points subtracted from a starting 100 for one CSS signal family,
* already capped at its category ceiling. A `0.0` field means "the signal was
* evaluated and clean"; the whole struct is only ever built when CSS analytics
* were produced, so there is no "missing pipeline" ambiguity to model with
* `Option` here (the parent `StylingHealth` is itself `Option` on the report).
*/
export interface StylingHealthPenalties {
/**
* Copy-paste declaration blocks (`duplicate_declaration_blocks`), scaled by
* total removable declarations. Capped at 20pt.
*/
duplication: number
/**
* Dead styling surface, two independently-normalized terms summed and capped
* at 20pt: (a) unused `@theme` tokens as a share of the total `@theme` token
* population (size-independent, so a declaration-sparse Tailwind project is
* not penalized for a few dead tokens); plus (b) the other dead entities
* (unreferenced classes, unused `@property`/`@layer` at-rules, dead
* `@font-face` families) as a share of `total_declarations`.
*/
dead_surface: number
/**
* Broken references: markup classes one edit from a defined class
* (`unresolved_class_references`) and animations referencing a `@keyframes`
* defined nowhere (`undefined_keyframes`). Capped at 15pt.
*/
broken_references: number
/**
* Design-token erosion: mixed `font-size` units (`font_size_unit_mix`),
* Tailwind arbitrary-value bypasses (`tailwind_arbitrary_values`), and
* distinct HARDCODED `box-shadow`/`border-radius`/`line-height` values above
* per-axis baselines (the v3 value-sprawl drift sub-term; `var(--*)`-
* referenced values are not counted). Capped at 10pt.
*/
token_erosion: number
/**
* Structural smells from the summary aggregates: `!important` density and
* deep style-rule nesting. Capped at 10pt.
*/
structural: number
}
/**
* One advisory STYLING FINDING: the graduation of a descriptive css candidate
* into a first-class, severity-aware, suppressible finding surfaced in
* `fallow audit`. The styling domain's OWN finding type (not borrowed into the
* dead-code `AnalysisResults`, and not glued in the CLI). `code` is the kebab
* IssueKind code (e.g. `css-token-drift`), so severity / inline suppression /
* SARIF / MCP all resolve via the shared `issue_meta` contract through
* `IssueKind::parse(code)`. One `Vec<StylingFinding>` carries every styling
* family; the `code` discriminates.
*/
export interface StylingFinding {
/**
* The kebab IssueKind code, e.g. `css-token-drift`.
*/
code: string
/**
* The specific sub-kind within the family, e.g. `tailwind-arbitrary-value`.
*/
sub_kind: string
/**
* Workspace-relative path of the finding.
*/
path: string
/**
* 1-based line of the finding.
*/
line: number
/**
* The offending literal value, e.g. `w-[13px]`.
*/
value: string
effective_severity: StylingFindingSeverity
/**
* Optional static lower-bound blast radius. For a dead design token this is
* `0`; for other styling findings it is omitted.
*/
blast_radius?: (number | null)
/**
* Confidence hint for agents and review UIs. Structural findings are high,
* reachability findings are low because dynamic consumers may exist.
*/
confidence?: (StylingFindingConfidence | null)
/**
* Suggested handling posture for agents. This is advisory data, fallow
* still never applies styling changes automatically.
*/
agent_disposition?: (StylingAgentDisposition | null)
/**
* Concrete reuse target for token-drift findings, when one can be resolved.
*/
nearest_token?: (NearestStylingToken | null)
/**
* One concise machine-readable edit hint for agent consumers.
*/
fix_hint?: (string | null)
/**
* Suggested next steps (verify / suppress; never an auto-fix).
*/
actions: CssCandidateAction[]
/**
* Audit-mode flag indicating whether the finding is new versus the base snapshot.
*/
introduced?: (boolean | null)
}
/**
* 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 {
/**
* Issue-type identifier, e.g. `unused-export`.
*/
id: string
/**
* Human-readable issue-type name.
*/
name: string
/**
* One-line description of what the issue type reports.
*/
summary: string
/**
* Why the issue matters.
*/
rationale: string
/**
* Illustrative code example of the issue.
*/
example: string
/**
* How to resolve findings of this type.
*/
how_to_fix: string
/**
* Public documentation URL for the issue type.
*/
docs: string
}
/**
* Envelope emitted by `fallow inspect --format json`.
*/
export interface InspectOutput {
target: InspectTargetDescriptor
identity: InspectIdentity
evidence: InspectEvidence
/**
* Non-fatal problems encountered while gathering evidence.
*/
warnings: string[]
/**
* `_meta` block with type-aware backend info, when applicable.
*/
_meta?: (Meta | null)
}
/**
* Graph identity facts for a file target. `Value`-typed fields carry the
* graph's verdict when analysis ran and are `null` when it did not.
*/
export interface InspectFileIdentity {
/**
* File path relative to the analysed root.
*/
file: string
/**
* Whether the module graph can reach the file from an entry point.
*/
is_reachable?: (boolean | null)
/**
* Whether the file is itself an entry point.
*/
is_entry_point?: (boolean | null)
/**
* Number of exports the file declares.
*/
export_count?: (number | null)
/**
* Number of modules the file imports.
*/
import_count?: (number | null)
/**
* Number of modules that import the file.
*/
imported_by_count?: (number | null)
}
/**
* Graph identity facts for a symbol target. `Value`-typed fields carry the
* graph's verdict when analysis ran and are `null` when it did not.
*/
export interface InspectSymbolIdentity {
/**
* File path relative to the analysed root.
*/
file: string
/**
* Name of the inspected export.
*/
export_name: string
/**
* Whether the containing file is reachable from an entry point.
*/
file_reachable?: (boolean | null)
/**
* Whether the containing file is itself an entry point.
*/
is_entry_point?: (boolean | null)
/**
* Whether the export has any recorded consumer.
*/
is_used?: (boolean | null)
/**
* Explanation of the usage verdict, when the graph recorded one.
*/
reason?: (string | null)
}
/**
* `evidence` block of [`InspectOutput`]: one section per analysis.
*/
export interface InspectEvidence {
trace_file: InspectEvidenceSection
/**
* Export-level usage trace; present only for symbol targets.
*/
trace_export?: (InspectEvidenceSection | null)
dead_code: InspectEvidenceSection
duplication: InspectEvidenceSection
complexity: InspectEvidenceSection
security: InspectEvidenceSection
impact_closure: InspectEvidenceSection
/**
* OPT-IN target-level git churn. Omitted unless historical evidence was
* explicitly requested by the caller.
*/
churn?: (InspectEvidenceSection | null)
/**
* OPT-IN symbol-level call chain. Present only when `--symbol-chain` was
* requested AND the target is a SYMBOL (best-effort, syntactic, OFF the
* ranked path). `None` (omitted) by default: symbol-level chains are
* best-effort and not part of the trusted ranked evidence.
*/
symbol_chain?: (InspectEvidenceSection | null)
/**
* Checker-backed declaration, alias, re-export, and use-site evidence.
* Present only for a symbol target in type-aware mode.
*/
semantic_trace?: (InspectEvidenceSection | null)
/**
* Package-public TypeScript surface and private-type reachability.
* Present only for a symbol target in type-aware mode.
*/
api_surface?: (InspectEvidenceSection | null)
/**
* Exact-symbol consumers and transitive affected files.
* Present only for a symbol target in type-aware mode.
*/
symbol_impact?: (InspectEvidenceSection | null)
/**
* Test entry points reachable from the exact symbol, with provenance.
* Present only for a symbol target in type-aware mode.
*/
targeted_tests?: (InspectEvidenceSection | null)
}
/**
* One evidence section: status, the scope the evidence covers, and either a
* data payload or a message explaining its absence.
*/
export interface InspectEvidenceSection {
status: InspectSectionStatus
scope: InspectEvidenceScope
/**
* Explanation for `error` / `unavailable` sections.
*/
message?: (string | null)
/**
* Section payload; present when the analysis ran.
*/
data?: {
[k: string]: unknown
}
}
/**
* Result of tracing an export: why it is considered used or unused.
*/
export interface ExportTrace {
/**
* The file containing the export.
*/
file: string
/**
* The export name being traced.
*/
export_name: string
namespace?: SemanticNamespace
/**
* Whether the file is reachable from an entry point.
*/
file_reachable: boolean
/**
* Whether the file is an entry point.
*/
is_entry_point: boolean
/**
* Whether the export is considered used.
*/
is_used: boolean
/**
* Files that reference this export directly.
*/
direct_references: ExportReference[]
/**
* Reachable direct references grouped by namespace. This is additive to
* `namespace` and `direct_references`, whose winning-lane meaning remains
* unchanged for backwards compatibility.
*/
direct_references_by_namespace?: NamespacedExportReferences[]
/**
* A star-export collision that makes the traced name ambiguous. When
* present, `is_used: false` is an abstention rather than an unused-code
* verdict.
*/
star_export_ambiguity?: (StarExportAmbiguity | null)
/**
* Re-export chains that pass through this export.
*/
re_export_chains: ReExportChain[]
/**
* Human-readable reason summary.
*/
reason: string
/**
* Exact checker-backed references when type-aware tracing is enabled.
*/
semantic?: (SemanticSymbolTrace | null)
}
/**
* A direct reference to an export.
*/
export interface ExportReference {
/**
* File that contains the reference.
*/
from_file: string
/**
* Reference kind, such as named import, default import, or re-export.
*/
kind: string
}
/**
* Direct references that credit one namespace of an export binding.
*/
export interface NamespacedExportReferences {
namespace: SemanticNamespace
/**
* Number of reachable references in this namespace.
*/
reference_count: number
/**
* Reachable references in deterministic graph order.
*/
references: ExportReference[]
}
/**
* The `export *` collision that keeps a name from being exported.
*/
export interface StarExportAmbiguity {
/**
* Files that each declare a colliding declaration under the traced name
* (project-root-relative), sorted. These are the origins to fix: keep one,
* rename or explicitly re-export the rest.
*/
sources: string[]
/**
* The namespaces the collision occurs in, type before value. A name can
* collide in type space, value space, or both.
*/
namespaces: SemanticNamespace[]
}
/**
* A re-export chain showing how an export is propagated.
*/
export interface ReExportChain {
/**
* The barrel file that re-exports this symbol.
*/
barrel_file: string
/**
* The name it is re-exported as.
*/
exported_as: string
/**
* Number of references on the barrel's re-exported symbol.
*/
reference_count: number
}
/**
* Result of tracing a class / enum / store MEMBER: the `--trace FILE:NAME`
* fallback when `NAME` is not a top-level export but a member declared on one
* (issue #1744). The trace runs on the module graph only, so it reports the
* OWNING export's reachability and usage (the gating precondition for
* member-level crediting) plus a pointer to the right `--unused-*-members`
* command, rather than per-member crediting provenance.
*/
export interface ClassMemberTrace {
/**
* The file containing the member.
*/
file: string
/**
* The member name being traced.
*/
member_name: string
/**
* The member kind: `class-method`, `class-property`, `enum-member`,
* `store-member`, or `namespace-member`.
*/
member_kind: string
/**
* The export that declares this member (the class / enum / store name).
*/
owner_export: string
owner_namespace?: SemanticNamespace
/**
* Whether the owning export is considered used.
*/
owner_is_used: boolean
/**
* Whether the file is reachable from an entry point.
*/
owner_file_reachable: boolean
/**
* Whether the file is an entry point.
*/
owner_is_entry_point: boolean
/**
* Files that reference the owning export directly.
*/
owner_direct_references: ExportReference[]
/**
* Re-export chains through which the owning export is reachable. Populated
* so a machine consumer can tell "used via a barrel" (empty direct refs but
* non-empty chains) from "genuinely unreferenced".
*/
owner_re_export_chains: ReExportChain[]
/**
* Human-readable reason summary plus the follow-up command to inspect the
* member finding.
*/
reason: string
/**
* Exact checker-backed member references when type-aware tracing is enabled.
*/
semantic?: (SemanticSymbolTrace | null)
}
/**
* Result of tracing all edges for a file.
*/
export interface FileTrace {
/**
* The traced file.
*/
file: string
/**
* Whether this file is reachable from entry points.
*/
is_reachable: boolean
/**
* Whether this file is an entry point.
*/
is_entry_point: boolean
/**
* Exports declared by this file.
*/
exports: TracedExport[]
/**
* Files that this file imports from.
*/
imports_from: string[]
/**
* Files that import from this file.
*/
imported_by: string[]
/**
* Re-exports declared by this file.
*/
re_exports: TracedReExport[]
/**
* The configs that make this file an entry point through Module
* Federation `exposes`, one per config. Absent when no Federation config
* exposes the file (issue #2796).
*/
sources?: TraceSource[]
}
/**
* An export with usage information.
*/
export interface TracedExport {
/**
* Export name.
*/
name: string
/**
* Whether the export is type-only.
*/
is_type_only: boolean
/**
* Number of references to this export.
*/
reference_count: number
/**
* Files that reference this export.
*/
referenced_by: ExportReference[]
}
/**
* A re-export with source information.
*/
export interface TracedReExport {
/**
* Source file being re-exported from.
*/
source_file: string
/**
* Imported symbol name.
*/
imported_name: string
/**
* Exported symbol name.
*/
exported_name: string
}
/**
* A config that names a traced file or a traced dependency, and the key that
* names it.
*/
export interface TraceSource {
/**
* The mechanism that names the file or the dependency:
* `module-federation`. The set is open.
*/
kind: string
/**
* The plugin that read the config, as it labels itself:
* `module-federation` for a standalone `module-federation.config.*`,
* or the bundler plugin (`webpack`, `rspack`, `rsbuild`, `vite`,
* `nextjs`) that read the same options inline from its own config.
*/
plugin: string
/**
* The file that names the file or the dependency, relative to the
* project root: the config file, or the source file of a Module
* Federation runtime call.
*/
config: string
/**
* The config key or the runtime function that names the file or the
* dependency: `exposes` for an exposed file, `remotes` for a remote
* alias, and `registerRemotes`, `loadRemote`, `init` or `createInstance`
* for a remote that a runtime call names. The set is open.
*/
key: string
}
/**
* Result of tracing a dependency: where it is used.
*/
export interface DependencyTrace {
/**
* The dependency name being traced.
*/
package_name: string
/**
* Files that import this dependency.
*/
imported_by: string[]
/**
* Files that import this dependency with type-only imports.
*/
type_only_imported_by: string[]
/**
* Whether the dependency is invoked from package.json scripts, CI configs
* or git hooks.
*/
used_in_scripts: boolean
/**
* Whether the dependency is used at all: imported, invoked from scripts,
* or listed as a peer by a used package (`peer_of`).
*/
is_used: boolean
/**
* Total import count.
*/
import_count: number
/**
* Used packages that list this dependency in their installed
* `peerDependencies`, required or optional, sorted by name. The
* unused-dependency check credits such a peer, because the package that
* lists it loads it at runtime. Absent when no used package lists it.
*/
peer_of?: string[]
/**
* The configs that declare this name as a Module Federation remote alias
* under `remotes`, one per config. A remote alias is provided by a
* remote container at runtime, not by an npm package. Absent when no
* Federation config declares the name (issue #2796).
*/
sources?: TraceSource[]
/**
* Why the unused devDependency check credits the dependency as tooling
* when no file imports it and no script, CI workflow or git hook runs it.
* When present, `is_used` is `true`. Absent otherwise.
*/
tooling_credit?: (ToolingCredit | null)
/**
* The manifests that the unused-dependency check flags for this name,
* relative to the project root and sorted. The check reads each
* declaring manifest on its own. An import credits the nearest manifest
* that installs the package, so a name that one workspace uses can still
* be unused in the root manifest or in another workspace. Absent when no
* manifest is flagged.
*/
unused_in?: string[]
}
/**
* Why the unused devDependency check counts a dependency as used tooling
* although no source file imports it.
*/
export interface ToolingCredit {
/**
* The evidence: `plugin-config` when the plugin that declares the
* dependency found its own config file, `plugin-reference` when a
* package.json script, a CI workflow or a git hook runs one of that
* plugin's packages, `ambient-types` for a type package that declares
* globals, `types-target` when the project declares or imports the
* package that a `@types/` package types, `types-config` when a config
* file, such as a tsconfig `types` entry, names the type package,
* `known-tooling` for a library from the tooling catalogue,
* `known-tooling-config` when a command-line tool from the catalogue has
* its own config file, and `own-peer` when the same manifest lists the
* devDependency in `peerDependencies`. The set is open.
*/
reason: string
/**
* The plugin that declares the dependency as tooling.
*/
plugin?: (string | null)
/**
* The config file found, relative to the project root, for
* `plugin-config` and `known-tooling-config`. A `package.json` path when
* the config is a package.json key. For `own-peer`, the manifest that
* lists the dependency.
*/
config?: (string | null)
/**
* The package that a script, CI workflow or git hook runs, for
* `plugin-reference`, or the package that a `@types/` package types,
* for `types-target`.
*/
reference?: (string | null)
}
/**
* Result of tracing a clone: all groups containing the code at a source
* location or addressed by a stable clone fingerprint.
*/
export interface CloneTrace {
/**
* File passed to the trace request, root-relative when a group matches.
*/
file: string
/**
* 1-based line passed to the trace request or representative group line.
*/
line: number
/**
* The matched clone instance, if one exists.
*/
matched_instance?: (CloneInstance | null)
/**
* Clone groups matched by the trace request.
*/
clone_groups: TracedCloneGroup[]
}
/**
* One clone group returned from a clone trace request.
*/
export interface TracedCloneGroup {
/**
* Stable content fingerprint, usually `dup:<8hex>` and widened on rare
* report collisions.
*/
fingerprint: string
/**
* Number of tokens in the duplicated block.
*/
token_count: number
/**
* Number of lines in the duplicated block.
*/
line_count: number
/**
* Maximum directory-tree or same-file line distance between instances.
*/
spread: number
/**
* Lowest all-pairs similarity for a near-miss clone group.
*/
similarity?: number
/**
* Root-relative clone instances in this group.
*/
instances: CloneInstance[]
suggestion: RefactoringSuggestion
/**
* Best-effort name for the extracted function. Advisory only.
*/
suggested_name?: (string | null)
}
/**
* Result of computing the impact closure for a single file as the seed.
*/
export interface ImpactClosureTrace {
/**
* The seed file, root-relative.
*/
seed: string
/**
* Root-relative paths transitively affected by the seed.
*/
affected_not_shown: string[]
/**
* Coordination gaps between the seed and consumers.
*/
coordination_gap: ImpactClosureGap[]
}
/**
* One coordination-gap entry in an [`ImpactClosureTrace`].
*/
export interface ImpactClosureGap {
/**
* Root-relative path of the consumer module.
*/
consumer_file: string
/**
* Exported symbol names the consumer references.
*/
consumed_symbols: string[]
/**
* Scope note for the syntactic trace.
*/
note: string
}
/**
* Result of asking how one module reaches another: the shortest import path.
*
* `reachable` is the only field that separates "no route exists" from "the
* route is empty because both ends are the same module". Both report
* `hops: 0`, so a consumer must read `reachable`, never the hop count.
*/
export interface ImportPathTrace {
schema_version: ImportPathTraceSchemaVersion
/**
* The module the walk started from, root-relative.
*/
from: string
/**
* The module the walk was looking for, root-relative.
*/
to: string
/**
* Whether `to` is reachable from `from` by following import edges.
*/
reachable: boolean
/**
* Number of import edges on the reported route. `0` both when the two ends
* are the same module and when there is no route at all.
*/
hops: number
/**
* The route, in import order. Empty whenever `hops` is `0`.
*/
path: ImportPathHop[]
/**
* Human-readable summary of the outcome.
*/
reason: string
}
/**
* One import edge on an [`ImportPathTrace`].
*/
export interface ImportPathHop {
/**
* The importing module, root-relative.
*/
from: string
/**
* The imported module, root-relative.
*/
to: string
/**
* Whether every symbol on this edge is type-only, so the hop is erased at
* build time. Type-only hops are reported, never skipped: an `import type`
* chain is a real compile-time coupling.
*/
type_only: boolean
/**
* Whether the edge carries a runtime value but no static one: the target
* loads only on demand (`import()`, a lazy glob or template pattern) or
* on another thread (a worker URL, a worker loader request,
* `child_process.fork`). False for a
* static hop and for a type-only hop.
*/
dynamic: boolean
/**
* 1-based line in `from` of the imported binding that creates this edge:
* the first value-carrying symbol on the import, or the first symbol when
* every symbol is type-only. On a multi-line import that is the binding's
* own line, not the `import` keyword's. Absent when the edge carries no
* span or the source could not be read.
*/
import_line?: (number | null)
}
/**
* The result of a symbol-level call-chain trace. Its own surface (`kind:
* "trace"`), NOT folded into the ranked brief.
*/
export interface SymbolChainTrace {
/**
* The file containing the traced symbol (project-root-relative).
*/
file: string
/**
* The traced symbol name.
*/
symbol: string
/**
* Whether the symbol's defining export was found in the graph. When
* `false`, the chains are empty and `reason` explains why.
*/
symbol_found: boolean
/**
* The chain depth applied to both directions.
*/
depth: number
/**
* Whether this trace is best-effort (always `true`: symbol-level chains are
* labeled best-effort, syntactic per ADR-001).
*/
best_effort: boolean
/**
* Caller chain hops (UP). Present only when `--callers` was requested.
*/
callers?: (ChainHop[] | null)
/**
* Callee chain hops (DOWN) resolved to an import-symbol edge. Present only
* when `--callees` was requested.
*/
callees?: (ChainHop[] | null)
/**
* Callees referenced at a call site in the symbol's module that the
* syntactic walk could NOT resolve to an import-symbol edge (locals,
* globals, dynamic dispatch, re-bound callees). Reported, never dropped.
* Present only when `--callees` was requested.
*/
unresolved_callees?: (UnresolvedCallee[] | null)
/**
* Set when the name is unresolvable because two different `export *`
* sources of this file supply it. `symbol_found` is `false` in that case
* for the same reason it is false for an unknown name (the file exports
* nothing under it per ECMA-262 ResolveExport), so this field is the only
* thing that separates a barrel mistake from a typo.
*/
star_export_ambiguity?: (StarExportAmbiguity | null)
/**
* A human-readable summary of the trace outcome.
*/
reason: string
}
/**
* One hop in a caller / callee chain.
*/
export interface ChainHop {
/**
* The file at this hop (project-root-relative). For a caller hop this is
* the importing module; for a callee hop the imported module.
*/
file: string
/**
* The symbol name as imported across the edge (`default`, `*` for namespace,
* the imported name otherwise).
*/
imported_as: string
/**
* The local binding name in the file at this hop.
*/
local_name: string
/**
* Whether the import edge is type-only (`import type { ... }`).
*/
type_only: boolean
/**
* The hop's depth (1 = direct caller/callee of the symbol).
*/
depth: number
}
/**
* A callee referenced at a call site that did not resolve to an import-symbol
* edge. Surfaced so a missing callee is never silently dropped.
*/
export interface UnresolvedCallee {
/**
* The callee path as written at the call site (e.g. `helper`,
* `obj.method`).
*/
callee: string
reason: UnresolvedReason
}
/**
* Result of resolving a runtime stack trace against the project graph.
*/
export interface ErrorTrace {
schema_version: ErrorTraceSchemaVersion
/**
* Where the trace was read from: `stdin`, or the path as the caller wrote
* it.
*/
source: string
/**
* The first non-blank input line that preceded any recognised frame,
* verbatim. Conventionally the error type and message, but it is reported
* as read and NOT parsed into parts. Absent when the input began with a
* frame or was empty.
*/
header?: (string | null)
/**
* Every recognised frame, in input order. Nothing is filtered out: a
* dependency or runtime-internal frame stays in the array with its origin
* recorded, so hop numbering matches the trace the caller pasted.
*/
frames: ErrorTraceFrame[]
counts: ErrorTraceCounts
/**
* Human-readable summary of the outcome.
*/
reason: string
}
/**
* One frame read from the input stack trace.
*/
export interface ErrorTraceFrame {
/**
* 0-based position in the input trace, so a caller can quote a frame back
* even after filtering the array.
*/
index: number
/**
* The input line this frame was read from, trimmed of surrounding
* whitespace and otherwise verbatim.
*/
raw: string
/**
* The frame's function identifier as written by the runtime, with the
* `async` and `new` markers stripped and recorded separately. Absent for a
* frame the runtime emitted without one.
*/
function?: (string | null)
/**
* Whether the runtime marked this frame as a constructor call (`new X`).
*/
is_constructor?: boolean
/**
* Whether the runtime marked this frame as an async call.
*/
is_async?: boolean
/**
* The frame's file as read from the trace, with any `file://` or
* `http(s)://` wrapper removed and separators forward-slashed. Reported as
* read: it is NOT rewritten to the module path it matched, so a caller can
* see what its runtime actually said. Absent for a frame with no location.
*/
file?: (string | null)
/**
* 1-based line from the frame's location, when the runtime supplied one.
*/
line?: (number | null)
/**
* 1-based column from the frame's location, when the runtime supplied one.
*/
column?: (number | null)
origin: FrameOrigin
resolution: FrameResolution
/**
* Every definition the identifier could name, in deterministic order.
* Exactly one entry when `resolution` is `resolved`, more than one when it
* is `ambiguous`, and empty otherwise.
*/
candidates: ErrorTraceCandidate[]
/**
* How many further candidates a presentation cap withheld.
* `candidates.len() + candidates_omitted` is the true match count, so an
* `ambiguous` frame never understates how ambiguous it is.
*/
candidates_omitted: number
/**
* Set when this frame's own line disagrees with the definition its
* identifier matched: some OTHER definition in the same file is declared
* closer above the line the runtime reported.
*
* The look-up matches on the identifier alone, so a `resolved` frame is
* resolved however far its line sits from the match. That is honest about
* the question asked and silent about a question a reader would ask next,
* which is why the disagreement is published instead of left to be
* noticed. The frame is NOT reclassified: the graph does know a
* definition under this identifier, and only the caller can say whether
* the runtime ran that one or a same-named definition elsewhere.
* `reason` names the declaration that sits closer. Only set on a
* `resolved` frame that carried a line and matched a definition whose own
* line could be read.
*/
line_mismatch?: boolean
/**
* Human-readable statement of what happened to this frame.
*/
reason: string
}
/**
* One definition a frame's identifier could name.
*/
export interface ErrorTraceCandidate {
/**
* Root-relative file declaring the definition.
*/
file: string
/**
* The exported name. For a member match this is the owning export.
*/
symbol: string
/**
* The member name, when the frame's identifier named a member of
* `symbol` rather than `symbol` itself. Absent for a direct export match.
*/
member?: (string | null)
/**
* What kind of definition this is: `export`, or the member kind
* (`class-method`, `class-property`, `enum-member`, `store-member`,
* `namespace-member`).
*/
kind: string
/**
* 1-based declaration line of the definition's identifier. Absent when the
* source file could not be read; never guessed.
*/
line?: (number | null)
}
/**
* Per-outcome totals for an [`ErrorTrace`].
*
* `resolved + ambiguous + not_found + not_attempted == frames`, and
* `in_project + node_modules + out_of_corpus == frames`. Both identities hold
* on every run, so a caller can verify that nothing was dropped.
*/
export interface ErrorTraceCounts {
/**
* Frames reported in `frames`.
*/
frames: number
/**
* Frames a cap withheld from `frames`. Their outcomes are NOT counted in
* the fields below, which describe the reported frames only.
*/
frames_omitted: number
/**
* Frames whose file resolved to project source.
*/
in_project: number
/**
* Frames whose file lives under an installed dependency tree.
*/
node_modules: number
/**
* Frames outside the analysed corpus, including frames with no location.
*/
out_of_corpus: number
/**
* Frames that matched exactly one definition.
*/
resolved: number
/**
* Frames that matched more than one definition.
*/
ambiguous: number
/**
* Frames the graph was asked about and could not name.
*/
not_found: number
/**
* Frames the graph was never asked about.
*/
not_attempted: number
/**
* Non-blank input lines that were neither recognised as a frame nor taken
* as `header`. A trace that is entirely unrecognised reports zero frames
* and a non-zero count here, rather than looking like an empty trace.
*/
unparsed_lines: number
}
/**
* 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 {
/**
* Summary comment body markdown.
*/
body: string
/**
* Stable fingerprint of the summary body, used for sticky updates.
*/
fingerprint: string
}
/**
* GitHub pull-request review comment.
*/
export interface GitHubReviewComment {
/**
* File path relative to the repository root.
*/
path: string
/**
* 1-based line on the new side of the diff.
*/
line: number
side: GitHubReviewSide
/**
* Comment body markdown, fingerprint marker included.
*/
body: string
/**
* Stable finding fingerprint used for comment reconciliation.
*/
fingerprint: string
/**
* The fingerprint that an older Fallow release wrote into the
* `fallow-fingerprint:v2:` marker of this comment, when it is different
* from `fingerprint`. For one release, an existing comment whose marker
* holds this value is the same comment. Omitted when equal.
*/
legacy_fingerprint?: (string | null)
/**
* True when the body was cut to fit the provider size limit; omitted
* when false.
*/
truncated?: boolean
}
/**
* GitLab merge-request discussion comment.
*/
export interface GitLabReviewComment {
/**
* Comment body markdown, fingerprint marker included.
*/
body: string
position: GitLabReviewPosition
/**
* Stable finding fingerprint used for comment reconciliation.
*/
fingerprint: string
/**
* The fingerprint that an older Fallow release wrote into the
* `fallow-fingerprint:v2:` marker of this comment, when it is different
* from `fingerprint`. For one release, an existing comment whose marker
* holds this value is the same comment. Omitted when equal.
*/
legacy_fingerprint?: (string | null)
/**
* True when the body was cut to fit the provider size limit; omitted
* when false.
*/
truncated?: boolean
}
/**
* `position` block inside [`GitLabReviewComment`]. Mirrors the GitLab
* merge-request discussion-position API.
*/
export interface GitLabReviewPosition {
/**
* Merge-base SHA of the MR diff; absent when refs were not supplied.
*/
base_sha?: (string | null)
/**
* First commit SHA of the MR diff; absent when refs were not supplied.
*/
start_sha?: (string | null)
/**
* Head commit SHA of the MR diff; absent when refs were not supplied.
*/
head_sha?: (string | null)
position_type: GitLabReviewPositionType
/**
* Pre-rename path when the diff renamed the file, else the same as
* `new_path`.
*/
old_path: string
/**
* File path on the new side of the diff.
*/
new_path: string
/**
* 1-based line on the new side of the diff.
*/
new_line: number
}
/**
* `meta` block inside [`ReviewEnvelopeOutput`].
*/
export interface ReviewEnvelopeMeta {
schema: ReviewEnvelopeSchema
provider: ReviewProvider
check_conclusion?: (ReviewCheckConclusion | null)
review_id?: (ReviewId | 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
/**
* PR / MR reference that was reconciled, when one was resolved.
*/
target?: (string | null)
/**
* True when no provider mutations were performed.
*/
dry_run: boolean
/**
* Inline comments in the review envelope being reconciled.
*/
comments: number
/**
* Distinct fingerprints in the current run's findings.
*/
current_fingerprints: number
/**
* Distinct fingerprints with an open Fallow lifecycle, including
* provider-resolved discussions not yet closed by a Fallow marker.
*/
existing_fingerprints: number
/**
* Fingerprints present in the current run but not yet commented.
*/
new_fingerprints: number
/**
* Fingerprints with open Fallow lifecycles whose findings no longer exist.
*/
stale_fingerprints: number
/**
* The new fingerprints themselves.
*/
new: string[]
/**
* The stale fingerprints themselves.
*/
stale: string[]
/**
* Non-fatal provider API warning encountered during reconciliation.
*/
provider_warning?: (string | null)
/**
* Resolution replies posted to stale comment threads.
*/
resolution_comments_posted: number
/**
* Provider discussion threads resolved or re-closed.
*/
threads_resolved: number
/**
* Remediation guidance when the apply loop stopped before finishing.
*/
apply_hint?: (string | null)
/**
* Errors encountered while applying provider mutations.
*/
apply_errors: string[]
/**
* Fingerprints whose provider mutation failed.
*/
failed_fingerprints?: string[]
/**
* Fingerprints left unprocessed after a failure aborted the apply loop.
*/
unapplied_fingerprints?: string[]
}
/**
* `fallow coverage setup --json` envelope.
*/
export interface CoverageSetupOutput {
schema_version: CoverageSetupSchemaVersion
framework_detected: CoverageSetupFramework
/**
* Package manager detected from lockfiles, when one was found.
*/
package_manager?: (CoverageSetupPackageManager | null)
/**
* Runtimes the instrumentation must cover at the project root.
*/
runtime_targets: CoverageSetupRuntimeTarget[]
/**
* Per-member setup guidance for workspace projects.
*/
members: CoverageSetupMember[]
/**
* Coverage config that was written to disk, when setup wrote one.
*/
config_written?: {
[k: string]: unknown
}
/**
* Shell commands the user should run to complete setup.
*/
commands: string[]
/**
* Files the user must edit by hand, with reasons.
*/
files_to_edit: CoverageSetupFileToEdit[]
/**
* Ready-to-paste code snippets for the files to edit.
*/
snippets: CoverageSetupSnippet[]
/**
* Dockerfile additions needed for containerized capture, when relevant.
*/
dockerfile_snippet?: (string | null)
/**
* Ordered human-readable follow-up instructions.
*/
next_steps: string[]
/**
* Non-fatal problems encountered during detection.
*/
warnings: string[]
/**
* `_meta` block with docs and field definitions, when requested.
*/
_meta?: {
[k: string]: unknown
}
}
/**
* Per-workspace-member setup guidance inside [`CoverageSetupOutput::members`].
*/
export interface CoverageSetupMember {
/**
* Package name of the workspace member.
*/
name: string
/**
* Member path relative to the workspace root.
*/
path: string
framework_detected: CoverageSetupFramework
/**
* Package manager detected for this member, when one was found.
*/
package_manager?: (CoverageSetupPackageManager | null)
/**
* Runtimes the instrumentation must cover for this member.
*/
runtime_targets: CoverageSetupRuntimeTarget[]
/**
* Files the user must edit by hand, with reasons.
*/
files_to_edit: CoverageSetupFileToEdit[]
/**
* Ready-to-paste code snippets for the files to edit.
*/
snippets: CoverageSetupSnippet[]
/**
* Dockerfile additions needed for containerized capture, when relevant.
*/
dockerfile_snippet?: (string | null)
/**
* Non-fatal problems encountered during detection.
*/
warnings: string[]
}
/**
* One manual edit the user must make to wire up coverage capture.
*/
export interface CoverageSetupFileToEdit {
/**
* File path relative to the project root.
*/
path: string
/**
* Why the file needs editing.
*/
reason: string
}
/**
* Ready-to-paste code snippet accompanying a file edit.
*/
export interface CoverageSetupSnippet {
/**
* Short description of what the snippet does.
*/
label: string
/**
* File path the snippet belongs in.
*/
path: string
/**
* The snippet source text.
*/
content: string
}
/**
* Envelope emitted by `fallow coverage analyze --format json`.
*/
export interface CoverageAnalyzeOutput {
schema_version: CoverageAnalyzeSchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
runtime_coverage: RuntimeCoverageReport
/**
* `_meta` block with docs and metric definitions, when `--explain` was
* passed.
*/
_meta?: (Meta | null)
}
/**
* Envelope emitted by `fallow list --boundaries --format json`. Surfaces
* the architecture boundary zones, rules, and the user's pre-expansion
* `autoDiscover` logical groups so consumers can render grouping intent that
* expansion would otherwise flatten out of `zones[]`.
*/
export interface ListBoundariesOutput {
boundaries: BoundariesListing
}
/**
* `boundaries` block carried by [`ListBoundariesOutput`].
*/
export interface BoundariesListing {
/**
* Whether the project configures architecture boundaries at all.
*/
configured: boolean
/**
* Number of entries in `zones`.
*/
zone_count: number
/**
* Boundary zones after preset and `autoDiscover` expansion.
*/
zones: BoundariesListZone[]
/**
* Number of entries in `rules`.
*/
rule_count: number
/**
* Import rules operating on expanded zone names.
*/
rules: BoundariesListRule[]
/**
* Number of entries in `logical_groups`.
*/
logical_group_count: number
/**
* Pre-expansion `autoDiscover` logical groups.
*/
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 {
/**
* Zone name referenced by rules.
*/
name: string
/**
* Glob patterns that classify files into the zone.
*/
patterns: string[]
/**
* Number of analyzable files the zone matched.
*/
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 {
/**
* Zone the rule constrains imports from.
*/
from: string
/**
* Zone names the `from` zone may import.
*/
allow: string[]
}
/**
* A pre-expansion `autoDiscover` logical group surfaced for observability.
* Captured during expansion 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 {
/**
* User-authored parent zone name.
*/
name: string
/**
* Child zone names produced by discovery.
*/
children: string[]
/**
* Authored `autoDiscover` paths.
*/
auto_discover: string[]
status: LogicalGroupStatus
/**
* Index of the authored entry in the pre-expansion `zones[]` config.
*/
source_zone_index: number
/**
* Files matched across the group's zones.
*/
file_count: number
/**
* User's pre-expansion rule keyed on the parent name, when authored.
*/
authored_rule?: (AuthoredRule | null)
/**
* Zone that keeps the parent's own patterns when the parent kept any.
*/
fallback_zone?: (string | null)
/**
* `zones[]` indices of duplicate parents merged into this group.
*/
merged_from?: (number[] | null)
/**
* Authored parent `root`, when one was declared.
*/
original_zone_root?: (string | null)
/**
* Per-child indices into the pre-expansion `zones[]` config.
*/
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. Paths are project-root-relative and use forward slashes,
* like `workspaces[].path` and like the `workspace_diagnostics[]` array on
* the analysis envelopes. 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: HealthSchemaVersion
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
/**
* The sections that this run produced, in a fixed order. A renderer
* reads it to tell an empty section from a section that the run did not
* produce: `findings` is the complexity list only when `complexity` is
* in this array. The value set is OPEN (see [`HealthSection`]). Absent
* in an envelope from a fallow version before this member, and on a
* report that no health run built.
*/
sections?: (HealthSection[] | null)
/**
* 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.
*
* Set whenever the run measured churn, which needs readable git history;
* `--hotspots` adds the per-file [`hotspots`](Self::hotspots) listing
* beside it rather than gating this summary.
*/
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)
/**
* Optional framework-specific detector coverage. Present only when the
* health run already needed the dead-code analysis output.
*/
framework_health?: (FrameworkHealthDiagnostics | null)
/**
* Structural CSS analytics (specificity hotspots, `!important` density,
* over-complex selectors, deep nesting). Present only with `--css`.
*/
css_analytics?: (CssAnalyticsReport | null)
/**
* Styling-health score and letter grade: a SECOND health axis derived from
* the CSS analytics (the design-system axis), orthogonal to the JS/TS code
* `health_score`. Present only with `--css` (the same condition as
* `css_analytics`), so a plain `fallow health` run is byte-unchanged. The
* code score is never affected by this field.
*/
styling_health?: (StylingHealth | null)
/**
* Advisory STYLING FINDINGS: the graduation of the descriptive css
* candidates into first-class, severity-aware, suppressible findings
* surfaced in `fallow audit`. Verdict-neutral by default (the rule defaults
* to `warn`); the styling domain's OWN findings collection, not the dead-code
* `AnalysisResults`. Present only with `--css`; empty is skipped so a plain
* run is byte-unchanged.
*/
styling_findings?: StylingFinding[]
/**
* Grouping mode when `--group-by` was passed.
*/
grouped_by?: (GroupByMode | null)
/**
* Per-bucket recomputed metrics; present only in grouped output.
*/
groups?: (HealthGroup[] | null)
/**
* The `--group` selector patterns, as given, when the run kept only some
* groups. A group that is not in `groups` was filtered out by this
* selector. The project-level sections are not filtered.
*/
group_filter?: (string[] | null)
/**
* The verdict of every gate this run evaluated, keyed by name. The CLI
* always emits it, with the command's default exit rule in it also when
* no flag armed a gate, so a CI integration reads the verdict instead of
* guessing from a process status it usually cannot see. A gate fails the
* build when `status` is `fail` AND `enforced` is true. The typed
* programmatic API runs no CLI gate and leaves it absent. See
* [`crate::GateOutcomes`].
*/
gate_outcomes?: (GateOutcomes | null)
/**
* Every narrowing or shaping request this run RECEIVED, keyed by name,
* absent when it was asked for nothing. An entry whose `status` is not
* `applied` means the run could not do what it was asked and reported
* something WIDER instead, so what follows is a valid report of a scope
* nobody requested. Honoured requests are published too, with
* `status: "applied"`, so an absent object means "nothing was asked for",
* never "nothing failed". See [`crate::RequestOutcomes`].
*/
request_outcomes?: (RequestOutcomes | null)
/**
* `_meta` block with metric definitions, when `--explain` was passed.
*/
_meta?: (Meta | null)
/**
* Workspace-discovery, source-discovery, and analysis-stage diagnostics
* for the run. See `CheckOutput::workspace_diagnostics` for the full
* contract: the kinds each stage records, project-root-relative paths,
* omitted when empty.
*/
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
* project-level health summary.
*
* A group carries a per-file list (`findings`, `file_scores`, `hotspots`,
* `large_functions`, `targets`) only when the project report shows the same
* list. A `--score` run keeps the score and the counts of each group and
* omits the lists.
*/
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 of the group, not the un-truncated total.
*/
functions_above_threshold: number
/**
* Number of critical-severity findings in this group, after the baseline
* filter and before `--top`. The project `summary.severity_critical_count`
* counts before the baseline filter, so with `--baseline` the group
* counts can add up to less.
*/
severity_critical_count: number
/**
* Number of high-severity findings in this group, after the baseline
* filter and before `--top`. The project `summary.severity_high_count`
* counts before the baseline filter, so with `--baseline` the group
* counts can add up to less.
*/
severity_high_count: number
/**
* Number of moderate-severity findings in this group, after the baseline
* filter and before `--top`. The project `summary.severity_moderate_count`
* counts before the baseline filter, so with `--baseline` the group
* counts can add up to less.
*/
severity_moderate_count: number
/**
* Number of ranked hotspot entries in this group, before `--top`. This
* is the length of the group's ranked hotspot list. It is not
* `vital_signs.hotspot_count`, which counts only the files with a
* hotspot score of 50 or more and feeds the health score.
*/
hotspot_count: 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. The duplication penalty counts each
* clone group with two or more instances in total and one or more in
* this group, and counts only the lines of the instances in this group.
* A clone that spans two groups thus lowers the score of each group.
* `dupes --group-by` assigns each clone group to one owner instead.
*/
health_score?: (HealthScore | null)
/**
* Trend of this group against the same group in the baseline snapshot.
* Present only when `--trend` or `--trend-from` was requested and the
* baseline holds this group with the same `grouped_by` mode.
*/
trend?: (HealthTrend | null)
/**
* Why `trend` is present or absent. Present only when a trend was
* requested and a baseline snapshot was loaded.
*/
trend_status?: (GroupTrendStatus | null)
/**
* Findings restricted to files in this group. Each entry is the typed
* [`HealthFinding`] wrapper around a
* `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` 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`
* 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`
* suppresses inline hints.
*/
actions_meta?: (HealthActionsMeta | null)
}
/**
* Envelope emitted by `fallow dupes --format json`.
*
* `Report` and `Group` are generic so the envelope can live in
* `fallow-output` while duplication report wrappers and grouped output
* internals continue to migrate out of CLI/API-specific crates.
*/
export interface DupesOutput {
schema_version: DupesSchemaVersion
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
/**
* Number of clone groups carried in `clone_groups[]`.
*/
clone_groups_shown: number
/**
* Number of scoped-corpus clone groups withheld from `clone_groups[]` by
* a presentation cap such as `--top`. `0` on an untruncated run, so
* `clone_groups_shown + clone_groups_omitted == stats.clone_groups`
* always holds and `stats` keeps describing the whole measured corpus.
*/
clone_groups_omitted: number
/**
* Number of clone families carried in `clone_families[]`.
*/
clone_families_shown: number
/**
* Number of scoped-corpus clone families withheld from `clone_families[]`
* by a presentation cap such as `--top`, which rebuilds the families from
* the groups that survived the cap. `0` on an untruncated run, so
* `clone_families_shown + clone_families_omitted == stats.clone_families`
* always holds and `stats` keeps describing the whole measured corpus.
*/
clone_families_omitted: number
/**
* Grouping mode when `--group-by` was passed.
*/
grouped_by?: (GroupByMode | null)
/**
* Total finding count across all groups; present only in grouped output.
*/
total_issues?: (number | null)
/**
* Grouped findings; present only in grouped output.
*/
groups?: (DuplicationGroup[] | null)
/**
* This run's view of the loaded baseline, present only in baseline runs.
* Carries the staleness counts, the advisory verdict and `gate_trips`, the
* same boolean `--fail-on-stale-baseline` exits on, so a CI integration
* reads one field instead of restating the rule. Read `change_scoped`
* before dividing `matched_entries` by `baseline_entries`: a narrowed run
* can report `matched_entries: 0` on a healthy baseline.
*/
baseline_staleness?: (BaselineStaleness | null)
/**
* The verdict of every gate this run armed, keyed by name, absent when
* it armed none. `dupes` has no default exit rule: a run with no armed
* gate always exits 0, so an absent object means that the run passed.
* `--fail-on-issues` and `--ci` arm `duplication-findings`, which fails
* on any clone group. A gate fails the build when `status` is `fail` AND
* `enforced` is true. See [`crate::GateOutcomes`].
*/
gate_outcomes?: (GateOutcomes | null)
/**
* Every narrowing or shaping request this run RECEIVED, keyed by name,
* absent when it was asked for nothing. An entry whose `status` is not
* `applied` means the run could not do what it was asked and reported
* something WIDER instead, so what follows is a valid report of a scope
* nobody requested. Honoured requests are published too, with
* `status: "applied"`, so an absent object means "nothing was asked for",
* never "nothing failed". See [`crate::RequestOutcomes`].
*/
request_outcomes?: (RequestOutcomes | null)
/**
* Applied package Git refs, omitted outside package-baseline runs.
*/
package_baselines?: PackageBaselineStatus[]
/**
* `_meta` block with metric / rule definitions, emitted when `--explain`
* is passed (always present in MCP responses).
*/
_meta?: (Meta | null)
/**
* Workspace-discovery and source-discovery diagnostics for the run
* (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. A standalone `fallow dupes` run has no
* dead-code analyze pass, so the two analysis-stage kinds never appear
* here.
*/
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
/**
* Number of tokens in the clone group.
*/
token_count: number
/**
* Number of source lines in the clone group.
*/
line_count: number
/**
* Lowest all-pairs similarity for a near-miss clone group.
*/
similarity?: 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
/**
* Maximum directory-tree or same-file line distance between instances.
*/
spread: number
/**
* 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.
*
* Omitted from JSON when the caller asked for a location-only payload
* (`fallow dupes --no-fragments`, and the MCP `find_dupes` default). The
* five location fields above address the same text, so a consumer that
* wants the source reads it from the file.
*/
fragment?: string
/**
* Whether the file path is a symlink, or lies under a symlinked
* directory, inside the project root. Omitted when `false`.
*
* A clone with a symlinked instance can be the same file under two
* paths, not copied code. `duplicates.ignoreSymlinks` (or
* `fallow dupes --ignore-symlinks`) removes these instances.
*/
is_symlink?: boolean
/**
* 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: CheckSchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
grouped_by: GroupByMode
/**
* Total findings across all groups.
*/
total_issues: number
/**
* One bucket per resolver key.
*/
groups: CheckGroupedEntry[]
/**
* `true` when the `unused-load-data-key` detector abstained for the whole
* project. The abstain has no file, so it is on the root and not in a
* group. An empty `unused_load_data_keys` with this flag set does not
* mean the project is clean: the rule could not run safely. Serialized
* only when `true`, like the flat `CheckOutput` field.
*/
unused_load_data_keys_global_abstain?: boolean
/**
* This run's view of the loaded baseline, present only in baseline runs.
* Carries the staleness counts, the advisory verdict and `gate_trips`, the
* same boolean `--fail-on-stale-baseline` exits on, so a CI integration
* reads one field instead of restating the rule. Read `change_scoped`
* before dividing `matched_entries` by `baseline_entries`: a narrowed run
* can report `matched_entries: 0` on a healthy baseline.
*/
baseline_staleness?: (BaselineStaleness | null)
/**
* The answer to `--finding-id`, present only when the run received one
* or more `--finding-id` values. The report then holds only the
* requested findings. Read `missing` as resolved only when `conclusive`
* is true; a scope, a baseline or a filter can hide a finding that still
* exists. See [`crate::FindingIdQuery`].
*/
finding_id_query?: (FindingIdQuery | null)
/**
* The verdict of every gate this run evaluated, keyed by name. The CLI
* always emits it, with the command's default exit rule in it also when
* no flag armed a gate, so a CI integration reads the verdict instead of
* guessing from a process status it usually cannot see. A gate fails the
* build when `status` is `fail` AND `enforced` is true. The typed
* programmatic API runs no CLI gate and leaves it absent. See
* [`crate::GateOutcomes`].
*/
gate_outcomes?: (GateOutcomes | null)
/**
* Every narrowing or shaping request this run RECEIVED, keyed by name,
* absent when it was asked for nothing. An entry whose `status` is not
* `applied` means the run could not do what it was asked and reported
* something WIDER instead, so what follows is a valid report of a scope
* nobody requested. Honoured requests are published too, with
* `status: "applied"`, so an absent object means "nothing was asked for",
* never "nothing failed". See [`crate::RequestOutcomes`].
*/
request_outcomes?: (RequestOutcomes | null)
/**
* Applied package Git refs, omitted outside package-baseline runs.
*/
package_baselines?: PackageBaselineStatus[]
/**
* `_meta` block with docs and rule definitions, when `--explain` was
* passed.
*/
_meta?: (Meta | null)
/**
* Diagnostics collected for the full analysis before issue grouping.
* See [`CheckOutput::workspace_diagnostics`] for the contract.
*/
workspace_diagnostics?: WorkspaceDiagnostic[]
/**
* 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 {
/**
* Resolver key: team name, directory prefix, package name, or section.
*/
key: string
/**
* Owners of a GitLab CODEOWNERS section; present for section grouping.
*/
owners?: (string[] | null)
/**
* Findings in this group.
*/
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[]
/**
* Exports marked `@deprecated` that still have at least one consumer in
* a reachable file. Wrapped in [`DeprecatedExportInUseFinding`]. Opt-in: the
* `deprecated-exports-in-use` rule defaults to `off`.
*/
deprecated_exports_in_use?: DeprecatedExportInUseFinding[]
/**
* 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[]
/**
* devDependencies imported by production (non-test, non-config) source code
* via a runtime/value import; they should be promoted to dependencies.
* The promote-side mirror of [`TestOnlyDependencyFinding`]. Wrapped in
* [`DevDependencyInProductionFinding`].
*/
dev_dependencies_in_production?: DevDependencyInProductionFinding[]
/**
* 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[]
/**
* Dependency cycles between workspace packages, built from resolved
* cross-package imports. Wrapped in [`PackageCycleFinding`] so each
* entry carries a typed `actions` array natively.
*/
package_cycles?: PackageCycleFinding[]
/**
* 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, imports, and catalogue-derived effects 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 package manager catalog sections not referenced by any
* workspace package via the catalog: protocol. Supports
* `pnpm-workspace.yaml` catalogs and Bun root `package.json` catalogs.
* Wrapped in [`UnusedCatalogEntryFinding`] so each entry carries a typed
* `actions` array natively, with per-instance `auto_fixable` derived
* from `hardcoded_consumers` and the catalog source file.
*/
unused_catalog_entries?: UnusedCatalogEntryFinding[]
/**
* Named groups under package manager catalogs sections 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. The package
* manager 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, package.json's
* pnpm.overrides block, npm or Bun's top-level overrides object, or Bun's
* top-level resolutions object,
* whose target package is not declared by any workspace package and is
* not present in pnpm-lock.yaml, package-lock.json, npm-shrinkwrap.json,
* or bun.lock. Default severity is warn because projects without a
* readable lockfile fall back to manifest-only checks; the hint field
* flags those conservative cases. When the only lockfile is bun's binary
* bun.lockb, resolution cannot be read and the check emits nothing.
* Wrapped in [`UnusedDependencyOverrideFinding`].
*/
unused_dependency_overrides?: UnusedDependencyOverrideFinding[]
/**
* Package-manager override or resolution entries whose key or value does
* not parse in the declaration source's grammar (empty key, empty value,
* malformed selector, unbalanced parent matcher). The package manager may
* reject or ignore these at install time. 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`, Svelte 5 `$props()`, and React props
* referenced nowhere in their own component. Wrapped in
* [`UnusedComponentPropFinding`] so each entry carries a typed `actions`
* array natively. Default severity is `warn`.
*/
unused_component_props?: UnusedComponentPropFinding[]
/**
* Used optional component inputs absent from inspected reachable callers. Off by default.
*/
absent_component_props?: AbsentComponentPropFinding[]
/**
* 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[]
/**
* Angular `@Input()` / signal `input()` / `model()` inputs read nowhere in
* their own component (neither the template nor the class body). Wrapped in
* [`UnusedComponentInputFinding`] so each entry carries a typed `actions`
* array natively. Default severity is `warn`.
*/
unused_component_inputs?: UnusedComponentInputFinding[]
/**
* Angular `@Output()` / signal `output()` outputs emitted nowhere in their
* own component (no `this.<output>.emit(...)`). Wrapped in
* [`UnusedComponentOutputFinding`] so each entry carries a typed `actions`
* array natively. Default severity is `warn`.
*/
unused_component_outputs?: UnusedComponentOutputFinding[]
/**
* Svelte components dispatching a custom event via `createEventDispatcher()`
* whose event name is listened to nowhere project-wide (cross-file
* dead-output direction). Wrapped in [`UnusedSvelteEventFinding`] so each
* entry carries a typed `actions` array natively. Default severity is
* `warn`.
*/
unused_svelte_events?: UnusedSvelteEventFinding[]
/**
* 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.
*/
export interface ImpactReport {
schema_version: ImpactReportSchemaVersion
/**
* Whether impact tracking is active for this project.
*/
enabled: boolean
enabled_source: EnabledSource
/**
* Number of recorded runs in the store.
*/
record_count: number
/**
* `_meta` block with docs and field definitions, when `--explain` was
* passed.
*/
_meta?: (Meta | null)
/**
* Timestamp of the earliest recorded run; absent with no records.
*/
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 between the two most recent changed-file 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)
/**
* Recorded gate runs grouped by source, over the same bounded window of
* recorded runs `record_count` reports. A floor, not a lifetime total, and
* absent when no run in that window carries a gate source. Local
* provenance, never an adoption metric.
*/
gate_runs?: (GateRunCounts | null)
/**
* Lifetime count of commit-gate containment events.
*/
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 {
/**
* Sum of the category counts.
*/
total_issues: number
/**
* Dead-code findings.
*/
dead_code: number
/**
* Complexity findings.
*/
complexity: number
/**
* Duplication findings.
*/
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
/**
* Total issues in the earlier run.
*/
previous_total: number
/**
* Total issues in the later run.
*/
current_total: number
}
/**
* Recorded gate runs grouped by the gate that produced them. Local
* provenance only: the store never leaves the machine, so this answers "where
* do my gate runs come from", never "how widely is fallow adopted".
*
* Counted over the recorded runs the store still holds, which is the same
* window `record_count` reports. The store keeps a bounded number of runs and
* drops the oldest, so on a long-lived project these are the shape of recent
* gate activity, not a lifetime total: read them as a floor. Absent when no
* run in that window carries a gate source, which is not the same as "no gate
* ever ran here".
*/
export interface GateRunCounts {
/**
* Runs recorded by the agent gate (`--gate-marker agent`).
*/
agent: number
/**
* Runs recorded by the git pre-commit hook (`--gate-marker pre-commit`).
*/
pre_commit: number
/**
* Runs recorded by a CI gate (`--gate-marker ci`).
*/
ci: number
/**
* Gate runs whose marker this build does not recognise, plus every gate
* run recorded before the store kept its source (store schema 6 and older).
*/
unknown: number
}
/**
* A commit-gate containment event recorded by `fallow impact`.
*/
export interface ContainmentEvent {
/**
* Timestamp when the commit gate blocked the commit.
*/
blocked_at: string
/**
* Timestamp when a later run passed clean.
*/
cleared_at: string
/**
* Abbreviated SHA of the cleared commit, when in a git repo.
*/
git_sha?: (string | null)
blocked_counts: ImpactCounts
}
/**
* A resolved or suppressed finding attribution event.
*/
export interface ResolutionEvent {
/**
* Finding kind that was resolved or suppressed, e.g. `unused-export`.
*/
kind: string
/**
* Root-relative path of the resolved finding.
*/
path: string
/**
* Symbol name, for symbol-level findings.
*/
symbol?: (string | null)
/**
* Abbreviated SHA of the resolving commit, when in a git repo.
*/
git_sha?: (string | null)
/**
* Timestamp the resolution was recorded.
*/
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
/**
* Per-project rows, one for each store with recorded history.
*/
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 {
/**
* Lifetime genuinely-resolved findings across projects.
*/
resolved_total: number
/**
* Lifetime `fallow-ignore` suppressions across projects.
*/
suppressed_total: number
/**
* Lifetime commit-gate containment events across projects.
*/
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 that have recorded at least one full `fallow` run.
*/
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
/**
* The verdict of every gate this run evaluated, keyed by name. The CLI
* always emits it, with the command's default exit rule in it also when
* no flag armed a gate, so a CI integration reads the verdict instead of
* guessing from a process status it usually cannot see. A gate fails the
* build when `status` is `fail` AND `enforced` is true. The typed
* programmatic API runs no CLI gate and leaves it absent. See
* [`crate::GateOutcomes`].
*/
gate_outcomes?: (GateOutcomes | null)
/**
* Every narrowing or shaping request this run RECEIVED, keyed by name,
* absent when it was asked for nothing. An entry whose `status` is not
* `applied` means the run could not do what it was asked and reported
* something WIDER instead, so what follows is a valid report of a scope
* nobody requested. Honoured requests are published too, with
* `status: "applied"`, so an absent object means "nothing was asked for",
* never "nothing failed". See [`crate::RequestOutcomes`].
*/
request_outcomes?: (RequestOutcomes | null)
/**
* 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)
/**
* Diagnostics owned by this security analysis run.
*/
workspace_diagnostics?: WorkspaceDiagnostic[]
/**
* 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)
}
/**
* Per-rule severity context inside [`SecurityOutputConfig::rules`].
*/
export interface SecurityOutputRulesConfig {
security_client_server_leak: SecurityRuleSeverityConfig
security_sink: SecurityRuleSeverityConfig
}
/**
* Configured-versus-effective severity for one security rule.
*/
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 + column. 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["fallowSecurity/v2"]`
* 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 {
/**
* Control patterns observed in files on this import trace. These are
* verification hints, not proof of sink protection or value-level data flow.
*/
controls: SecurityDefensiveControl[]
/**
* Verification question for the consuming agent. It is a prompt, not a
* missing-guard verdict.
*/
verification_prompt: string
}
/**
* Control pattern observed in a file on an attack-surface import trace.
* Its presence does not prove that it executes before the sink or protects
* the same input.
*/
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 {
/**
* File path relative to the analysed root.
*/
path: string
/**
* 1-based line of the skipped call site.
*/
line: number
/**
* 1-based column of the skipped call site.
*/
col: number
reason: SkippedSecurityCalleeReason
expression_kind: SkippedSecurityCalleeExpressionKind
}
/**
* Count of unresolved callees in one file.
*/
export interface SecurityUnresolvedCalleeTopFile {
/**
* File path relative to the analysed root.
*/
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
/**
* The verdict of every gate this run evaluated, keyed by name. The CLI
* always emits it, with the command's default exit rule in it also when
* no flag armed a gate, so a CI integration reads the verdict instead of
* guessing from a process status it usually cannot see. A gate fails the
* build when `status` is `fail` AND `enforced` is true. The typed
* programmatic API runs no CLI gate and leaves it absent. See
* [`crate::GateOutcomes`].
*/
gate_outcomes?: (GateOutcomes | null)
/**
* Every narrowing or shaping request this run RECEIVED, keyed by name,
* absent when it was asked for nothing. An entry whose `status` is not
* `applied` means the run could not do what it was asked and reported
* something WIDER instead, so what follows is a valid report of a scope
* nobody requested. Honoured requests are published too, with
* `status: "applied"`, so an absent object means "nothing was asked for",
* never "nothing failed". See [`crate::RequestOutcomes`].
*/
request_outcomes?: (RequestOutcomes | null)
/**
* Security-specific rule and field metadata, emitted with `--explain`.
*/
_meta?: (Meta | null)
/**
* Gate verdict, present only when `--gate <mode>` was set.
*/
gate?: (SecurityGate | null)
/**
* Diagnostics owned by the full security analysis summarized here.
*/
workspace_diagnostics?: WorkspaceDiagnostic[]
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-severity candidates.
*/
high: number
/**
* Medium-severity candidates.
*/
medium: number
/**
* Low-severity candidates.
*/
low: number
}
/**
* Fixed reachability counters for summary JSON.
*/
export interface SecurityReachabilityCounts {
/**
* Candidates reachable from an entry point.
*/
entry_reachable: number
/**
* Candidates reachable from an untrusted input source.
*/
untrusted_source_reachable: number
/**
* Candidates where taint flows through a call argument.
*/
arg_level: number
/**
* Candidates where taint is only module-level.
*/
module_level: number
/**
* Candidates whose flow crosses a client/server boundary.
*/
crosses_boundary: number
/**
* Candidates backed by a concrete taint source.
*/
source_backed: number
}
/**
* Fixed runtime coverage counters for summary JSON.
*/
export interface SecurityRuntimeStateCounts {
/**
* Candidates on frequently executed runtime paths.
*/
runtime_hot: number
/**
* Candidates on rarely executed runtime paths.
*/
runtime_cold: number
/**
* Candidates on paths never seen executing.
*/
never_executed: number
/**
* Candidates on low-traffic paths.
*/
low_traffic: number
/**
* Candidates in files runtime coverage did not observe.
*/
coverage_unavailable: number
/**
* Candidates whose runtime state could not be classified.
*/
runtime_unknown: number
/**
* Candidates analysed without any runtime coverage data.
*/
not_collected: number
}
/**
* The `fallow security survivors --format json` envelope.
*/
export interface SecuritySurvivorsOutput {
schema_version: SecuritySurvivorsSchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
/**
* Diagnostics preserved from the candidate security report.
*/
workspace_diagnostics?: WorkspaceDiagnostic[]
summary: SecuritySurvivorsSummary
/**
* Verifier-retained candidates keyed by finding id.
*/
survivors: {
[k: string]: SecuritySurvivor
}
/**
* Ambiguous candidates keyed by finding id. These are not dismissed and are
* kept explicit so queues can decide whether to include them.
*/
needs_human_review: {
[k: string]: SecuritySurvivor
}
}
/**
* Aggregate counts for survivor rendering.
*/
export interface SecuritySurvivorsSummary {
/**
* Candidates in the input security report.
*/
candidates: number
/**
* Verifier verdicts supplied.
*/
verdicts: number
/**
* Candidates the verifier retained.
*/
survivors: number
/**
* Candidates the verifier dismissed.
*/
dismissed: number
/**
* Candidates the verifier flagged as ambiguous.
*/
needs_human_review: number
/**
* Candidates without any verdict.
*/
unverdicted: number
}
/**
* One verifier-retained candidate row.
*/
export interface SecuritySurvivor {
/**
* Stable candidate id from `security_findings[].finding_id`.
*/
finding_id: string
verdict: SecurityVerifierVerdictStatus
/**
* Short machine-oriented verdict reason.
*/
reason?: (string | null)
/**
* Longer free-form verdict explanation.
*/
rationale?: (string | null)
/**
* Optional verifier-provided confidence or review priority.
*/
confidence?: (string | null)
/**
* Optional verifier-provided impact statement.
*/
impact?: (string | null)
/**
* Optional verifier-owned remediation direction.
*/
fix_direction?: (string | null)
candidate: SecurityFinding
}
/**
* The `fallow security blind-spots --format json` envelope.
*/
export interface SecurityBlindSpotsOutput {
schema_version: SecurityBlindSpotsSchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
/**
* Diagnostics owned by the security analysis used for this view.
*/
workspace_diagnostics?: WorkspaceDiagnostic[]
summary: SecurityBlindSpotsSummary
/**
* Grouped unresolved callee diagnostics, derived from existing samples.
*/
groups: SecurityBlindSpotGroup[]
}
/**
* Aggregate counts for blind-spot output.
*/
export interface SecurityBlindSpotsSummary {
/**
* Files containing at least one unresolved callee.
*/
unresolved_edge_files: number
/**
* Total unresolved callee sites in the analysis.
*/
unresolved_callee_sites: number
/**
* Callee sites captured in the bounded diagnostic sample.
*/
sampled_callee_sites: number
}
/**
* One actionable blind-spot group.
*/
export interface SecurityBlindSpotGroup {
reason: SkippedSecurityCalleeReason
expression_kind: SkippedSecurityCalleeExpressionKind
/**
* Count in the bounded diagnostic sample.
*/
sampled_count: number
/**
* Top files in this bounded diagnostic sample.
*/
files: SecurityBlindSpotFile[]
/**
* Suggested next action for this group.
*/
suggestion: string
}
/**
* One file inside a blind-spot group.
*/
export interface SecurityBlindSpotFile {
/**
* File path relative to the analysed root.
*/
path: string
/**
* Count in the bounded diagnostic sample.
*/
sampled_count: number
}
/**
* Bare `fallow --format json` envelope.
*/
export interface CombinedOutput {
schema_version: CombinedSchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
/**
* The verdict of every gate this run evaluated, keyed by name. The CLI
* always emits it, with the default exit rule of each section that ran
* (`error-severity-findings`, `health-findings`). Without
* `--fail-on-issues` or `--ci`, the machine formats of the combined run
* exit 0 for findings, so most entries have `enforced: false`. With one of
* these flags, the findings rules and `duplication-threshold` are
* `enforced`, the dupes section adds an enforced `duplication-findings`
* entry, and every format exits 1 when one fails. For the default
* exit rules (`error-severity-findings`,
* `health-findings`), `status` gives the verdict of the human run. An
* advisory entry can report `fail` without a failure of the human run: an
* example is a `stale-baseline` entry that `--fail-on-stale-baseline` did
* not arm. A gate fails the build when `status` is `fail` AND `enforced`
* is true. The typed
* programmatic API leaves it absent. See [`crate::GateOutcomes`].
*/
gate_outcomes?: (GateOutcomes | null)
/**
* Every narrowing or shaping request this run RECEIVED, keyed by name,
* absent when it was asked for nothing. An entry whose `status` is not
* `applied` means the run could not do what it was asked and reported
* something WIDER instead, so what follows is a valid report of a scope
* nobody requested. Honoured requests are published too, with
* `status: "applied"`, so an absent object means "nothing was asked for",
* never "nothing failed". See [`crate::RequestOutcomes`].
*/
request_outcomes?: (RequestOutcomes | null)
/**
* Applied package Git refs of the `check` and `dupes` sections. The map
* does not narrow the `health` section.
*/
package_baselines?: PackageBaselineStatus[]
/**
* Per-section `_meta` blocks, when `--explain` was passed.
*/
_meta?: (CombinedMeta | null)
/**
* Dead-code section of the combined run.
*/
check?: (CheckOutput | null)
/**
* Duplication section of the combined run.
*/
dupes?: (CombinedDupesSection | null)
/**
* Health section of the combined run.
*/
health?: (HealthReport | null)
/**
* Workspace-discovery, source-discovery, and analysis-stage diagnostics
* for the run (issue #2366). See `CheckOutput::workspace_diagnostics` for
* the full contract: root-relative paths, omitted when empty. The
* combined envelope carries them here rather than inside a section, so a
* run that skips a section (`--skip check`, `--only health`,
* `--only dupes`) still reports every diagnostic its analyses recorded.
*/
workspace_diagnostics?: WorkspaceDiagnostic[]
/**
* Read-only follow-up commands aggregated across the combined run's
* findings. See `CheckOutput::next_steps` for the contract.
*/
next_steps?: NextStep[]
}
/**
* Optional `_meta` block for [`CombinedOutput`].
*/
export interface CombinedMeta {
/**
* `_meta` block for the dead-code section.
*/
check?: (Meta | null)
/**
* `_meta` block for the duplication section.
*/
dupes?: (Meta | null)
/**
* `_meta` block for the health section.
*/
health?: (Meta | null)
/**
* Telemetry identifiers for the run.
*/
telemetry?: (TelemetryMeta | null)
}
/**
* Wire shape of the `dupes` section inside the bare combined envelope.
*
* The section is the standalone payload plus the view of the loaded
* duplication baseline. The standalone `dupes` envelope carries the same
* `baseline_staleness` key at its root.
*/
export interface CombinedDupesSection {
/**
* 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
/**
* This run's view of the loaded duplication baseline, present only when
* `--dupes-baseline` loaded one. Read `change_scoped` before dividing
* `matched_entries` by `baseline_entries`: a narrowed run can report
* `matched_entries: 0` on a healthy baseline.
*/
baseline_staleness?: (BaselineStaleness | null)
}
/**
* Envelope emitted by `fallow flags --format json`.
*/
export interface FeatureFlagsOutput {
schema_version: FeatureFlagsSchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
/**
* What the run was asked to narrow and whether it did. See
* [`crate::RequestOutcomes`] for the full contract.
*
* `fallow flags` accepts `--changed-since`, and an unresolvable ref widens
* the scan to the whole project rather than failing the run. Until this
* member existed the only account of that was a stderr line, which `--quiet`
* removes, so a flag inventory read as scoped to the change could silently
* be the whole project's (issue #2734).
*
* The command applies no diff filter, so the object carries the
* `changed-since` entry only. Omitted when the run was asked for nothing,
* which keeps a scan that passed no narrowing flag byte-identical and moves
* no `schema_version`.
*/
request_outcomes?: (RequestOutcomes | null)
/**
* Detected feature-flag findings.
*/
feature_flags: FeatureFlagFinding[]
/**
* Number of entries in `feature_flags`.
*/
total_flags: number
/**
* Workspace-discovery and source-discovery diagnostics for the run. See
* `CheckOutput::workspace_diagnostics` for the full contract.
*
* A flags run walks and parses the project like every other analysis, so
* it records the same discovery kinds: a `skipped-large-file`,
* `skipped-minified-file`, or `source-read-failure` file was never
* scanned for flags, and a `source-parse-degraded` file was scanned from
* a partial module. Each is a reason a flag can be missing from
* `feature_flags[]`, which is exactly what a consumer reading a
* zero-result run needs to know. The analysis-stage kinds appear here
* too: the scan correlates flags with dead exports, so it runs the
* dead-code analyze pass that records them.
*
* Omitted when empty, so a project with no discovery noise sees no
* change.
*/
workspace_diagnostics?: WorkspaceDiagnostic[]
/**
* One row per flag with the reasons the flag can be retired.
*
* Present only with `--retirement`. Without that option the key is
* omitted, so the envelope stays byte-identical and `schema_version`
* does not move. The per-site `feature_flags[]` array is the same with
* and without the option.
*/
retirement?: (FlagRetirementReport | null)
/**
* `_meta` block; see [`FeatureFlagsMeta`].
*/
_meta?: (FeatureFlagsMeta | null)
}
/**
* One feature flag finding in JSON output.
*/
export interface FeatureFlagFinding {
/**
* File path relative to the analysed root.
*/
path: string
/**
* Detected flag identifier, e.g. the env var or SDK key name.
*/
flag_name: string
kind: FeatureFlagKind
confidence: FeatureFlagConfidence
/**
* 1-based line of the flag usage.
*/
line: number
/**
* 1-based column of the flag usage.
*/
col: number
/**
* Suggested follow-up actions (investigate / suppress).
*/
actions: FeatureFlagAction[]
/**
* Flag SDK the call belongs to, for SDK-call findings.
*/
sdk_name?: (string | null)
/**
* Overlap with dead-code findings when the flag guards unused exports.
*/
dead_code_overlap?: (FeatureFlagDeadCodeOverlap | null)
}
/**
* Per-finding action emitted for feature flag findings.
*/
export interface FeatureFlagAction {
type: FeatureFlagActionType
/**
* Whether `fallow fix` can apply the action automatically.
*/
auto_fixable: boolean
/**
* Human-readable action description.
*/
description: string
/**
* Suppression comment to insert, for suppress actions.
*/
comment?: (string | null)
}
/**
* Dead-code overlap block attached when a flag guards unused exports.
*/
export interface FeatureFlagDeadCodeOverlap {
/**
* Lines inside the flag-guarded region.
*/
guarded_lines: number
/**
* Number of unused exports the flag guards.
*/
dead_export_count: number
/**
* Names of the unused exports the flag guards.
*/
dead_exports: string[]
}
/**
* The `retirement` block of `fallow flags --retirement --format json`.
*/
export interface FlagRetirementReport {
/**
* The analysis clock that ages count from, as an RFC 3339 UTC
* timestamp. `null` when the age mode is `off`, and also when no git
* history is available: outside a repository, on a branch without
* commits, or in a shallow clone. A `workspace_diagnostics` entry then
* gives the reason.
*/
generated_at_clock?: (string | null)
age_mode: FlagAgeMode
/**
* The vendor export that the report read. Present only with
* `--flag-state`.
*/
vendor_state?: (RetirementVendorState | null)
summary: RetirementSummary
/**
* Verdict of `--fail-on-regression` against a flags regression
* baseline. Present only when the gate ran.
*/
regression?: (FlagRegressionResult | null)
/**
* Verdict of `--max-flag-age`. Present only with that option.
*/
max_flag_age?: (FlagAgeGate | null)
/**
* One row per flag after the `--min-age`, `--reason`, `--sort` and
* `--top` options. A row with an empty `reasons` array is not a
* candidate.
*/
flags: RetirementFlag[]
}
/**
* The `--flag-state` vendor export that the report read.
*/
export interface RetirementVendorState {
/**
* The vendor name from the export, for example `launchdarkly`.
*/
source: string
/**
* When the export was made, as the export gives it.
*/
exported_at: string
/**
* Days between `exported_at` and the analysis clock. `null` when the
* date cannot be read.
*/
export_age_days?: (number | null)
/**
* Number of flags in the export.
*/
flags: number
}
/**
* Totals of the retirement report.
*/
export interface RetirementSummary {
/**
* Distinct flags in the code in scope, before `--min-age` and
* `--reason`. The `vendor-only` rows of a `--flag-state` export do not
* count here, so the count does not change when a key is added in the
* vendor only. `by_reason` counts them.
*/
distinct_flags: number
/**
* Rows in scope with at least one reason, `vendor-only` rows included.
*/
candidates: number
/**
* Number of rows in scope per reason.
*/
by_reason: {
[k: string]: number
}
}
/**
* Verdict of the flags regression gate.
*/
export interface FlagRegressionResult {
status: RegressionStatus
/**
* The `--tolerance` value. Absent when the status is `skipped`.
*/
tolerance?: (number | null)
/**
* How to read `tolerance`. Absent when the status is `skipped`.
*/
tolerance_kind?: (RegressionToleranceKind | null)
/**
* The compared counts: `distinct_flags` first, then each `--reason`
* code. Empty when the status is `skipped`.
*/
metrics: FlagRegressionMetric[]
/**
* Whether one count grew more than the tolerance.
*/
exceeded: boolean
/**
* Why the gate did not run. Present only when the status is `skipped`.
*/
reason?: (string | null)
}
/**
* One count that the flags regression gate compares.
*/
export interface FlagRegressionMetric {
/**
* `distinct_flags`, or a reason code from `--reason`.
*/
metric: string
/**
* The count in the baseline.
*/
baseline: number
/**
* The count in this run.
*/
current: number
/**
* `current - baseline`.
*/
delta: number
/**
* Whether the growth is more than the tolerance.
*/
exceeded: boolean
}
/**
* Verdict of `--max-flag-age`.
*/
export interface FlagAgeGate {
status: RegressionStatus
/**
* The `--max-flag-age` value in days.
*/
max_days: number
/**
* Whether one flag in scope is older than `max_days`.
*/
exceeded: boolean
/**
* Flags in the code in scope without a measured age. The gate cannot
* check these flags.
*/
unmeasured: number
/**
* Why the gate did not run. Present only when the status is `skipped`.
*/
reason?: (string | null)
/**
* The flags in scope that are older than `max_days`, oldest first.
* The `--reason`, `--min-age` and `--top` options do not change this
* list.
*/
flags: FlagAgeGateEntry[]
}
/**
* A flag that is older than `--max-flag-age`.
*/
export interface FlagAgeGateEntry {
/**
* Flag identifier.
*/
flag_name: string
kind: RetirementFlagKind
/**
* Flag SDK, for SDK flags with a known provider.
*/
sdk_name?: (string | null)
/**
* Workspace root of the flag, in a project with workspaces.
*/
workspace?: (string | null)
/**
* Age of the flag in days.
*/
age_days: number
}
/**
* One flag in the retirement report.
*/
export interface RetirementFlag {
/**
* Flag identifier.
*/
flag_name: string
kind: RetirementFlagKind
/**
* Flag SDK, for SDK flags with a known provider.
*/
sdk_name?: (string | null)
/**
* Workspace root relative to the analysed root, when the project has
* workspaces and the flag is inside one. Part of the flag identity.
*/
workspace?: (string | null)
/**
* Every site of the flag, sorted by path, line and column.
*/
sites: RetirementSite[]
/**
* Number of sites in this row that read the flag.
*/
read_sites: number
/**
* Whether every read site is in a test, story or mock file. Read sites
* of the same flag in other workspaces count too.
*/
test_only: boolean
/**
* First commit that added the flag name. Set in `pickaxe` mode only.
*/
first_seen?: (FlagCommit | null)
/**
* Oldest commit among the lines that still hold the flag.
*/
oldest_surviving_site?: (FlagCommit | null)
/**
* Newest commit among the lines that still hold the flag.
*/
last_touched?: (FlagCommit | null)
/**
* Days between the flag's oldest known commit and the analysis clock.
* In `blame` mode this is a lower bound.
*/
age_days?: (number | null)
/**
* Retirement reasons, in report order. Empty for a flag that is not a
* candidate.
*/
reasons: RetirementReason[]
/**
* Evidence for each reason.
*/
evidence: RetirementEvidence[]
/**
* Follow-up actions. Empty for a flag that is not a candidate.
*/
actions: RetirementAction[]
/**
* The vendor state of the flag. Present only with `--flag-state`, for
* a flag whose key is in the export.
*/
vendor?: (RetirementVendor | null)
}
/**
* One site of a flag in the retirement report.
*/
export interface RetirementSite {
/**
* File path relative to the analysed root.
*/
path: string
/**
* 1-based line.
*/
line: number
/**
* 0-based byte column.
*/
col: number
role: FlagSiteRole
/**
* Whether the file is a test, story or mock file.
*/
in_test: boolean
}
/**
* A commit that git history links to a flag.
*/
export interface FlagCommit {
/**
* Abbreviated commit hash.
*/
commit: string
/**
* Commit date in UTC, as `YYYY-MM-DD`.
*/
date: string
}
/**
* One piece of evidence for a retirement reason.
*/
export interface RetirementEvidence {
reason: RetirementReason
/**
* File path relative to the analysed root. For `vendor-only`, the path
* of the `--flag-state` file: relative to the root when the file is
* inside it, else as given.
*/
path: string
/**
* 1-based line.
*/
line: number
/**
* What the evidence shows.
*/
detail: string
}
/**
* A follow-up action for a retirement candidate.
*/
export interface RetirementAction {
type: RetirementActionType
/**
* Always `false`: Fallow never removes a flag.
*/
auto_fixable: boolean
/**
* Human-readable action description.
*/
description: string
}
/**
* The vendor state of one flag in the retirement report.
*/
export interface RetirementVendor {
/**
* The key in the vendor export, before `flags.vendorKeyPrefix` is
* removed.
*/
key: string
state: VendorFlagState
/**
* Whether the flag serves one variation, when the export says so.
*/
serves_single_variation?: (boolean | null)
/**
* When the vendor created the flag, as the export gives it.
*/
created_at?: (string | null)
/**
* When the vendor last evaluated the flag, as the export gives it.
*/
last_evaluated_at?: (string | null)
}
/**
* Optional `_meta` block for [`FeatureFlagsOutput`]. Both fields are optional
* because the two contributors are independent: `feature_flags` details are
* present only with `--explain`, and `telemetry` is injected post-pass by
* [`attach_telemetry_meta`] whenever an analysis run id is available (which is
* the default path). Mirrors `Meta` / `CombinedMeta`, which also model
* `telemetry` as an optional, never-required property.
*/
export interface FeatureFlagsMeta {
/**
* Feature-flag detection explanations, emitted only with `--explain`.
*/
feature_flags?: (FeatureFlagsMetaDetails | null)
/**
* Local telemetry correlation metadata for agent follow-up runs.
*/
telemetry?: (TelemetryMeta | null)
}
/**
* Feature flag explanatory metadata.
*/
export interface FeatureFlagsMetaDetails {
/**
* What the flags command reports.
*/
description: string
kinds: FeatureFlagsKindMeta
confidence: FeatureFlagsConfidenceMeta
/**
* Public documentation URL for the flags command.
*/
docs: string
}
/**
* Feature flag kind explanations.
*/
export interface FeatureFlagsKindMeta {
/**
* Explanation of the `environment_variable` kind.
*/
environment_variable: string
/**
* Explanation of the `sdk_call` kind.
*/
sdk_call: string
/**
* Explanation of the `config_object` kind.
*/
config_object: string
}
/**
* Feature flag confidence explanations.
*/
export interface FeatureFlagsConfidenceMeta {
/**
* Explanation of the `high` confidence level.
*/
high: string
/**
* Explanation of the `medium` confidence level.
*/
medium: string
/**
* Explanation of the `low` confidence level.
*/
low: string
}
/**
* Complete `fallow audit --brief --format json` wire envelope.
*
* This is distinct from [`ReviewBriefOutput`], which is the reusable review
* digest embedded in walkthrough output. The wire envelope also carries audit
* metadata, optional telemetry, and the subtract-style analysis subreports.
*/
export interface ReviewBriefWireOutput {
schema_version: ReviewBriefSchemaVersion
version: ToolVersion
/**
* Command discriminator singleton: always `"audit-brief"`.
*/
command: string
verdict: AuditVerdict
/**
* Number of changed files in the audit scope.
*/
changed_files_count: number
/**
* Base ref used to determine the changeset.
*/
base_ref: string
/**
* Human-readable description of the resolved base, when available.
*/
base_description?: (string | null)
/**
* Head commit SHA, when available.
*/
head_sha?: (string | null)
elapsed_ms: ElapsedMs
/**
* Whether base-snapshot analysis was skipped for this run.
*/
base_snapshot_skipped?: (boolean | null)
summary: AuditSummary
attribution: AuditAttribution
/**
* Optional metric definitions and local telemetry correlation metadata.
*/
_meta?: (Meta | null)
decisions: DecisionSurface
triage: DiffTriage
graph_facts: GraphFacts
partition: PartitionFacts
impact_closure: ImpactClosureFacts
focus: FocusMap
deltas: ReviewDeltas
/**
* Reviewer-private weakening signals.
*/
weakening: WeakeningSignal[]
routing: RoutingFacts
/**
* Owner-group reach from the CODEOWNERS file. Absent when no CODEOWNERS
* file is found or the file cannot be read.
*/
ownership?: (OwnershipFacts | null)
/**
* Dead-code findings scoped to the audit changeset.
*/
dead_code?: (CheckOutput | null)
/**
* Duplication findings scoped to the audit changeset.
*/
duplication?: (DupesReportPayload | null)
/**
* Complexity findings scoped to the audit changeset.
*/
complexity?: (HealthReport | null)
/**
* Branching conservation across the changeset. Absent when no base
* comparison ran.
*/
branching?: (BranchingReport | null)
}
/**
* The ranked, capped decision surface plus the set of signal_ids the
* deterministic layer emitted (the anti-hallucination allowlist).
*/
export interface DecisionSurface {
/**
* Ranked decisions, highest consequence first.
*/
decisions: Decision[]
/**
* Present when more than the cap were extracted.
*/
truncated?: (TruncationNote | null)
/**
* Every signal_id the deterministic layer emitted, INCLUDING those whose
* decision was collapsed below the cap or suppressed. The anti-hallucination
* allowlist: an agent decision whose id is absent is rejected.
*/
emitted_signal_ids: string[]
}
/**
* One consequential structural decision, framed as a judgment question for a
* human with taste, anchored to a fallow-emitted signal.
*/
export interface Decision {
/**
* Deterministic anchor to the fallow-emitted candidate this decision frames.
* `accept_signal_id` rejects any id not in the emitted set.
*/
signal_id: string
category: DecisionCategory
/**
* The decision framed as a judgment question for the human.
*/
question: string
/**
* Root-relative file the decision is anchored at.
*/
anchor_file: string
/**
* 1-based anchor line, when the underlying signal carries one (0 = file head).
*/
anchor_line: number
/**
* The raw fallow-emitted candidate key the `signal_id` hashes.
*/
signal_key: string
/**
* The `signal_id` this decision WOULD have had before any rename in this
* change (the anchor file's pre-rename path). Present only when the anchor was
* renamed. A review-memory layer carries a dismissal across a `git mv`: if
* `previous_signal_id` was dismissed in an earlier PR, treat this decision as
* dismissed too. Keeps `signal_id` itself exact + deterministic.
*/
previous_signal_id?: (string | null)
/**
* Blast radius: count of modules affected beyond the diff by this decision.
*/
blast: number
/**
* `blast * reversibility_weight`: the rank key (sorted descending).
*/
consequence: number
/**
* The routed expert(s) to ask, from ownership routing. Empty when no
* ownership signal is available for the anchor file.
*/
expert: string[]
/**
* Whether the anchor file's only qualified owner is one person.
*/
bus_factor_one?: boolean
/**
* Honest per-decision count: in-repo modules OUTSIDE the diff that already
* depend on this decision's anchor. This is the DISPLAY number (taste
* ownership: the human reads reversibility from the count itself), distinct
* from `blast` (the project-wide proxy used only for ranking). Never a door
* label. Internal-only by construction, so it cannot see a published library's
* external consumers; the public-API trade-off clause names that risk in prose.
*/
internal_consumer_count: number
/**
* The named structural sacrifice this change makes, stated as a fact, never a
* recommendation (e.g. "Couples `app` to `infra`; 4 in-repo modules already
* depend on this anchor."). A sibling fact to `question`; it never tells the
* human what to choose.
*/
tradeoff: string
}
/**
* A note for decisions collapsed below the cap.
*/
export interface TruncationNote {
/**
* How many decisions were collapsed below the cap.
*/
collapsed: number
/**
* Human-readable collapse reason.
*/
reason: string
}
/**
* Stage 0 of the brief: triage facts derived purely from the diff size.
*
* `hunks` and `net_lines` are populated when the caller supplies parsed diff
* evidence. They remain absent when no diff is available.
*/
export interface DiffTriage {
/**
* Number of changed files in the audit scope.
*/
files: number
/**
* Number of diff hunks, or `None` when no diff evidence was supplied.
*/
hunks?: (number | null)
/**
* Net added-minus-removed lines, or `None` without diff evidence.
*/
net_lines?: (number | null)
risk_class: RiskClass
review_effort: ReviewEffort
}
/**
* Stage 1 of the brief: graph-derived orientation facts.
*
* `boundaries_touched` is derived from the run's boundary-violation zones.
* `exports_added` and `api_width_delta` both report the exports-aware public
* API widening count. Removed exports are not represented in this
* widening-only signal. The set of modules the changed code reaches is Stage
* 3's `impact_closure`, which owns both its magnitude and its paths.
*/
export interface GraphFacts {
/**
* Number of public API exports added by the changeset. Zero means the
* changeset adds no public API exports.
*/
exports_added: number
/**
* Widening-only public API delta, currently equal to `exports_added`.
* Removed exports are not represented, so zero means no public API exports
* were added.
*/
api_width_delta: number
/**
* Architecture boundary zones touched by the changeset, deduped and sorted.
* Derived from the run's boundary-violation findings.
*/
boundaries_touched: string[]
}
/**
* Stage 2 of the brief: the partition + order. The changed files split into
* coherent BY-MODULE units (the only byte-identical-deterministic clustering
* definition straight from the graph), plus a dependency-sensible review ORDER
* over those units (definitions before consumers, mechanical/leaf units last,
* ties broken by the path sort). Stage 2 sits UNDER the decision surface as a
* drill-down; it is the backbone the directed-review loop hands the agent.
*
* Feature-cluster and concern partitioning are deferred (they need scoring
* heuristics whose tie-breaks are a fresh nondeterminism surface).
*/
export interface PartitionFacts {
/**
* The by-module units, sorted by module directory. Empty when no graph was
* retained or no changed file maps to a known module.
*/
units: ReviewUnitFact[]
/**
* The dependency-sensible review order: module-directory strings,
* definitions before consumers, mechanical/leaf units last. A permutation of
* the `units` module directories.
*/
order: string[]
/**
* Connected components of the inter-unit dependency graph: groups of
* module directories that share no import edge with any unit outside the
* group. Present only when there are two or more; a single slice is just
* `order`. A slice proves the absence of import edges to the rest of the
* change, nothing more: whether it can land on its own is still a
* judgment (generated files and lockstep contracts share no edge and
* still belong together). An orientation fact, never a demand to split.
*/
independent_slices?: string[][]
}
/**
* One review unit: a coherent by-module cluster of the changed set.
*/
export interface ReviewUnitFact {
/**
* The module directory the unit covers (root-relative, forward-slashed).
* The empty string is the repository-root group.
*/
module_dir: string
/**
* The changed files in this unit, path-sorted.
*/
files: string[]
}
/**
* Stage 3 of the brief: the impact closure. The transitive
* affected-but-not-in-diff set plus the coordination gap. The differentiator a
* diff tool fundamentally cannot do, because it has no graph.
*
* Honest scope (ADR-001, syntactic): the coordination gap is an attention
* pointer at the exact inter-module failure mode, NOT a correctness proof.
*/
export interface ImpactClosureFacts {
/**
* The FULL number of files transitively affected by the changeset
* (reverse-deps + re-export chains) that are NOT in the diff. Computed
* BEFORE [`affected_not_shown`](Self::affected_not_shown) is capped to a
* sample, so it is always the true magnitude of the blast radius.
*/
affected_count: number
/**
* A capped, path-sorted sample of the affected root-relative paths (at most
* [`AFFECTED_SAMPLE_CAP`]), deduped. The full count lives in
* [`affected_count`](Self::affected_count) and the distribution in
* [`affected_by_dir`](Self::affected_by_dir); use this list to jump to
* representative files, NEVER to enumerate the blast radius or to infer its
* shape. Because it is a prefix of the sorted set, it clusters in whichever
* directory sorts first. To reconstruct the full set, run
* `fallow check --impact-closure <path>` once per changed file and union the
* results: that flag seeds from a single file, so no single command
* reproduces this changeset-wide union.
*/
affected_not_shown: string[]
/**
* The blast radius rolled up by parent directory: how the affected files
* distribute, heaviest directory first, ties broken by directory path so the
* order is deterministic. This is the SHAPE signal, and unlike
* [`affected_not_shown`](Self::affected_not_shown) its counts are exact for
* every directory it lists. At most [`AFFECTED_DIR_CAP`] entries.
*/
affected_by_dir: AffectedDirectory[]
/**
* How many directories did not fit within [`AFFECTED_DIR_CAP`] and are
* absent from [`affected_by_dir`](Self::affected_by_dir). They are the
* lightest ones; their files are still counted in
* [`affected_count`](Self::affected_count). Zero when nothing was omitted.
* Add this to `affected_by_dir.len()` for the true number of directories
* the change reaches.
*/
affected_by_dir_omitted: number
/**
* Coordination gaps: a changed file exports a contract consumed by a module
* absent from the diff. One entry per (changed file, consumer) pair. NOT a
* subset of [`affected_not_shown`](Self::affected_not_shown): the gap
* deliberately skips story and test consumers that the affected set counts.
*/
coordination_gap: CoordinationGapFact[]
}
/**
* One directory of the blast radius and how many affected files it holds.
*/
export interface AffectedDirectory {
/**
* Root-relative parent directory, forward-slashed. The empty string is the
* repository root.
*/
dir: string
/**
* How many affected-but-not-in-diff files live directly in `dir`. Exact,
* never sampled.
*/
count: number
}
/**
* One coordination-gap entry: a changed file exports symbols consumed by a
* `consumer_file` that is NOT in the diff. Deduped per (changed, consumer) pair
* (firing-precision rule R2).
*/
export interface CoordinationGapFact {
/**
* Root-relative path of the changed file whose contract is consumed elsewhere.
*/
changed_file: string
/**
* Root-relative path of the consumer module that is NOT in the diff.
*/
consumer_file: string
/**
* The exported symbol names the consumer references, sorted.
*/
consumed_symbols: string[]
/**
* Honest scope note: this is a syntactic attention pointer, not a proof.
*/
note: string
}
/**
* The weighted focus map: the ranked `review-here` units plus the FULL
* `deprioritized` escape-hatch list, so nothing is hidden.
*
* Completeness invariant (the escape-hatch done-condition): the two lists
* partition the unit set, so `review_here.len() + deprioritized.len()` equals
* the total unit count by construction.
*/
export interface FocusMap {
/**
* Units labeled `review-here`, ranked by composite score (descending), ties
* broken by path for determinism.
*/
review_here: FocusUnit[]
/**
* EVERY de-prioritized unit (`not-prioritized`, plus runtime-backed `skip`
* units on the paid path) -- the escape hatch. Always present and fully
* enumerated so a reviewer can always "show me what you de-prioritized"; the
* human brief collapses it by default and re-expands under
* `--show-deprioritized`. Nothing is ever hidden, including a `skip`.
*/
deprioritized: FocusUnit[]
}
/**
* One review unit on the focus map: its file, composite score, label, human
* reason, and any confidence flags.
*/
export interface FocusUnit {
/**
* Root-relative path of the changed file this unit covers.
*/
file: string
score: FocusScore
label: FocusLabel
/**
* A human-readable reason for the label, built from the present signals.
*/
reason: string
/**
* Confidence flags (advisory; never lower the score). Sorted, deduped.
*/
confidence?: ConfidenceFlag[]
}
/**
* The composite attention score, with the deterministic component sub-scores
* kept on the wire so the runtime layer adds its weight without recomputing the
* signals.
*/
export interface FocusScore {
/**
* Fan-in/out blast-radius component.
*/
fan_io: number
/**
* Security source -> sink taint-touch component (0 until a security pass is
* threaded onto the brief path; the seam is built and tested).
*
* Omitted from the wire while it is zero, the same treatment `runtime`
* gets. Publishing a permanently-zero component as a required field made
* it read as a measurement that found nothing, when nothing measured it.
* A consumer that sums components must read an absent component as zero.
*/
security_taint?: number
/**
* Risk-zone component (boundary / public-API / security-sensitive).
*/
risk_zone: number
/**
* Change-shape component (new/widened export, signature change proxy).
*/
change_shape: number
/**
* Runtime-weight component (paid): a hot path (runtime evidence of high
* invocation) adds an invocation-bucketed weight so it amplifies the blast
* and outranks an otherwise-equal cold unit. `0` in free mode (no runtime
* input), so the free-tier total stays the four deterministic components and
* is byte-identical to the no-runtime baseline.
*/
runtime?: number
/**
* The summed total of every present component (the four deterministic ones
* plus the runtime weight).
*/
total: number
}
/**
* Diff-aware deterministic deltas (6.A), framed new-vs-pre-existing against
* the audit base snapshot. Each entry is a brief summary/verdict line.
*
* `public_api` is batch-consolidated to ONE decision per change (rule R1):
* the `added` list carries the introduced public-export keys as evidence, but a
* reviewer reads "the public surface widened by N", never one decision per
* symbol.
*/
export interface ReviewDeltas {
/**
* Cross-zone boundary EDGES introduced vs base (R2 first-edge-only: one per
* `<from_zone>-><to_zone>` pair, never per import). New-vs-pre-existing.
*/
boundary_introduced: string[]
/**
* Circular dependencies introduced vs base (canonical file-set keys).
*/
cycle_introduced: string[]
/**
* Exports-aware public-API surface delta: the public-export keys
* (`<rel_path>::<name>`) added vs base, resolved through `package.json`
* `exports` + re-export reachability. A symbol re-exported only through an
* internal barrel NOT in `exports` is absent here (zero delta); one
* reachable through an `exports` path is present (exactly one).
*/
public_api_added: string[]
/**
* Third-party dependencies a changed `package.json` declares that the base
* manifest did not, as `<manifest>::<name>` keys. Every dependency section
* participates. Always present, empty when no manifest changed.
*/
dependency_added: string[]
/**
* Declared dependencies whose range moved across a major version (or a
* `0.x` minor) vs base, as `<manifest>::<name>@<from>-><to>` keys. Minor
* and patch moves are not candidates; a non-numeric range is skipped.
* Always present, empty when nothing crossed a major version.
*/
dependency_major_bumped: string[]
}
/**
* One weakening signal: a category, the file it was detected in, and a short
* human-readable evidence string. Reviewer-private; never gates.
*/
export interface WeakeningSignal {
kind: WeakeningKind
/**
* Root-relative path of the changed file the signal was detected in.
*/
file: string
/**
* Short evidence string (e.g. the offending token or the threshold delta).
*/
evidence: string
}
/**
* The full routing section: one unit per changed source file with a routable
* signal. Files with no ownership signal are omitted (no noise).
*/
export interface RoutingFacts {
/**
* Per-changed-file routing units, sorted by file path.
*/
units: RoutingUnit[]
}
/**
* One routed unit with its experts and bus-factor flag.
*/
export interface RoutingUnit {
/**
* Root-relative path of the changed file.
*/
file: string
/**
* The routed expert(s): the CODEOWNERS declared owner when present, else the
* top git-blame / recency contributor; empty when no signal is available.
*/
expert: string[]
/**
* Whether the only qualified owner is a single contributor (bus-factor-1):
* a knowledge-concentration risk worth a second reviewer.
*/
bus_factor_one?: boolean
}
/**
* How far a changeset reaches across CODEOWNERS owner groups.
*
* Computed from the CODEOWNERS file alone: no git history is read, so the
* section is present whenever a CODEOWNERS file is found, also when the churn
* walk behind `routing` finds nothing. Each file maps to its primary owner
* (the first owner of the last matching rule). A file that no rule matches,
* or that a GitLab negation rule matches, belongs to the `(unowned)` group.
* The owner strings use the same vocabulary as `routing.units[].expert`.
*
* Absent from the brief when no CODEOWNERS file is found, or when the file
* cannot be read or does not parse. A configured `codeowners` path that
* fails also prints a warning on stderr.
*
* `groups[].direct_count` counts all changed files, source or not, so the
* sum over all groups is the number of changed files, not the size of
* `impact_closure.in_diff`. Slice owners count only the files of the
* partition units, which are source files.
*/
export interface OwnershipFacts {
/**
* Distinct owner groups across the changed files and the impact closure.
* The `(unowned)` group counts as one group. Exact, never capped.
*/
group_count: number
/**
* Owner groups that own no changed file and appear only through the
* impact closure. Exact, never capped.
*/
transitive_only_count: number
/**
* Changed files that belong to the `(unowned)` group.
*/
unowned_direct_count: number
/**
* The owner groups, sorted by `direct_count` descending, then
* `affected_count` descending, then `owner`. At most [`OWNER_GROUP_CAP`]
* entries. The counts in each entry are exact.
*/
groups: OwnerGroupFact[]
/**
* How many owner groups did not fit within [`OWNER_GROUP_CAP`] and are
* absent from `groups`. Zero when nothing was omitted.
*/
groups_omitted: number
/**
* The owner set of each independent slice, aligned by index with
* `partition.independent_slices`. Present only when that list is present
* (two or more slices). A fact for the reviewer, never a demand to split.
*/
slices?: OwnershipSliceFact[]
}
/**
* One owner group and how many files of the changeset it owns.
*/
export interface OwnerGroupFact {
/**
* The CODEOWNERS owner (`@user`, `@org/team`, or an email), or
* `(unowned)`.
*/
owner: string
/**
* Changed files this group owns.
*/
direct_count: number
/**
* Files of the impact closure (affected, not in the diff) this group owns.
*/
affected_count: number
}
/**
* The owners of one independent slice of the partition.
*/
export interface OwnershipSliceFact {
/**
* The module directories of the slice, as in
* `partition.independent_slices`.
*/
module_dirs: string[]
/**
* The distinct owners of the changed files in the slice, sorted. The
* `(unowned)` group is a distinct owner. Never empty.
*/
owners: string[]
/**
* True when the slice has exactly one owner, so one owner group can
* review it on its own.
*/
separable: boolean
}
/**
* The brief's branching section.
*/
export interface BranchingReport {
/**
* Files carrying the split signature: branching within `tolerance` of
* where it was, more functions, a smaller largest function. Empty when
* none do, which is the common case and is not itself a finding. See
* `SplitInPlace` for why this describes a shape rather than asserting a
* refactor.
*/
split_in_place: SplitInPlace[]
/**
* The band inside which a file's branching counts as held. Published
* because a claim against an unpublished threshold is not reproducible by
* a consumer.
*/
tolerance: number
scope: BranchingScope
branch_points: BranchingMetric
functions: BranchingMetric
peak_unit_cyclomatic: BranchingMetric
/**
* Base-side branch points of the files that have no head entry.
*/
branch_points_only_in_base: number
cognitive: BranchingCognitive
/**
* The files that moved the numbers most, largest absolute branch-point
* change first.
*/
by_file: BranchingFileDelta[]
/**
* Files with a change that the list did not name.
*/
by_file_omitted: number
}
/**
* One file present on both revisions whose branching held while it gained
* functions and its largest function shrank.
*
* Local by construction: nothing here depends on any other file, so unrelated
* work in the changeset cannot make it more or less true. A set-level
* classifier cannot make this claim, because a changeset contains arbitrary
* other work and an aggregate cannot attribute.
*
* It is a description, not an inference. The three conditions are the
* signature a split leaves, and they are also satisfiable without one: the
* peak is a file-level maximum (`FileBranching::peak_cyclomatic`), so it can
* fall because the largest function left the file while arriving helpers
* happen to carry the branching it took with it. Both numbers are reported so
* a reader can see that for themselves, and the rendered text states what was
* measured rather than concluding a refactor happened.
*
* Files carrying synthetic template units are excluded, because those units
* are outside every count here, so the numbers would not describe the file a
* reader opens. Test paths are excluded too: their totals are reported in
* `BranchingScope` instead.
*/
export interface SplitInPlace {
/**
* Root-relative path.
*/
path: string
/**
* Branch points on the base revision.
*/
branch_points_before: number
/**
* And on head. Within `tolerance` of `branch_points_before`, which is what
* "held" means here. Both are reported because one number alone cannot be
* checked.
*/
branch_points_after: number
/**
* Accounted functions before the split.
*/
functions_before: number
/**
* Accounted functions after it.
*/
functions_after: number
/**
* Highest single-function cyclomatic score before.
*/
peak_before: number
/**
* And after. It falls by construction when a function is split, which is
* why it is evidence here and never a metric to celebrate.
*/
peak_after: number
}
/**
* Size and composition of the compared set.
*/
export interface BranchingScope {
/**
* Files carrying units on both revisions.
*/
files_both: number
/**
* Files carrying units on the head revision only.
*/
files_added: number
/**
* Files that carried units on the base revision only, whether they were
* deleted or merely lost every accounted unit. Reported, and excluded from
* every headline number: such a file contributes its whole base-side total
* as a fall with no head counterpart.
*/
files_only_in_base: number
/**
* Branch points on test-shaped paths within the head totals. Test code
* routinely dominates both terms, so a reader needs to see its share
* before reading the headline.
*/
test_branch_points: number
/**
* Functions on test-shaped paths within the head totals.
*/
test_functions: number
/**
* Share of head branch points owned by the single largest file, so a
* reader can see when one vendored or generated file owns the number.
*/
largest_file_share_of_branch_points: number
}
/**
* One metric across the two revisions.
*/
export interface BranchingMetric {
/**
* Value on the base revision.
*/
previous: number
/**
* Value on the head revision.
*/
current: number
/**
* `current - previous`. Signed, so a consumer never has to infer direction
* from a separate field.
*/
delta: number
}
/**
* The cognitive figure and what drove it.
*
* `previous` and `current` exclude prop-count and hook-density increments.
* Both are cognitive-only, and prop count records an excess over a floor, so
* it is superlinear in a split and would move this number with branching and
* nesting both flat. This therefore does not match the cognitive score the
* complexity findings report.
*/
export interface BranchingCognitive {
/**
* Cognitive weight on the base revision.
*/
previous: number
/**
* Cognitive weight on the head revision.
*/
current: number
/**
* `current - previous`.
*/
delta: number
/**
* Change in the summed nesting depth behind those increments.
*/
nesting_weight_delta: number
/**
* What the improvement is attributable to, absent when cognitive did not
* fall. There is nothing to attribute when the number rose or held.
*/
attributed_to?: (CognitiveAttribution | null)
}
/**
* One file's contribution to the change.
*/
export interface BranchingFileDelta {
/**
* Root-relative path.
*/
path: string
/**
* Change in branch points for this file.
*/
branch_points_delta: number
/**
* Change in accounted functions for this file.
*/
functions_delta: number
}
/**
* The separable `decision-surface` envelope: the single call that puts taste-
* decisions in front of a human, callable WITHOUT the full pipeline (the
* `decision_surface` MCP tool's output). Carries `kind`/`schema_version` plus
* structured `actions[]` per decision.
*/
export interface DecisionSurfaceOutput {
schema_version: DecisionSurfaceSchemaVersion
/**
* Fallow CLI version that produced this output.
*/
version: string
/**
* Command discriminator singleton: always `"decision-surface"`.
*/
command: string
/**
* The ranked, capped decisions, each with structured actions.
*/
decisions: DecisionWithActions[]
/**
* Present when more than the cap were extracted.
*/
truncated?: (TruncationNote | null)
/**
* Count of fallow-emitted signal_ids (the anti-hallucination allowlist size).
*/
signal_count: number
}
/**
* One decision plus its structured `actions[]`.
*/
export interface DecisionWithActions {
/**
* Deterministic anchor to the fallow-emitted candidate this decision frames.
* `accept_signal_id` rejects any id not in the emitted set.
*/
signal_id: string
category: DecisionCategory
/**
* The decision framed as a judgment question for the human.
*/
question: string
/**
* Root-relative file the decision is anchored at.
*/
anchor_file: string
/**
* 1-based anchor line, when the underlying signal carries one (0 = file head).
*/
anchor_line: number
/**
* The raw fallow-emitted candidate key the `signal_id` hashes.
*/
signal_key: string
/**
* The `signal_id` this decision WOULD have had before any rename in this
* change (the anchor file's pre-rename path). Present only when the anchor was
* renamed. A review-memory layer carries a dismissal across a `git mv`: if
* `previous_signal_id` was dismissed in an earlier PR, treat this decision as
* dismissed too. Keeps `signal_id` itself exact + deterministic.
*/
previous_signal_id?: (string | null)
/**
* Blast radius: count of modules affected beyond the diff by this decision.
*/
blast: number
/**
* `blast * reversibility_weight`: the rank key (sorted descending).
*/
consequence: number
/**
* The routed expert(s) to ask, from ownership routing. Empty when no
* ownership signal is available for the anchor file.
*/
expert: string[]
/**
* Whether the anchor file's only qualified owner is one person.
*/
bus_factor_one?: boolean
/**
* Honest per-decision count: in-repo modules OUTSIDE the diff that already
* depend on this decision's anchor. This is the DISPLAY number (taste
* ownership: the human reads reversibility from the count itself), distinct
* from `blast` (the project-wide proxy used only for ranking). Never a door
* label. Internal-only by construction, so it cannot see a published library's
* external consumers; the public-API trade-off clause names that risk in prose.
*/
internal_consumer_count: number
/**
* The named structural sacrifice this change makes, stated as a fact, never a
* recommendation (e.g. "Couples `app` to `infra`; 4 in-repo modules already
* depend on this anchor."). A sibling fact to `question`; it never tells the
* human what to choose.
*/
tradeoff: string
/**
* Structured actions: route to the expert, or suppress.
*/
actions: DecisionAction[]
}
/**
* A structured action attached to a surfaced decision (the agent-actionable
* surface). Mirrors the typed-action shape the rest of fallow emits.
*/
export interface DecisionAction {
type: DecisionActionType
/**
* Human-readable description of the action.
*/
description: string
/**
* Runnable command or paste-ready suppression comment.
*/
command?: (string | null)
/**
* Whether fallow can carry the action out automatically. Always `false`:
* a decision is a human judgment, never auto-applied.
*/
auto_fixable: boolean
}
/**
* The `fallow review --walkthrough-guide` envelope: the current digest + schema
* the agent fetches. The tool owns this; the skill stays thin (it fetches this
* rather than embedding a frozen copy). Always emitted with exit 0.
*/
export interface WalkthroughGuide {
schema_version: ReviewBriefSchemaVersion
/**
* Fallow CLI version that produced this guide.
*/
version: string
/**
* Command discriminator singleton: always `"review-walkthrough-guide"`.
*/
command: string
/**
* The deterministic graph-snapshot hash pinned into the digest. The agent
* echoes it back; a mismatch on reentry refuses the payload as stale.
*/
graph_snapshot_hash: string
digest: ReviewBriefOutput
direction: ReviewDirection
/**
* The per-hunk change anchors: one stable id per changed region. An agent
* may cite a `change_anchor` as a judgment anchor in addition to an emitted
* `signal_id`, so a trade-off about a changed region with no graph finding
* can still anchor (and be post-validated) rather than hallucinate.
*/
change_anchors: ChangeAnchor[]
agent_schema: AgentSchema
/**
* The injection-resistance note (digest is graph-only; PR prose untrusted).
*/
injection_note: string
}
/**
* The full `fallow audit --brief --format json` envelope. Carries the
* informational verdict, the triage and graph-facts orientation stages, plus
* the reused "subtract" section (the same dead-code / duplication / complexity
* payload `fallow audit --format json` emits).
*/
export interface ReviewBriefOutput {
schema_version: ReviewBriefSchemaVersion
/**
* Fallow CLI version that produced this output.
*/
version: string
/**
* Command discriminator singleton: always `"audit-brief"`.
*/
command: string
triage: DiffTriage
graph_facts: GraphFacts
partition: PartitionFacts
impact_closure: ImpactClosureFacts
focus: FocusMap
deltas: ReviewDeltas
/**
* 6.F, headline: reviewer-private weakening signals (tests
* removed/skipped, thresholds lowered, suppressions added, security steps
* removed). Advisory, never gates, never auto-posted.
*/
weakening: WeakeningSignal[]
routing: RoutingFacts
/**
* How far the change reaches across CODEOWNERS owner groups, computed
* from the CODEOWNERS file alone. Absent when no CODEOWNERS file is
* found or the file cannot be read.
*/
ownership?: (OwnershipFacts | null)
decisions: DecisionSurface
/**
* Branching conservation across the changeset: total branching against
* the number of functions now holding it. Absent when no base comparison
* ran, which keeps the wire shape byte-identical for a consumer that
* never had a base snapshot.
*/
branching?: (BranchingReport | null)
}
/**
* The review direction artifact: the order to review in, the coherent units,
* and per-unit concern lens + out-of-diff + expert. A minimal projection of the
* EXISTING graph facts (routing units + impact closure); the full weighted-focus
* engine is a later epic. Graph-derived only (injection-resistant).
*/
export interface ReviewDirection {
/**
* The dependency-sensible review order: unit file paths, units carrying
* out-of-diff consumers first (review the load-bearing definitions before
* the mechanical units).
*/
order: string[]
/**
* Coherent review units, in `order`.
*/
units: DirectionUnit[]
}
/**
* One directed review unit projected from the graph: a file the change touches,
* the concern to check, the out-of-diff consumers it must account for, and the
* routed expert. Graph-derived only (routing + impact closure), NEVER from prose.
*/
export interface DirectionUnit {
/**
* Root-relative path of the unit to review.
*/
file: string
/**
* The concern lens the agent should check for this unit, derived from the
* unit's risk signals (impact-closure consumers vs a plain touched file).
*/
concern_lens: string
/**
* Per-unit review-effort budget: the weighted-focus composite score for
* this file. A cloud fan-out spends AI passes/verifiers PROPORTIONAL to this
* (higher = review harder); a local single-agent loop can ignore it.
*/
scoring_budget: number
/**
* Root-relative paths of modules affected by this unit but NOT in the diff
* (the out-of-diff context the agent must reason about).
*/
out_of_diff: string[]
/**
* Routed expert(s), when ownership signals are available.
*/
expert: string[]
/**
* Direct test adjacency of this unit. Absent when the graph was not
* retained or the unit is itself a test file.
*/
test_adjacency?: (TestAdjacency | null)
}
/**
* One stable per-hunk CHANGE ANCHOR: a changed region the agent may cite as a
* judgment anchor IN ADDITION to a `signal_id`. Where a `signal_id` anchors a
* graph FINDING ("fallow emitted this exact finding"), a change_anchor anchors
* only a changed REGION ("fallow confirms this region changed") , a strictly
* weaker guarantee, surfaced as `anchor_kind` on the accepted judgment so a
* consumer can tell the two apart. Graph/diff-derived; NEVER from prose.
*/
export interface ChangeAnchor {
/**
* Stable, CONTENT-addressed id: `chg:<16-hex>` over the file path + the
* normalized added text (line numbers are NOT hashed, so an edit above the
* hunk or a whitespace-only change does not move the id).
*/
change_anchor: string
/**
* Root-relative path of the changed file.
*/
file: string
/**
* 1-based first line of the hunk in the head file (display/deep-link only;
* NOT part of the id).
*/
start_line: number
/**
* Number of added lines in the hunk (display only; NOT part of the id).
*/
line_count: number
/**
* Rename-durable anchor: the id this same hunk would have had under the
* pre-rename path. `None` unless the file was renamed in this change, so an
* agent that cited the anchor before a `git mv` still resolves.
*/
previous_change_anchor?: (string | null)
}
/**
* The shape the agent must return, embedded in the guide so a thin skill needs
* no frozen copy. Documents the anchoring + staleness contract in the wire.
*/
export interface AgentSchema {
/**
* How the agent must structure each judgment: cite an emitted `signal_id`
* or `change_anchor`, add free-text `framing` (non-deterministic, fenced),
* an optional `concern`, and an optional `action` from the closed
* vocabulary.
*/
judgment_shape: string
/**
* The agent MUST echo this `graph_snapshot_hash` back in its JSON; a
* mismatch on reentry REFUSES the payload as stale.
*/
echo_field: string
/**
* The anchoring rule name.
*/
anchoring_rule: string
/**
* The closed `action` vocabulary ([`JUDGMENT_ACTIONS`]): a judgment with
* any other value is rejected on reentry.
*/
action_vocabulary: string[]
/**
* The recommended `concern` vocabulary ([`JUDGMENT_CONCERNS`]), documented
* for grouping; free text is still accepted.
*/
concern_vocabulary: string[]
}
/**
* The `fallow review --walkthrough-file` validation envelope: the result of
* post-validating the agent's judgment against the live graph. Always exit 0.
*/
export interface WalkthroughValidation {
schema_version: ReviewBriefSchemaVersion
/**
* Fallow CLI version that produced this validation.
*/
version: string
/**
* Command discriminator singleton: always `"review-walkthrough-validation"`.
*/
command: string
/**
* The current run's deterministic graph-snapshot hash.
*/
graph_snapshot_hash: string
/**
* `true` when the agent's echoed hash != the current hash (the tree moved):
* the WHOLE payload is refused, `accepted` is empty.
*/
stale: boolean
/**
* Judgments that cite a real fallow-emitted signal, framing fenced.
*/
accepted: AcceptedJudgment[]
/**
* Judgments rejected (unanchored signal id, or all-rejected when stale).
*/
rejected: RejectedJudgment[]
/**
* Count of accepted judgments.
*/
accepted_count: number
/**
* Count of rejected judgments.
*/
rejected_count: number
/**
* Count of accepted judgments whose `signal_id` resolved against the live
* allowlist. Zero unanchored when this equals `accepted_count` and there are
* no rejections (the clean done-condition).
*/
unanchored_count: number
}
/**
* One accepted judgment: the real anchored signal passed through with the
* agent's framing FENCED as non-deterministic.
*/
export interface AcceptedJudgment {
/**
* The fallow-emitted `signal_id` (verified against the allowlist). Empty
* when this judgment was anchored by a `change_anchor` instead.
*/
signal_id: string
/**
* The fallow-emitted `change_anchor` (verified against the allowlist). Empty
* when this judgment was anchored by a `signal_id`.
*/
change_anchor: string
/**
* Which anchor resolved: `"signal"` (a graph FINDING, the strong anchor) or
* `"change"` (a changed REGION only, the weaker anchor). Lets a consumer
* distinguish a finding-anchored judgment from a region-anchored one rather
* than collapsing both into one accepted bucket.
*/
anchor_kind: string
/**
* The agent's fenced free-text framing.
*/
agent_framing: string
/**
* The agent's optional concern category.
*/
concern?: (string | null)
/**
* The author-action label the judgment carries (`block`, `address`,
* `consider`, or `fyi`), validated against [`JUDGMENT_ACTIONS`]. It tells
* the receiving author what is required and what is optional; it is the
* reviewer's instruction, fenced with the framing, never a gate.
*/
action?: (string | null)
/**
* Hard fence: always `false`. The framing is agent prose, never a
* deterministic fallow result, so it never gates or auto-posts.
*/
deterministic: boolean
}
/**
* One rejected judgment plus the reason it was rejected.
*/
export interface RejectedJudgment {
/**
* The `signal_id` the agent cited (fallow never emitted it). Empty when the
* judgment cited a `change_anchor` instead.
*/
signal_id: string
/**
* The `change_anchor` the agent cited (fallow never emitted it). Empty when
* the judgment cited a `signal_id` instead.
*/
change_anchor: string
/**
* The rejection reason: `unanchored-signal-id` (cited a signal fallow did
* not emit), `unknown-change-anchor` (cited a region fallow did not emit),
* `stale-snapshot` (the tree moved), or `invalid-action` (an `action`
* outside [`JUDGMENT_ACTIONS`]; the anchor itself resolved).
*/
reason: string
/**
* The offending value for an `invalid-action` rejection, echoed so the
* agent can correct it in one round trip. Absent for the other reasons.
*/
invalid_value?: (string | null)
}
/**
* The `fallow suppressions --format json` envelope. `FallowOutput`
* discriminates it by the `kind: "suppression-inventory"` tag.
*
* A read-only projection over the suppression markers present in analyzed
* files this run: nothing here is a finding, and the command that emits it
* always exits 0.
*/
export interface SuppressionInventoryOutput {
schema_version: SuppressionInventorySchemaVersion
/**
* What the run was asked to narrow and whether it did. See
* [`crate::RequestOutcomes`] for the full contract.
*
* `fallow suppressions` accepts `--changed-since`, and an unresolvable ref
* widens the inventory to the whole project rather than failing the run.
* Until this member existed the only account of that was a stderr line,
* which `--quiet` removes, so an inventory read as scoped to the change
* could silently be the whole project's (issue #2734).
*
* The command applies no diff filter, so the object carries the
* `changed-since` entry only. Omitted when the run was asked for nothing,
* which keeps an inventory that passed no narrowing flag byte-identical and
* leaves `schema_version` at `1`.
*/
request_outcomes?: (RequestOutcomes | null)
summary: SuppressionInventorySummary
/**
* Per-file suppression listings, sorted by path then line.
*/
files: SuppressionInventoryFile[]
}
/**
* Project-level totals for the suppression inventory.
*/
export interface SuppressionInventorySummary {
/**
* Total suppression markers in scope.
*/
total: number
/**
* Number of files carrying at least one marker.
*/
files: number
/**
* Markers without a human-authored `--` reason.
*/
without_reason: number
/**
* Markers that also appear as stale-suppression findings this run. This
* is a JOIN against the existing stale-suppression detector's output
* (matched by file and kind), not a new detection.
*/
stale: number
/**
* Marker counts per suppressed kind, sorted by count (descending) then
* kind. `kind` is `null` for blanket markers, mirroring the per-entry
* contract.
*/
by_kind: SuppressionKindCount[]
}
/**
* One `by_kind` row in the suppression inventory summary.
*/
export interface SuppressionKindCount {
/**
* The suppressed issue kind in kebab-case, or `null` for blanket markers
* (rendered as "blanket" in human output; machine consumers branch on
* `null`).
*/
kind?: (string | null)
/**
* Number of markers targeting this kind.
*/
count: number
}
/**
* One file's suppression listing.
*/
export interface SuppressionInventoryFile {
/**
* Project-root-relative path, forward-slash separated.
*/
path: string
/**
* Markers in this file, sorted by line.
*/
suppressions: SuppressionInventoryEntry[]
}
/**
* One suppression marker in the inventory.
*/
export interface SuppressionInventoryEntry {
/**
* 1-based line of the suppression comment itself; 0 only if unknown.
*/
line: number
/**
* The suppressed issue kind in kebab-case (e.g. `"unused-export"`), or
* `null` for a blanket marker that suppresses every kind on its target.
* Human output renders the blanket case as the literal word "blanket";
* the JSON contract deliberately keeps `null` so machine consumers
* branch on `null` instead of a magic string.
*/
kind?: (string | null)
level: SuppressionInventoryLevel
origin: SuppressionInventoryOrigin
/**
* Human-authored reason after `--`; `null` when absent.
*/
reason?: (string | null)
/**
* Whether a human-authored reason is present.
*/
reason_present: boolean
}
/**
* Versioned readiness envelope emitted by `fallow doctor --format json`.
*/
export interface DoctorOutput {
schema_version: DoctorSchemaVersion
version: ToolVersion
root: DoctorProjectRoot
status: DoctorStatus
summary: DoctorSummary
/**
* Checks in stable contract order.
*/
checks: DoctorCheck[]
}
/**
* Counts for every per-check status.
*/
export interface DoctorSummary {
/**
* Successful checks.
*/
pass: number
/**
* Advisory checks.
*/
warn: number
/**
* Failed checks.
*/
fail: number
/**
* Inapplicable or prerequisite-blocked checks.
*/
skipped: number
}
/**
* One deterministic doctor check result.
*/
export interface DoctorCheck {
id: DoctorCheckId
category: DoctorCheckCategory
status: DoctorCheckStatus
/**
* Whether failure makes the project not ready.
*/
required: boolean
/**
* Human-readable result with no host-specific absolute paths.
*/
message: string
/**
* Optional actionable next command.
*/
remediation?: (DoctorRemediation | null)
}
/**
* Actionable remediation attached to a doctor check.
*/
export interface DoctorRemediation {
/**
* Command the user or agent can choose to run.
*/
command: string
cwd: DoctorProjectRoot
/**
* Whether running the command can modify project files or dependencies.
*/
mutating: boolean
}
/**
* Envelope emitted by `fallow type-aware status --format json`.
*/
export interface TypeAwareStatusOutput {
schema_version: TypeAwareStatusSchemaVersion
version: ToolVersion
/**
* Whether a usable type-aware companion was found.
*/
available: boolean
/**
* How the companion was discovered, e.g. `installed-sibling`.
*/
discovery_source?: (string | null)
/**
* Root-relative companion path, or only the executable name when the
* companion lives outside the analyzed project.
*/
companion_path?: (string | null)
/**
* npm package version of the companion, when known.
*/
package_version?: (string | null)
/**
* Type-aware protocol version fallow speaks.
*/
protocol_version: number
/**
* Checker backend family, e.g. `typescript-go`.
*/
backend_family?: (string | null)
/**
* Version of the checker backend, when known.
*/
backend_version?: (string | null)
/**
* How to make the companion available, when it is not.
*/
remediation?: (string | null)
}
/**
* Raw `fallow similar-code --format json` output.
*/
export interface SimilarCodeOutput {
schema_version: SimilarCodeSchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
generation: SimilarCodeGeneration
/**
* Deterministically ordered unverified candidates.
*/
candidates: SimilarCodeCandidate[]
completion: SimilarCodeCompletion
/**
* Non-severity diagnostics in deterministic order.
*/
diagnostics: SimilarCodeDiagnostic[]
}
/**
* Complete provenance needed to reproduce candidate generation.
*/
export interface SimilarCodeGeneration {
/**
* Version of extraction and normalization semantics used for both IDs.
*/
extraction_semantics_version: number
/**
* Version of the calculation that produces model embeddings.
*/
embedding_semantics_version: number
provider: SimilarCodeProviderProvenance
model: SimilarCodeModelProvenance
parameters: SimilarCodeGenerationParameters
scope: SimilarCodeScopeProvenance
/**
* Minimum cosine similarity admitted into the candidate set.
*/
threshold: number
/**
* Minimum source line count admitted into function extraction.
*/
min_lines: number
}
/**
* Immutable local provider provenance for one generation run.
*/
export interface SimilarCodeProviderProvenance {
provider: SimilarCodeProvider
/**
* Exact companion package version.
*/
companion_version: string
/**
* Companion protocol version negotiated for this run.
*/
protocol_version: number
/**
* Whether source content left the local machine. Version 1 requires false.
*/
source_left_machine: boolean
}
/**
* Immutable model artifact provenance.
*/
export interface SimilarCodeModelProvenance {
/**
* Stable model identifier.
*/
model_id: string
/**
* Immutable model revision.
*/
revision: string
/**
* SHA-256 digest of the exact model artifact bytes.
*/
artifact_sha256: string
/**
* SPDX license identifier or reviewed license label.
*/
license: string
/**
* Embedding vector dimensions.
*/
dimensions: number
}
/**
* Parameters that materially affect generated embeddings and scores.
*/
export interface SimilarCodeGenerationParameters {
/**
* Numeric representation used for model inference.
*/
dtype: string
/**
* Pooling strategy applied to model output.
*/
pooling: string
/**
* Whether vectors were normalized before comparison.
*/
normalized: boolean
/**
* Maximum inference batch size used by the run.
*/
batch_size: number
/**
* Maximum tokenizer length before deterministic truncation.
*/
max_tokens: number
/**
* Digest over the complete effective generation parameter set.
*/
parameter_sha256: string
}
/**
* Effective endpoint scope used for corpus admission and pair retention.
*/
export interface SimilarCodeScopeProvenance {
/**
* Whether file, changed-file, diff, or workspace scoping was active.
*/
active: boolean
/**
* Sorted project-root-relative paths satisfying every active predicate.
*/
paths: string[]
}
/**
* One unverified semantic similar-code candidate.
*/
export interface SimilarCodeCandidate {
/**
* Snapshot-stable opaque candidate identity.
*/
candidate_id: string
/**
* Content-stable key used for safe line-movement rebinding.
*/
review_key: string
left: SimilarCodeLocation
right: SimilarCodeLocation
/**
* Cosine similarity reported by the pinned provider and model.
*/
similarity: number
similarity_band: SimilarCodeSimilarityBand
verification_status: SimilarCodeVerificationStatus
enrichment: SimilarCodeEnrichmentAvailability
/**
* Read-only inspect and review affordances.
*/
actions: SimilarCodeAction[]
}
/**
* Exact named location of one candidate function.
*/
export interface SimilarCodeLocation {
/**
* Project-root-relative, forward-slash path.
*/
path: string
/**
* Extracted function or method name.
*/
name: string
/**
* One-based inclusive start line.
*/
start_line: number
/**
* One-based inclusive start column.
*/
start_column: number
/**
* One-based inclusive end line.
*/
end_line: number
/**
* One-based inclusive end column.
*/
end_column: number
/**
* SHA-256 digest of the exact extracted function source.
*/
source_sha256: string
}
/**
* Availability of every supported source-grounded enrichment.
*/
export interface SimilarCodeEnrichmentAvailability {
graph_relationship: SimilarCodeEnrichmentState
entry_point_reachability: SimilarCodeEnrichmentState
callers: SimilarCodeEnrichmentState
callees: SimilarCodeEnrichmentState
ownership: SimilarCodeEnrichmentState
churn: SimilarCodeEnrichmentState
tests: SimilarCodeEnrichmentState
deterministic_clone_coverage: SimilarCodeEnrichmentState
runtime: SimilarCodeEnrichmentState
}
/**
* Read-only follow-up exposed for a candidate.
*/
export interface SimilarCodeAction {
action: SimilarCodeActionType
/**
* Human-readable description of the read-only operation.
*/
description: string
/**
* Explicit mutation guarantee. Version 1 requires this to be true.
*/
read_only: boolean
}
/**
* Typed completion, limit, skip, and cache accounting.
*/
export interface SimilarCodeCompletion {
status: SimilarCodeCompletionStatus
/**
* Per-phase completion in pipeline order.
*/
phases: SimilarCodePhaseCompletion[]
limits: SimilarCodeLimits
/**
* Aggregated skips in phase and reason order.
*/
skips: SimilarCodeSkip[]
cache: SimilarCodeCacheSummary
/**
* Aggregate model inference wall time reported by the local provider.
*/
provider_inference_ms: number
}
/**
* Accounting for one bounded generation phase.
*/
export interface SimilarCodePhaseCompletion {
phase: SimilarCodePhase
status: SimilarCodePhaseStatus
/**
* Number of admitted inputs processed by this phase.
*/
processed: number
/**
* Total admitted inputs known to this phase, when available.
*/
total?: (number | null)
/**
* Stable explanation when the phase did not complete.
*/
reason?: (string | null)
}
/**
* Effective resource limits for a similar-code run.
*/
export interface SimilarCodeLimits {
/**
* Maximum source files admitted.
*/
max_files: number
/**
* Maximum extracted functions admitted.
*/
max_functions: number
/**
* Maximum aggregate normalized source bytes admitted.
*/
max_source_bytes: number
/**
* Maximum normalized bytes admitted for one function.
*/
max_function_bytes: number
/**
* Maximum embedding batch size.
*/
max_batch_size: number
/**
* Maximum vector bytes retained for comparison.
*/
max_vector_bytes: number
/**
* Maximum pair comparisons performed.
*/
max_comparisons: number
/**
* Maximum candidates returned.
*/
max_candidates: number
/**
* Maximum returned neighbors per function.
*/
max_neighbors_per_function: number
/**
* End-to-end timeout in milliseconds.
*/
timeout_ms: number
}
/**
* Count of skipped work for a stable reason.
*/
export interface SimilarCodeSkip {
phase: SimilarCodePhase
reason: SimilarCodeSkipReason
/**
* Number of inputs skipped for this phase and reason.
*/
count: number
}
/**
* Privacy-safe cache accounting. Source fragments are never represented.
*/
export interface SimilarCodeCacheSummary {
status: SimilarCodeCacheStatus
/**
* Valid vector cache hits.
*/
hits: number
/**
* Vector cache misses.
*/
misses: number
/**
* Newly written vector cache entries.
*/
writes: number
/**
* Corrupt or incompatible entries ignored safely.
*/
invalid_entries: number
}
/**
* Actionable diagnostic without a severity or gate implication.
*/
export interface SimilarCodeDiagnostic {
domain: SimilarCodeDiagnosticDomain
/**
* Stable machine-readable code.
*/
code: string
/**
* Bounded human-readable explanation.
*/
message: string
/**
* Optional project-root-relative path.
*/
path?: (string | null)
}
/**
* `fallow similar-code inspect --format json` output.
*/
export interface SimilarCodeInspectOutput {
schema_version: SimilarCodeInspectSchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
generation: SimilarCodeGeneration
candidate: SimilarCodeCandidate
packet: SimilarCodeInspectPacket
completion: SimilarCodeCompletion
/**
* Non-severity diagnostics in deterministic order.
*/
diagnostics: SimilarCodeDiagnostic[]
}
/**
* Bounded source-grounded packet for one immutable candidate.
*/
export interface SimilarCodeInspectPacket {
/**
* Candidate identity this packet describes.
*/
candidate_id: string
/**
* Content-stable review key this packet describes.
*/
review_key: string
availability: SimilarCodeEnrichmentAvailability
/**
* Graph relationship label, when relationship evidence is available.
*/
graph_relationship?: (string | null)
left: SimilarCodeSideEvidence
right: SimilarCodeSideEvidence
}
/**
* Bounded evidence for one side of an inspect packet.
*/
export interface SimilarCodeSideEvidence {
/**
* Bounded source window included only in inspect output, never raw output or cache.
*/
source_window?: (string | null)
/**
* Declared parameter count when extraction supplied it.
*/
parameter_count?: (number | null)
/**
* Whether the inspected function is declared async.
*/
is_async?: (boolean | null)
/**
* Whether the inspected function is a generator.
*/
is_generator?: (boolean | null)
/**
* Whether the inspected function contains an await expression.
*/
has_await?: (boolean | null)
/**
* Whether the inspected function contains a throw expression.
*/
has_throw?: (boolean | null)
/**
* Conservative syntactic side-effect classification.
*/
side_effect_hint?: (SimilarCodeSideEffectHint | null)
/**
* Whether the function is reachable from a configured entry point.
*/
entry_point_reachable?: (boolean | null)
/**
* Bounded, deterministically ordered direct callers.
*/
callers: SimilarCodeNamedReference[]
/**
* Bounded, deterministically ordered direct callees.
*/
callees: SimilarCodeNamedReference[]
/**
* Bounded, deterministically ordered ownership labels.
*/
owners: string[]
/**
* Recent commit count in the configured churn window.
*/
churn_commits?: (number | null)
/**
* Bounded, root-relative related test paths.
*/
tests: string[]
/**
* Fraction covered by deterministic clone groups, from zero through one.
*/
deterministic_clone_coverage?: (number | null)
/**
* Runtime observation count when compatible runtime evidence is present.
*/
runtime_observations?: (number | null)
}
/**
* One named graph reference used in an inspect packet.
*/
export interface SimilarCodeNamedReference {
/**
* Project-root-relative, forward-slash path.
*/
path: string
/**
* Referenced symbol name.
*/
name: string
/**
* One-based source line.
*/
line: number
}
/**
* `fallow similar-code review --format json` output.
*/
export interface SimilarCodeReviewOutput {
schema_version: SimilarCodeReviewSchemaVersion
version: ToolVersion
elapsed_ms: ElapsedMs
generation: SimilarCodeGeneration
review: SimilarCodeReviewProvenance
/**
* Raw candidates joined with verdicts in candidate order.
*/
candidates: SimilarCodeReviewedCandidate[]
completion: SimilarCodeCompletion
/**
* Non-severity diagnostics in deterministic order.
*/
diagnostics: SimilarCodeDiagnostic[]
}
/**
* Digests that make the review join reproducible without exposing source.
*/
export interface SimilarCodeReviewProvenance {
/**
* SHA-256 digest of the exact candidate JSON input bytes.
*/
candidates_sha256: string
/**
* SHA-256 digest of the exact verdict JSON input bytes.
*/
verdicts_sha256: string
}
/**
* One candidate joined with its separate verdict, if safely matched.
*/
export interface SimilarCodeReviewedCandidate {
candidate: SimilarCodeCandidate
/**
* Safely matched external verdict, absent when still unverified.
*/
verdict?: (SimilarCodeVerdict | null)
verdict_match: SimilarCodeVerdictMatch
outcome: SimilarCodeDomainOutcome
}
/**
* Separate verdict input for one immutable candidate.
*/
export interface SimilarCodeVerdict {
/**
* Snapshot identity from the raw candidate.
*/
candidate_id: string
/**
* Content-stable identity from the raw candidate.
*/
review_key: string
/**
* Whether the pair is useful enough to review. Null means undecided.
*/
candidate_worthy?: (boolean | null)
/**
* Whether the two functions behave equivalently. Null means undecided.
*/
behaviorally_equivalent?: (boolean | null)
/**
* Whether consolidation is safe. Null means undecided.
*/
refactor_safe?: (boolean | null)
outcome: SimilarCodeDomainOutcome
/**
* Bounded explanation grounded in the inspected sources.
*/
rationale: string
}
/**
* Single CodeClimate-compatible issue inside [`CodeClimateOutput`].
*/
export interface CodeClimateIssue {
type: CodeClimateIssueKind
/**
* Fallow rule identifier, e.g. `fallow/unused-file`.
*/
check_name: string
/**
* Human-readable finding description.
*/
description: string
/**
* CodeClimate category labels, e.g. `Clarity` or `Duplication`.
*/
categories: string[]
severity: CodeClimateSeverity
/**
* Stable finding fingerprint GitLab uses to track issues across pushes.
*/
fingerprint: string
location: CodeClimateLocation
/**
* Other source locations that provide evidence for the finding. GitLab's
* Code Quality widget ignores this standard CodeClimate field, but Fallow
* preserves it for review-comment rendering.
*/
other_locations?: CodeClimateLocation[]
/**
* Optional owner attribution used by grouped dead-code output.
*/
owner?: (string | null)
/**
* Optional grouping attribution used by grouped health and duplication
* output.
*/
group?: (string | null)
}
/**
* Location block inside [`CodeClimateIssue::location`].
*/
export interface CodeClimateLocation {
/**
* File path relative to the analysed root.
*/
path: string
lines: CodeClimateLines
}
/**
* Inclusive line range for [`CodeClimateLocation`].
*/
export interface CodeClimateLines {
/**
* 1-based start line.
*/
begin: number
/**
* Inclusive 1-based end line. Omitted for point findings.
*/
end?: (number | null)
}
/**
* Structured JSON error emitted on stdout when `--format json` is active and a
* command fails. It carries no `kind` discriminator: it is distinguished from
* the kind-tagged success envelopes by the required `error: true` field, and is
* a document-root branch alongside `FallowOutput` and `CodeClimateOutput` in
* `docs/output-schema.json`. Agents that pass `--format json` and observe a
* non-zero exit code parse this shape from stdout.
*/
export interface ErrorOutput {
/**
* Always `true`. The discriminator that separates an error document from a
* success envelope (which instead carries a `kind`).
*/
error: boolean
/**
* Human-readable error message.
*/
message: string
/**
* The process exit code the CLI returns alongside this document.
*/
exit_code: number
/**
* Stable machine-readable code such as `FALLOW_INVALID_COVERAGE_PATH`,
* when the failure has one. Present so an agent can branch on the reason
* without pattern-matching the human message.
*/
code?: (string | null)
/**
* Remediation hint for the caller, when the failure has one.
*/
help?: (string | null)
}
/**
* @deprecated Legacy alias for the dead-code/check schema version. Use the exact envelope-specific alias instead.
*/
export type SchemaVersion = CheckSchemaVersion;
/**
* 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: legacy output aliases remain supported throughout v3.
// Removing them requires an explicit deprecation period and a future major
// release. 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 `DeprecatedExportInUse` name.
* The wire shape is byte-identical: `DeprecatedExportInUseFinding` flattens the bare
* finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
* the optional audit-mode `introduced` flag. Consumers that imported
* `DeprecatedExportInUse` from `fallow/types` pre-migration continue to work via
* this alias; new code should prefer `DeprecatedExportInUseFinding`.
*/
export type DeprecatedExportInUse = DeprecatedExportInUseFinding;
/**
* Backwards-compat alias for the pre-#384 bare `DevDependencyInProduction` name.
* The wire shape is byte-identical: `DevDependencyInProductionFinding` flattens the bare
* finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
* the optional audit-mode `introduced` flag. Consumers that imported
* `DevDependencyInProduction` from `fallow/types` pre-migration continue to work via
* this alias; new code should prefer `DevDependencyInProductionFinding`.
*/
export type DevDependencyInProduction = DevDependencyInProductionFinding;
/**
* 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 `PackageCycle` name.
* The wire shape is byte-identical: `PackageCycleFinding` flattens the bare
* finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
* the optional audit-mode `introduced` flag. Consumers that imported
* `PackageCycle` from `fallow/types` pre-migration continue to work via
* this alias; new code should prefer `PackageCycleFinding`.
*/
export type PackageCycle = PackageCycleFinding;
/**
* 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;
/**
* @deprecated Renamed to BaselineStaleness in 3.27.0, when dead-code and dupes started carrying the same shape. The members are unchanged.
*/
export type HealthBaselineStaleness = BaselineStaleness;