fallow 2.85.0 → 2.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,33 +25,20 @@
25
25
 
26
26
 
27
27
  /**
28
- * Schemas for the JSON output of fallow commands. To identify which envelope you have, check for the unique top-level field: `summary.total_issues` (check), `health_score` (health), `clone_groups` (dupes), `runtime_coverage` (coverage analyze), `boundaries` (list --boundaries), `command: "audit"` (audit), `body` plus `comments` (review-github / review-gitlab), `schema: "fallow-review-reconcile/v1"` (ci reconcile-review), `framework_detected` plus `members` (coverage setup), `id` plus `how_to_fix` (explain), `check`+`dupes`+`health` keys together (bare combined invocation). `HealthOutput` and `DupesOutput` flatten their body (`HealthReport` / `DupesReportPayload`) into top-level fields, so the discriminator field is from the body shape itself, not a wrapper key. Every object-shaped envelope is a variant of `FallowOutput`; `CodeClimateOutput` is a bare JSON array (per the Code Climate / GitLab Code Quality spec) and stays a sibling root branch.
28
+ * Schemas for the JSON output of fallow commands. Object-shaped envelopes covered by the `FallowOutput` contract carry a top-level `kind` discriminator (for example `dead-code`, `dead-code-grouped`, `health`, `dupes`, `combined`, `audit`, `explain`, `impact`, `security`, `coverage-setup`, `coverage-analyze`, `list-boundaries`, `review-envelope`, and `review-reconcile`). Consumers should branch on `kind` instead of probing for unique field presence. `--legacy-envelope` removes only the document-root `kind` for one compatibility cycle. `CodeClimateOutput` is a bare JSON array (per the Code Climate / GitLab Code Quality spec) and stays a sibling root branch discriminated by checking whether the document root is an array.
29
29
  */
30
30
  export type FallowJsonOutput = (FallowOutput | CodeClimateOutput)
31
31
  /**
32
- * Typed root of every fallow `--format json` envelope shape that
33
- * serializes as a JSON object. The schema derived from this enum drives
34
- * the document-root `oneOf` in `docs/output-schema.json`, replacing the
35
- * previously hand-maintained block.
32
+ * Typed root of every fallow JSON envelope shape that serializes as a JSON
33
+ * object and participates in the documented `FallowOutput` contract. The
34
+ * schema derived from this enum drives the document-root `oneOf` in
35
+ * `docs/output-schema.json`.
36
36
  *
37
- * `#[serde(untagged)]` preserves wire compatibility: consumers see exactly
38
- * the same top-level keys today (`schema_version`, `version`, plus the
39
- * per-envelope shape). The schema's `oneOf` lets agents narrow by trying
40
- * variants in order; field sets differ enough that the first matching
41
- * variant is the correct one in practice. Note that [`HealthOutput`] and
42
- * [`DupesOutput`] flatten their inner body (`HealthReport` /
43
- * `DuplicationReport`) into top-level fields, so the actual
44
- * discriminators are nested-body keys such as `health_score` (health) and
45
- * `clone_groups` (dupes), NOT `report` or `groups`.
46
- *
47
- * Variant order is **most-specific first**. Schemars 1 preserves
48
- * declaration order in the emitted `oneOf`, and validators that enforce
49
- * strict `oneOf` (and any future migration that adds `Deserialize`) will
50
- * try branches top-to-bottom. The required-field sets shrink as we move
51
- * down the list, with [`CombinedOutput`] last because its three required
52
- * fields (`schema_version`, `version`, `elapsed_ms`) are a strict subset
53
- * of every other variant's required set; placing it earlier would let a
54
- * `CheckOutput` payload silently match `CombinedOutput` first.
37
+ * The default wire shape now carries a top-level `kind` discriminator so
38
+ * agents and schema-validating clients can select the variant in O(1) instead
39
+ * of probing for unique field presence. `--legacy-envelope` is a one-cycle
40
+ * compatibility flag that removes only this document-root `kind` field from
41
+ * CLI JSON output; nested report objects are not rewritten.
55
42
  *
56
43
  * One envelope is intentionally NOT in this enum:
57
44
  * - `CodeClimateOutput` serializes as a bare JSON array
@@ -59,13 +46,36 @@ export type FallowJsonOutput = (FallowOutput | CodeClimateOutput)
59
46
  * spec; `#[serde(tag = ...)]` cannot internally tag a non-object
60
47
  * variant and wrapping the array would break the spec. The root schema
61
48
  * carries it as a sibling `oneOf` branch alongside `FallowOutput`.
62
- *
63
- * A future major release plans to switch this to
64
- * `#[serde(tag = "kind")]` for true O(1) discriminability on AI / agent
65
- * consumers, paired with a one-cycle `--legacy-envelope` opt-out flag.
66
- * Tracked under issue #384.
67
49
  */
68
- export type FallowOutput = (AuditOutput | ExplainOutput | ReviewEnvelopeOutput | ReviewReconcileOutput | CoverageSetupOutput | CoverageAnalyzeOutput | ListBoundariesOutput | HealthOutput | DupesOutput | CheckGroupedOutput | ImpactReport | CheckOutput | CombinedOutput)
50
+ export type FallowOutput = ((AuditOutput & {
51
+ kind: "audit"
52
+ }) | (ExplainOutput & {
53
+ kind: "explain"
54
+ }) | (ReviewEnvelopeOutput & {
55
+ kind: "review-envelope"
56
+ }) | (ReviewReconcileOutput & {
57
+ kind: "review-reconcile"
58
+ }) | (CoverageSetupOutput & {
59
+ kind: "coverage-setup"
60
+ }) | (CoverageAnalyzeOutput & {
61
+ kind: "coverage-analyze"
62
+ }) | (ListBoundariesOutput & {
63
+ kind: "list-boundaries"
64
+ }) | (HealthOutput & {
65
+ kind: "health"
66
+ }) | (DupesOutput & {
67
+ kind: "dupes"
68
+ }) | (CheckGroupedOutput & {
69
+ kind: "dead-code-grouped"
70
+ }) | (ImpactReport & {
71
+ kind: "impact"
72
+ }) | (SecurityOutput & {
73
+ kind: "security"
74
+ }) | (CheckOutput & {
75
+ kind: "dead-code"
76
+ }) | (CombinedOutput & {
77
+ kind: "combined"
78
+ }))
69
79
  /**
70
80
  * Schema version for this output format (independent of tool version). Bump
71
81
  * policy: ADDITIVE changes (new optional top-level fields, new optional struct
@@ -87,15 +97,12 @@ export type FallowOutput = (AuditOutput | ExplainOutput | ReviewEnvelopeOutput |
87
97
  * additive examples: dupes --group-by adds optional grouped_by, total_issues,
88
98
  * groups fields without bumping.
89
99
  */
90
- export type SchemaVersion = 6
100
+ export type SchemaVersion = 7
91
101
  /**
92
102
  * Fallow CLI version that produced this envelope. Renders to the JSON wire as
93
103
  * a bare string (e.g. `"2.74.0"`).
94
104
  */
95
105
  export type ToolVersion = string
96
- /**
97
- * Singleton `command` discriminator for [`AuditOutput`].
98
- */
99
106
  export type AuditCommand = "audit"
100
107
  /**
101
108
  * Verdict for the audit command.
@@ -106,9 +113,6 @@ export type AuditVerdict = ("pass" | "warn" | "fail")
106
113
  * integer.
107
114
  */
108
115
  export type ElapsedMs = number
109
- /**
110
- * Gating mode for `fallow audit`.
111
- */
112
116
  export type AuditGate = ("new-only" | "all")
113
117
  /**
114
118
  * A suggested action attached to a finding in the JSON output. Each finding
@@ -220,17 +224,6 @@ export type AuditIntroduced = boolean
220
224
  export type DependencyLocation = ("dependencies" | "devDependencies" | "optionalDependencies")
221
225
  /**
222
226
  * The kind of member.
223
- *
224
- * # Examples
225
- *
226
- * ```
227
- * use fallow_types::extract::MemberKind;
228
- *
229
- * let kind = MemberKind::EnumMember;
230
- * assert_eq!(kind, MemberKind::EnumMember);
231
- * assert_ne!(kind, MemberKind::ClassMethod);
232
- * assert_ne!(MemberKind::ClassMethod, MemberKind::ClassProperty);
233
- * ```
234
227
  */
235
228
  export type MemberKind = ("enum_member" | "class_method" | "class_property" | "namespace_member")
236
229
  /**
@@ -354,43 +347,10 @@ export type ExceededThreshold = ("cyclomatic" | "cognitive" | "both" | "crap" |
354
347
  export type FindingSeverity = ("moderate" | "high" | "critical")
355
348
  /**
356
349
  * Coverage tier classification for CRAP findings.
357
- *
358
- * Bucketed coverage signal that lets action consumers (AI agents, IDE
359
- * extensions, CI integrations) pick the right remediation without knowing
360
- * the underlying coverage values:
361
- * - `None`: file has no test reachability (estimated model 0% band) or
362
- * Istanbul data shows 0% statement coverage. The right action is
363
- * "add tests from scratch."
364
- * - `Partial`: some coverage exists (estimated model 40% band, or
365
- * Istanbul shows >0% but below the high watermark). The right
366
- * action is "increase coverage on uncovered branches."
367
- * - `High`: coverage is at or above the high watermark (estimated model
368
- * 85% band, or Istanbul shows >= 70%). Action selection still checks
369
- * the CRAP formula before deciding whether coverage or refactoring is
370
- * the better remediation.
371
- *
372
- * The high watermark default is 70 (matches Istanbul `lines: 70`).
373
- * Partial is anything in `(0, 70)`. None is `<= 0`.
374
350
  */
375
351
  export type CoverageTier = ("none" | "partial" | "high")
376
352
  /**
377
353
  * Provenance of a CRAP finding's coverage signal.
378
- *
379
- * Discriminates whether the `coverage_tier` and `crap` score were derived
380
- * from real Istanbul data, the graph-based estimated model evaluated against
381
- * the finding's own file, or the graph-based estimated model evaluated
382
- * against a different file (today: an Angular component `.ts` reached via
383
- * the inverse `templateUrl` edge from a synthetic `<template>` finding on
384
- * the component's `.html` template).
385
- *
386
- * Consumers reading this field:
387
- * - AI agents picking remediation actions ("the score is inherited, the fix
388
- * may need to land on the component file, not the template").
389
- * - Dashboards plotting CRAP trends ("the discriminator changed shape;
390
- * absorb the rollout rather than flagging a step change").
391
- * - Future tier 2 (AOT source-map back-mapping) will introduce
392
- * `MeasuredAotSourceMap` so consumers can distinguish measured-AOT from
393
- * inherited-JIT without parsing the score itself.
394
354
  */
395
355
  export type CoverageSource = ("istanbul" | "estimated" | "estimated_component_inherited")
396
356
  /**
@@ -406,6 +366,10 @@ export type HealthFindingActionType = ("refactor-function" | "add-tests" | "incr
406
366
  * Coverage model used for CRAP score computation.
407
367
  */
408
368
  export type CoverageModel = ("static_binary" | "static_estimated" | "istanbul")
369
+ /**
370
+ * Whether CRAP findings in the report used one coverage-source kind or a mix.
371
+ */
372
+ export type CoverageSourceConsistency = ("uniform" | "mixed")
409
373
  /**
410
374
  * Discriminant for [`UntestedFileAction::kind`]. Mirrors the action types
411
375
  * emitted by `build_untested_file_actions`.
@@ -420,13 +384,7 @@ export type UntestedExportActionType = ("add-test-import" | "suppress-file")
420
384
  * Churn trend indicator based on comparing recent vs older halves of the analysis period.
421
385
  */
422
386
  export type ChurnTrend = ("accelerating" | "stable" | "cooling")
423
- /**
424
- * Format discriminator for [`ContributorEntry::identifier`].
425
- */
426
387
  export type ContributorIdentifierFormat = ("raw" | "handle" | "anonymized" | "hash")
427
- /**
428
- * Machine-readable ownership state for a hotspot.
429
- */
430
388
  export type OwnershipState = ("active" | "unowned" | "declared_inactive" | "drifting")
431
389
  /**
432
390
  * Discriminant for [`HotspotAction::kind`].
@@ -594,34 +552,13 @@ export type ReviewCheckConclusion = ("success" | "neutral" | "failure")
594
552
  * Schema-version discriminator for the review reconcile envelope.
595
553
  */
596
554
  export type ReviewReconcileSchema = "fallow-review-reconcile/v1"
597
- /**
598
- * Singleton schema-version discriminator for [`CoverageSetupOutput`].
599
- */
600
555
  export type CoverageSetupSchemaVersion = "1"
601
- /**
602
- * Framework label inside coverage setup output.
603
- */
604
556
  export type CoverageSetupFramework = ("nextjs" | "nestjs" | "nuxt" | "sveltekit" | "astro" | "remix" | "vite" | "plain_node" | "unknown")
605
- /**
606
- * Package manager label inside coverage setup output.
607
- */
608
557
  export type CoverageSetupPackageManager = ("npm" | "pnpm" | "yarn" | "bun")
609
- /**
610
- * Runtime target inside coverage setup output.
611
- */
612
558
  export type CoverageSetupRuntimeTarget = ("node" | "browser")
613
- /**
614
- * Singleton schema-version discriminator for [`CoverageAnalyzeOutput`].
615
- * Independent from the global [`SchemaVersion`] because the runtime
616
- * coverage envelope versions independently from the rest of the
617
- * JSON contract.
618
- */
619
559
  export type CoverageAnalyzeSchemaVersion = "1"
620
560
  /**
621
- * Discovery outcome for a [`LogicalGroup`]. Discriminates "no children" into
622
- * "the directory exists and is empty" versus "at least one `autoDiscover`
623
- * path was invalid or unreadable", so consumers can render an actionable
624
- * hint instead of "0 children, mystery".
561
+ * Discovery outcome for a [`LogicalGroup`].
625
562
  */
626
563
  export type LogicalGroupStatus = ("ok" | "empty" | "invalid_path")
627
564
  /**
@@ -645,6 +582,20 @@ export type ImpactReportSchemaVersion = "1"
645
582
  * Direction of a count trend between two recorded runs.
646
583
  */
647
584
  export type ImpactTrendDirection = ("improving" | "declining" | "stable")
585
+ /**
586
+ * The `fallow security --format json` schema version. Independently versioned
587
+ * from the main contract, mirroring `ImpactReportSchemaVersion`.
588
+ */
589
+ export type SecuritySchemaVersion = "1"
590
+ /**
591
+ * The kind of security candidate. Findings are CANDIDATES for downstream agent
592
+ * verification, NOT verified vulnerabilities.
593
+ */
594
+ export type SecurityFindingKind = ("client-server-leak" | "tainted-sink")
595
+ /**
596
+ * The role a hop plays in a security finding's structural import trace.
597
+ */
598
+ export type TraceHopRole = ("client-boundary" | "intermediate" | "secret-source" | "sink")
648
599
  /**
649
600
  * Discriminator value for [`CodeClimateIssue::kind`].
650
601
  */
@@ -661,69 +612,22 @@ export type CodeClimateSeverity = ("info" | "minor" | "major" | "critical" | "bl
661
612
  export type CodeClimateOutput = CodeClimateIssue[]
662
613
 
663
614
  /**
664
- * Envelope emitted by `fallow audit --format json`. Combines dead code,
665
- * complexity, and duplication scoped to changed files with a verdict
666
- * (`pass` / `warn` / `fail`), a per-category summary, optional
667
- * new-vs-inherited attribution, and full sub-results.
668
- *
669
- * Like [`CombinedOutput`], `audit`'s `duplication` and `complexity`
670
- * sub-keys hold body shapes rather than per-command envelopes:
671
- * `duplication` is [`DupesReportPayload`] (the typed wrapper payload
672
- * emitted via `crate::output_dupes::DupesReportPayload::from_report`),
673
- * `complexity` is [`HealthReport`]. `dead_code` is the full
674
- * [`CheckOutput`] envelope. The committed schema points `duplication`
675
- * at `#/definitions/DupesReportPayload` and `complexity` at
676
- * `#/definitions/HealthReport` so the documented shape matches the
677
- * wire; the `committed_property_refs_match_derived_property_refs`
678
- * drift test enforces the alignment.
615
+ * `fallow audit --format json` envelope.
679
616
  */
680
617
  export interface AuditOutput {
681
618
  schema_version: SchemaVersion
682
619
  version: ToolVersion
683
620
  command: AuditCommand
684
621
  verdict: AuditVerdict
685
- /**
686
- * Number of files changed between base ref and HEAD.
687
- */
688
622
  changed_files_count: number
689
- /**
690
- * Git ref used as comparison base (explicit or auto-detected).
691
- */
692
623
  base_ref: string
693
- /**
694
- * Short SHA of HEAD. Omitted when git is unavailable.
695
- */
696
624
  head_sha?: (string | null)
697
625
  elapsed_ms: ElapsedMs
698
- /**
699
- * Only emitted when --performance is set. true means audit reused the
700
- * current run's keys as the base snapshot because every changed file was
701
- * either a non-behavioral doc or token-equivalent at the base ref (the
702
- * docs-only-diff fast path); false means the regular base worktree
703
- * analysis ran.
704
- */
705
626
  base_snapshot_skipped?: (boolean | null)
706
627
  summary: AuditSummary
707
628
  attribution: AuditAttribution
708
- /**
709
- * Full dead code results (omitted if no changed files). Issue objects
710
- * include introduced: true/false when audit can compare against the base
711
- * ref.
712
- */
713
629
  dead_code?: (CheckOutput | null)
714
- /**
715
- * Full duplication results (omitted if no changed files). Clone groups
716
- * include introduced: true/false when audit can compare against the base
717
- * ref. Carries typed [`crate::output_dupes::CloneGroupFinding`] and
718
- * [`crate::output_dupes::CloneFamilyFinding`] wrappers (matches what
719
- * `crates/cli/src/audit.rs` emits via
720
- * `crate::output_dupes::DupesReportPayload::from_report`).
721
- */
722
630
  duplication?: (DupesReportPayload | null)
723
- /**
724
- * Full complexity results (omitted if no changed files). Findings include
725
- * introduced: true/false when audit can compare against the base ref.
726
- */
727
631
  complexity?: (HealthReport | null)
728
632
  }
729
633
  /**
@@ -763,14 +667,7 @@ export interface CheckOutput {
763
667
  schema_version: SchemaVersion
764
668
  version: ToolVersion
765
669
  elapsed_ms: ElapsedMs
766
- /**
767
- * Total number of issues found across all categories.
768
- */
769
670
  total_issues: number
770
- /**
771
- * Entry-point detection summary. Present when the analysis populated
772
- * the metadata block; absent in synthesised fixtures.
773
- */
774
671
  entry_points?: (EntryPoints | null)
775
672
  summary: CheckSummary
776
673
  /**
@@ -925,34 +822,10 @@ unused_dependency_overrides?: UnusedDependencyOverrideFinding[]
925
822
  * error. Wrapped in [`MisconfiguredDependencyOverrideFinding`].
926
823
  */
927
824
  misconfigured_dependency_overrides?: MisconfiguredDependencyOverrideFinding[]
928
- /**
929
- * Per-category delta comparison against a saved baseline. Only present
930
- * when `--baseline` is used (today only via the combined invocation).
931
- */
932
825
  baseline_deltas?: (BaselineDeltas | null)
933
- /**
934
- * Baseline match statistics. Only present when `--baseline` is used.
935
- */
936
826
  baseline?: (BaselineMatch | null)
937
- /**
938
- * Regression check result. Only present when `--fail-on-regression` is
939
- * used.
940
- */
941
827
  regression?: (RegressionResult | null)
942
- /**
943
- * `_meta` block with metric / rule definitions, emitted when `--explain`
944
- * is passed (always present in MCP responses).
945
- */
946
828
  _meta?: (Meta | null)
947
- /**
948
- * Workspace-discovery diagnostics surfaced by
949
- * `discover_workspaces_with_diagnostics` (issue #473): malformed
950
- * declared-workspace `package.json`, glob matches with no `package.json`,
951
- * malformed `tsconfig.json`, missing tsconfig reference paths. Omitted
952
- * when empty so consumers on monorepos without discovery noise see no
953
- * new field. Pairing of `#[serde(default, skip_serializing_if = ...)]`
954
- * is required for schemars to mark the field non-required.
955
- */
956
829
  workspace_diagnostics?: WorkspaceDiagnostic[]
957
830
  }
958
831
  /**
@@ -2643,177 +2516,41 @@ actions_meta?: (HealthActionsMeta | null)
2643
2516
  }
2644
2517
  /**
2645
2518
  * Wire envelope for a single complexity finding.
2646
- *
2647
- * Flattens [`ComplexityViolation`] for wire continuity and adds the typed
2648
- * `actions` list plus the audit-mode `introduced` flag. The
2649
- * `#[serde(flatten)]` keeps each `findings[]` item byte-identical to the
2650
- * pre-wrapper shape: inner fields (`path`, `name`, `line`, `cyclomatic`,
2651
- * ...) sit at the top level alongside `actions` and optional `introduced`.
2652
- *
2653
- * Construct via [`HealthFinding::with_actions`] in the typical health
2654
- * pipeline (the wrapper computes its own `actions` from a
2655
- * [`HealthActionContext`]) or via [`HealthFinding::new`] when the caller
2656
- * already has the action list (e.g., tests, audit cross-attribution).
2657
2519
  */
2658
2520
  export interface HealthFinding {
2659
- /**
2660
- * Absolute file path.
2661
- */
2662
2521
  path: string
2663
- /**
2664
- * Function name, `"<anonymous>"` for unnamed functions/arrows, or
2665
- * `"<template>"` for synthetic Angular template findings.
2666
- */
2667
2522
  name: string
2668
- /**
2669
- * 1-based line number.
2670
- */
2671
2523
  line: number
2672
- /**
2673
- * 0-based column.
2674
- */
2675
2524
  col: number
2676
- /**
2677
- * Cyclomatic complexity.
2678
- */
2679
2525
  cyclomatic: number
2680
- /**
2681
- * SonarSource cognitive complexity (structural + nesting penalty).
2682
- */
2683
2526
  cognitive: number
2684
- /**
2685
- * Number of lines in the function.
2686
- */
2687
2527
  line_count: number
2688
- /**
2689
- * Number of parameters (excluding TypeScript's this parameter).
2690
- */
2691
2528
  param_count: number
2692
2529
  exceeded: ExceededThreshold
2693
2530
  severity: FindingSeverity
2694
- /**
2695
- * CRAP score (`CC^2 * (1 - cov/100)^3 + CC`), rounded to one decimal.
2696
- * Present when the function also exceeded `--max-crap`, otherwise absent.
2697
- */
2698
2531
  crap?: (number | null)
2699
- /**
2700
- * Per-function statement coverage percentage (0.0 to 100.0) used to
2701
- * derive `crap`. Present when Istanbul data matched the function,
2702
- * otherwise absent (estimated model or unmatched functions).
2703
- */
2704
2532
  coverage_pct?: (number | null)
2705
- /**
2706
- * Bucketed coverage tier used to drive action selection. Present whenever
2707
- * CRAP triggered the finding (Istanbul or estimated), absent otherwise.
2708
- * `none` = coverage is at most 0% (file not test-reachable, or Istanbul
2709
- * reports 0); `partial` = coverage is in `(0, 70)`; `high` = coverage is
2710
- * at or above the high watermark (default `>= 70`, or the estimated 85%
2711
- * band).
2712
- */
2713
2533
  coverage_tier?: (CoverageTier | null)
2714
- /**
2715
- * Provenance of the coverage signal. Present whenever CRAP triggered the
2716
- * finding. `istanbul` = direct fnMap match; `estimated` = graph-based
2717
- * estimate against the finding's own file; `estimated_component_inherited`
2718
- * = graph-based estimate inherited from an Angular component `.ts`
2719
- * reached via the inverse `templateUrl` edge (synthetic `<template>`
2720
- * findings on `.html` files only).
2721
- */
2722
2534
  coverage_source?: (CoverageSource | null)
2723
- /**
2724
- * Owning component file that contributed reachability when
2725
- * `coverage_source == "estimated_component_inherited"`. Always paired
2726
- * with that variant of `coverage_source` and absent otherwise. The
2727
- * value is the `.ts` file fallow walked to via the inverse `templateUrl`
2728
- * edge (e.g. `permissions.component.ts`); the JSON serializer strips it
2729
- * to project-relative form just like other path fields. Lets human and
2730
- * AI consumers explain "the template scored partial because the
2731
- * component it belongs to is tested" without re-deriving the link.
2732
- */
2733
2535
  inherited_from?: (string | null)
2734
- /**
2735
- * Breakdown of a synthetic `<component>` rollup finding into its
2736
- * worst-class-function and template contributions. Present only on
2737
- * findings whose [`name`](Self::name) is the literal string
2738
- * `"<component>"` (Angular components whose class AND template both
2739
- * contributed to a per-component complexity rollup); absent on every
2740
- * other finding kind.
2741
- *
2742
- * The owning [`HealthFinding`](crate::health_types::HealthFinding)'s
2743
- * [`cyclomatic`](Self::cyclomatic) / [`cognitive`](Self::cognitive)
2744
- * totals are `class_worst_function + template`, so consumers ranking
2745
- * by complexity see the component as one unit. The breakdown carries
2746
- * the pre-summation numbers plus the worst class function's name so
2747
- * consumers can explain "this component ranked high because the
2748
- * template added 6 cyclomatic on top of the worst class function's 3".
2749
- */
2750
2536
  component_rollup?: (ComponentRollup | null)
2751
2537
  /**
2752
- * Machine-actionable fix and suppress hints. Always populated; never
2753
- * empty in the typical pipeline (the action selector emits at least
2754
- * `suppress-line` or `suppress-file` unless suppressed by the
2755
- * context).
2538
+ * Machine-actionable fix and suppress hints.
2756
2539
  */
2757
2540
  actions: HealthFindingAction[]
2758
2541
  /**
2759
- * Audit-mode flag indicating whether the finding is new versus the
2760
- * audit base snapshot. `Some(true)` when introduced in the diff,
2761
- * `Some(false)` when present in both snapshots, `None` outside audit
2762
- * mode (the field is skipped from the wire).
2542
+ * Audit-mode flag indicating whether the finding is new versus the base
2543
+ * snapshot.
2763
2544
  */
2764
2545
  introduced?: (boolean | null)
2765
2546
  }
2766
- /**
2767
- * Per-component breakdown attached to a synthetic `<component>`
2768
- * [`HealthFinding`](crate::health_types::HealthFinding). See
2769
- * [`ComplexityViolation::component_rollup`] for the owning-finding
2770
- * contract; the wrapper flattens the inner type's
2771
- * [`component_rollup`](ComplexityViolation::component_rollup) field
2772
- * onto its own wire shape.
2773
- */
2774
2547
  export interface ComponentRollup {
2775
- /**
2776
- * Angular component class name (e.g. `"HostGameComponent"`). Derived
2777
- * from the worst class function's `ClassName.methodName` identifier.
2778
- */
2779
2548
  component: string
2780
- /**
2781
- * Name of the worst class function/method whose individual cyclomatic
2782
- * is the largest among the component's class findings (e.g.
2783
- * `"ngOnInit"`). When two methods tie on cyclomatic the first by
2784
- * iteration order wins; consumers should treat the choice as
2785
- * representative, not authoritative.
2786
- */
2787
2549
  class_worst_function: string
2788
- /**
2789
- * Cyclomatic complexity of the worst class function alone (the
2790
- * `class_worst_function`).
2791
- */
2792
2550
  class_cyclomatic: number
2793
- /**
2794
- * Cognitive complexity of the worst class function alone.
2795
- */
2796
2551
  class_cognitive: number
2797
- /**
2798
- * Path of the Angular template that contributed to the rollup.
2799
- * External-template components use the `.html` template file path;
2800
- * inline-template components use the owning `.ts` itself (since the
2801
- * `<template>` finding for inline templates is anchored at the
2802
- * component's `@Component` decorator on the same file). Stored
2803
- * absolute internally; the JSON output strips it to project-relative
2804
- * form via the global `strip_root_prefix` post-pass (as with every
2805
- * other `PathBuf` field in this crate).
2806
- */
2807
2552
  template_path: string
2808
- /**
2809
- * Cyclomatic complexity contributed by the template alone (control
2810
- * flow on `*ngIf` / `*ngFor` / `@if` / `@for` / `@switch` etc.).
2811
- */
2812
2553
  template_cyclomatic: number
2813
- /**
2814
- * Cognitive complexity contributed by the template alone (nesting +
2815
- * branching penalty on the same constructs as `template_cyclomatic`).
2816
- */
2817
2554
  template_cognitive: number
2818
2555
  }
2819
2556
  /**
@@ -2882,77 +2619,20 @@ target_path?: (string | null)
2882
2619
  * Summary statistics for the health report.
2883
2620
  */
2884
2621
  export interface HealthSummary {
2885
- /**
2886
- * Number of files analyzed.
2887
- */
2888
2622
  files_analyzed: number
2889
- /**
2890
- * Total number of functions found.
2891
- */
2892
2623
  functions_analyzed: number
2893
- /**
2894
- * Number of functions exceeding at least one threshold (before --top
2895
- * truncation).
2896
- */
2897
2624
  functions_above_threshold: number
2898
- /**
2899
- * Configured cyclomatic threshold.
2900
- */
2901
2625
  max_cyclomatic_threshold: number
2902
- /**
2903
- * Configured cognitive threshold.
2904
- */
2905
2626
  max_cognitive_threshold: number
2906
- /**
2907
- * Configured CRAP (Change Risk Anti-Patterns) score threshold. Functions
2908
- * meeting or exceeding this score appear as findings with the `crap` and
2909
- * optional `coverage_pct` fields populated.
2910
- */
2911
2627
  max_crap_threshold: number
2912
- /**
2913
- * Number of files with health scores. Only present when --file-scores is
2914
- * used. 0 indicates the flag was set but scoring failed.
2915
- */
2916
2628
  files_scored?: (number | null)
2917
- /**
2918
- * Average maintainability index across all scored files (before --top
2919
- * truncation). Only present when --file-scores is used and at least one
2920
- * file was scored.
2921
- */
2922
2629
  average_maintainability?: (number | null)
2923
- /**
2924
- * Coverage model used for CRAP score computation. 'static_estimated'
2925
- * (default) uses per-function graph-based estimation from export
2926
- * references: directly test-referenced = 85%, indirectly reachable = 40%,
2927
- * untested = 0%. 'istanbul' uses real per-function statement coverage from
2928
- * a coverage-final.json file (--coverage flag or auto-detected).
2929
- * 'static_binary' is the legacy binary model. Only present when file
2930
- * scores are computed.
2931
- */
2932
2630
  coverage_model?: (CoverageModel | null)
2933
- /**
2934
- * Number of functions matched against Istanbul coverage data.
2935
- * Only present when `coverage_model` is `istanbul`.
2936
- */
2631
+ coverage_source_consistency?: (CoverageSourceConsistency | null)
2937
2632
  istanbul_matched?: (number | null)
2938
- /**
2939
- * Total functions that could potentially be matched.
2940
- * Only present when `coverage_model` is `istanbul`.
2941
- */
2942
2633
  istanbul_total?: (number | null)
2943
- /**
2944
- * Number of findings with critical severity (cognitive >= 40 or cyclomatic
2945
- * >= 50).
2946
- */
2947
2634
  severity_critical_count: number
2948
- /**
2949
- * Number of findings with high severity (cognitive 25-39 or cyclomatic
2950
- * 30-49).
2951
- */
2952
2635
  severity_high_count: number
2953
- /**
2954
- * Number of findings with moderate severity.
2955
- */
2956
2636
  severity_moderate_count: number
2957
2637
  }
2958
2638
  /**
@@ -3103,173 +2783,43 @@ high_risk: number
3103
2783
  */
3104
2784
  very_high_risk: number
3105
2785
  }
3106
- /**
3107
- * Project-level health score. Score = 100 minus available penalties from dead
3108
- * code, complexity, maintainability, hotspots, unused deps, circular deps,
3109
- * unit size, coupling, and duplication. Missing metrics do not penalize;
3110
- * --score computes the score and duplication penalty, while churn-backed
3111
- * hotspot penalties require hotspot analysis (--hotspots, or --targets with
3112
- * --score).
3113
- */
3114
2786
  export interface HealthScore {
3115
- /**
3116
- * Health score formula version. Version 2 uses scale-invariant
3117
- * density/tail metrics for monorepo-safe scoring.
3118
- */
3119
2787
  formula_version: number
3120
- /**
3121
- * Overall score (0-100, higher is better). Reproducible: 100 -
3122
- * sum(penalties) == score.
3123
- */
3124
2788
  score: number
3125
- /**
3126
- * Letter grade. A: score >= 85, B: 70-84, C: 55-69, D: 40-54, F: below 40.
3127
- */
3128
2789
  grade: string
3129
2790
  penalties: HealthScorePenalties
3130
2791
  }
3131
2792
  /**
3132
2793
  * Per-component penalty breakdown for the health score.
3133
- *
3134
- * Each field shows how many points were subtracted for that component.
3135
- * `None` means the metric was not available (pipeline didn't run).
3136
2794
  */
3137
2795
  export interface HealthScorePenalties {
3138
- /**
3139
- * Points lost from dead files (max 15). Null if dead code pipeline not
3140
- * run.
3141
- */
3142
2796
  dead_files?: (number | null)
3143
- /**
3144
- * Points lost from dead exports (max 15). Null if dead code pipeline not
3145
- * run.
3146
- */
3147
2797
  dead_exports?: (number | null)
3148
- /**
3149
- * Points lost from critical-complexity density (max 20). Older snapshots
3150
- * without density fields fall back to average cyclomatic complexity above
3151
- * 1.5.
3152
- */
3153
2798
  complexity: number
3154
- /**
3155
- * Points lost from legacy p90 cyclomatic complexity above 10. Current
3156
- * scale-invariant runs report 0 because tail complexity is folded into
3157
- * complexity.
3158
- */
3159
2799
  p90_complexity: number
3160
- /**
3161
- * Points lost from low maintainability index density (max 15).
3162
- */
3163
2800
  maintainability?: (number | null)
3164
- /**
3165
- * Points lost from top-percentile hotspot density (max 10). Null if
3166
- * hotspots not computed.
3167
- */
3168
2801
  hotspots?: (number | null)
3169
- /**
3170
- * Points lost from unused dependency density (max 25). Null if dead code
3171
- * pipeline not run.
3172
- */
3173
2802
  unused_deps?: (number | null)
3174
- /**
3175
- * Points lost from circular dependency density (max 25). Null if dead code
3176
- * pipeline not run.
3177
- */
3178
2803
  circular_deps?: (number | null)
3179
- /**
3180
- * Points lost from oversized-function density (max 10). Null if no
3181
- * functions analyzed.
3182
- */
3183
2804
  unit_size?: (number | null)
3184
- /**
3185
- * Points lost from coupling concentration density (max 5). Null if file
3186
- * scores not computed.
3187
- */
3188
2805
  coupling?: (number | null)
3189
- /**
3190
- * Points lost from code duplication (max 10). Penalty = min(max(0,
3191
- * duplication_pct - 5) * 1, 10). Null if duplication pipeline not run.
3192
- */
3193
2806
  duplication?: (number | null)
3194
2807
  }
3195
2808
  /**
3196
2809
  * Per-file health score combining complexity, coupling, and dead code metrics.
3197
- *
3198
- * Files with zero functions (barrel files, re-export files) are excluded by default.
3199
- *
3200
- * ## Maintainability Index Formula
3201
- *
3202
- * ```text
3203
- * dampening = min(lines / 50, 1.0)
3204
- * fan_out_penalty = min(ln(fan_out + 1) × 4, 15)
3205
- * maintainability = 100
3206
- * - (complexity_density × 30 × dampening)
3207
- * - (dead_code_ratio × 20)
3208
- * - fan_out_penalty
3209
- * ```
3210
- *
3211
- * Clamped to \[0, 100\]. Higher is better. The dampening factor prevents
3212
- * complexity density from dominating the score on small files (< 50 lines).
3213
- *
3214
- * - **complexity_density**: total cyclomatic complexity / lines of code
3215
- * - **dead_code_ratio**: fraction of value exports (excluding type-only exports) with zero references (0.0–1.0)
3216
- * - **fan_out_penalty**: logarithmic scaling with cap at 15 points; reflects diminishing marginal risk of additional imports
3217
2810
  */
3218
2811
  export interface FileHealthScore {
3219
- /**
3220
- * File path (absolute; stripped to relative in output).
3221
- */
3222
2812
  path: string
3223
- /**
3224
- * Number of files that import this file.
3225
- */
3226
2813
  fan_in: number
3227
- /**
3228
- * Number of files this file imports.
3229
- */
3230
2814
  fan_out: number
3231
- /**
3232
- * Fraction of value exports with zero references (0.0–1.0). Files with no value exports get 0.0.
3233
- * Type-only exports (interfaces, type aliases) are excluded from both numerator and denominator
3234
- * to avoid inflating the ratio for well-typed codebases that export props types alongside components.
3235
- */
3236
2815
  dead_code_ratio: number
3237
- /**
3238
- * Total cyclomatic complexity / lines of code.
3239
- */
3240
2816
  complexity_density: number
3241
- /**
3242
- * Weighted composite score (0–100, higher is better).
3243
- */
3244
2817
  maintainability_index: number
3245
- /**
3246
- * Sum of cyclomatic complexity across all functions.
3247
- */
3248
2818
  total_cyclomatic: number
3249
- /**
3250
- * Sum of cognitive complexity across all functions.
3251
- */
3252
2819
  total_cognitive: number
3253
- /**
3254
- * Number of functions in this file.
3255
- */
3256
2820
  function_count: number
3257
- /**
3258
- * Total lines of code (from line_offsets).
3259
- */
3260
2821
  lines: number
3261
- /**
3262
- * Maximum CRAP score among functions in this file. Computed via the active
3263
- * `coverage_model` per the canonical formula CC^2 * (1 - cov/100)^3 + CC
3264
- * (Savoia & Evans, 2007). Coverage source: `static_estimated` (default,
3265
- * graph-based per-function estimate), `istanbul` (real per-function
3266
- * statement coverage from --coverage), or the legacy `static_binary`
3267
- * (whole-file 0%/100%, retained for compatibility).
3268
- */
3269
2822
  crap_max: number
3270
- /**
3271
- * Count of functions with CRAP >= 30 (CC >= 5 without test path).
3272
- */
3273
2823
  crap_above_threshold: number
3274
2824
  }
3275
2825
  /**
@@ -3453,51 +3003,16 @@ comment?: (string | null)
3453
3003
  * test code.
3454
3004
  */
3455
3005
  export interface HotspotFinding {
3456
- /**
3457
- * File path (absolute; stripped to relative in output).
3458
- */
3459
3006
  path: string
3460
- /**
3461
- * Hotspot score (0–100). Higher means more risk.
3462
- */
3463
3007
  score: number
3464
- /**
3465
- * Number of commits in the analysis window.
3466
- */
3467
3008
  commits: number
3468
- /**
3469
- * Recency-weighted commit count (exponential decay, half-life 90 days).
3470
- */
3471
3009
  weighted_commits: number
3472
- /**
3473
- * Total lines added across all commits.
3474
- */
3475
3010
  lines_added: number
3476
- /**
3477
- * Total lines deleted across all commits.
3478
- */
3479
3011
  lines_deleted: number
3480
- /**
3481
- * Cyclomatic complexity / lines of code.
3482
- */
3483
3012
  complexity_density: number
3484
- /**
3485
- * Number of files that import this file (blast radius).
3486
- */
3487
3013
  fan_in: number
3488
3014
  trend: ChurnTrend
3489
- /**
3490
- * Ownership signals (bus factor, contributors, declared owner, drift).
3491
- * Populated only when `--ownership` is requested.
3492
- */
3493
3015
  ownership?: (OwnershipMetrics | null)
3494
- /**
3495
- * True when the file path matches a test/mock convention (e.g.
3496
- * `** /__tests__/**`, `** /*.test.*`, `** /*.spec.*`, `** /__mocks__/**`).
3497
- * Test files are intentionally included in hotspot ranking (test
3498
- * maintenance IS real work), but tagging them lets consumers decide
3499
- * whether to weight or filter them downstream.
3500
- */
3501
3016
  is_test_path?: boolean
3502
3017
  /**
3503
3018
  * Machine-actionable refactor and review hints. Always populated;
@@ -3509,89 +3024,23 @@ is_test_path?: boolean
3509
3024
  */
3510
3025
  actions: HotspotAction[]
3511
3026
  }
3512
- /**
3513
- * Per-file ownership signals attached to hotspot entries when the user
3514
- * passes `--ownership`. All fields are derived from git history and the
3515
- * repository's CODEOWNERS file (if any).
3516
- */
3517
3027
  export interface OwnershipMetrics {
3518
- /**
3519
- * Avelino truck factor: minimum contributors covering at least 50% of
3520
- * recency-weighted commits in the analysis window. Lower = higher
3521
- * knowledge-loss risk.
3522
- */
3523
3028
  bus_factor: number
3524
- /**
3525
- * Distinct authors in the analysis window after bot filtering.
3526
- */
3527
3029
  contributor_count: number
3528
3030
  top_contributor: ContributorEntry
3529
- /**
3530
- * Up to three additional contributors by share, ordered desc.
3531
- * Useful for "who else could review this file" routing.
3532
- */
3533
3031
  recent_contributors?: ContributorEntry[]
3534
- /**
3535
- * Contributors whose last touch is within 90 days, ordered by share
3536
- * descending. First-class field so AI agents do not have to
3537
- * reconstruct it from [`recent_contributors`](Self::recent_contributors)
3538
- * filtered by [`ContributorEntry::stale_days`]. Excludes the top
3539
- * contributor (they are the sole author being flagged); consumers
3540
- * wanting the full list can union with `top_contributor`.
3541
- */
3542
3032
  suggested_reviewers?: ContributorEntry[]
3543
- /**
3544
- * CODEOWNERS-resolved owner for this file, if a rule matched.
3545
- * Only the primary (first) owner of the matched rule is reported.
3546
- */
3547
3033
  declared_owner?: (string | null)
3548
- /**
3549
- * Tristate: `Some(true)` = CODEOWNERS file exists but no rule matches
3550
- * this file; `Some(false)` = a CODEOWNERS rule matches; `None` = no
3551
- * CODEOWNERS file was discovered for the repository (cannot determine).
3552
- */
3553
3034
  unowned?: (boolean | null)
3554
3035
  ownership_state: OwnershipState
3555
- /**
3556
- * True when ownership has drifted from the original author to a new
3557
- * top contributor. Pairs with [`drift_reason`](Self::drift_reason).
3558
- */
3559
3036
  drift: boolean
3560
- /**
3561
- * Human-readable explanation of the drift, populated only when
3562
- * [`drift`](Self::drift) is true.
3563
- */
3564
3037
  drift_reason?: (string | null)
3565
3038
  }
3566
- /**
3567
- * Per-author contribution summary. The identifier is rendered per the
3568
- * configured ownership.emailMode (handle, anonymized/hash, or raw); the format field
3569
- * discriminates the modes so type-aware consumers can branch without
3570
- * re-parsing.
3571
- */
3572
3039
  export interface ContributorEntry {
3573
- /**
3574
- * Display string per the configured email mode: raw email
3575
- * (`alice@example.com`), local-part handle (`alice`), or stable anonymized hash
3576
- * pseudonym (`xxh3:<16hex>`). The format depends on `format`.
3577
- *
3578
- * Renamed from `email` because in `handle` and `anonymized`/`hash` modes the value
3579
- * is no longer an email address; consumers tempted to use it as one
3580
- * (e.g. `mailto:`) would be wrong.
3581
- */
3582
3040
  identifier: string
3583
3041
  format: ContributorIdentifierFormat
3584
- /**
3585
- * Recency-weighted share of total weighted commits (0..1, three decimals).
3586
- */
3587
3042
  share: number
3588
- /**
3589
- * Days since this contributor last touched the file.
3590
- */
3591
3043
  stale_days: number
3592
- /**
3593
- * Total commits by this contributor in the analysis window.
3594
- */
3595
3044
  commits: number
3596
3045
  }
3597
3046
  /**
@@ -3634,29 +3083,11 @@ suggested_pattern?: (string | null)
3634
3083
  */
3635
3084
  heuristic?: (HotspotActionHeuristic | null)
3636
3085
  }
3637
- /**
3638
- * Summary statistics for hotspot analysis.
3639
- */
3640
3086
  export interface HotspotSummary {
3641
- /**
3642
- * Analysis window display string (e.g., "6 months").
3643
- */
3644
3087
  since: string
3645
- /**
3646
- * Minimum commits threshold.
3647
- */
3648
3088
  min_commits: number
3649
- /**
3650
- * Number of files with churn data meeting the threshold.
3651
- */
3652
3089
  files_analyzed: number
3653
- /**
3654
- * Number of files excluded (below min_commits).
3655
- */
3656
3090
  files_excluded: number
3657
- /**
3658
- * Whether the repository is a shallow clone.
3659
- */
3660
3091
  shallow_clone: boolean
3661
3092
  }
3662
3093
  /**
@@ -4110,21 +3541,9 @@ auto_fixable: boolean
4110
3541
  * A function exceeding the very-high-risk size threshold (>60 LOC).
4111
3542
  */
4112
3543
  export interface LargeFunctionEntry {
4113
- /**
4114
- * Absolute file path.
4115
- */
4116
3544
  path: string
4117
- /**
4118
- * Function name, or `"<anonymous>"` for unnamed functions/arrows.
4119
- */
4120
3545
  name: string
4121
- /**
4122
- * 1-based line number.
4123
- */
4124
3546
  line: number
4125
- /**
4126
- * Number of lines in the function.
4127
- */
4128
3547
  line_count: number
4129
3548
  }
4130
3549
  /**
@@ -4443,181 +3862,51 @@ scope: string
4443
3862
  * fallow JSON-producing command.
4444
3863
  */
4445
3864
  export interface ExplainOutput {
4446
- /**
4447
- * Canonical rule id, for example `fallow/unused-export`.
4448
- */
4449
3865
  id: string
4450
- /**
4451
- * Human-readable rule name.
4452
- */
4453
3866
  name: string
4454
- /**
4455
- * Short one-line explanation of the issue.
4456
- */
4457
3867
  summary: string
4458
- /**
4459
- * Why the issue matters and what fallow checks.
4460
- */
4461
3868
  rationale: string
4462
- /**
4463
- * Concrete example of the finding.
4464
- */
4465
3869
  example: string
4466
- /**
4467
- * Recommended fix or suppression guidance.
4468
- */
4469
3870
  how_to_fix: string
4470
- /**
4471
- * Docs URL for the rule.
4472
- */
4473
3871
  docs: string
4474
3872
  }
4475
3873
  /**
4476
3874
  * Envelope emitted by `fallow --format review-github` / `review-gitlab`.
4477
- * Consumed by `action/scripts/review.sh` and `ci/scripts/review.sh` to
4478
- * post inline PR / MR review comments.
4479
3875
  */
4480
3876
  export interface ReviewEnvelopeOutput {
4481
- /**
4482
- * GitHub review event. Omitted for GitLab.
4483
- */
4484
3877
  event?: (ReviewEnvelopeEvent | null)
4485
- /**
4486
- * Review summary body (rendered above per-line comments). Deprecated in
4487
- * v2 envelopes: prefer [`summary.body`](`ReviewEnvelopeSummary::body`),
4488
- * which is byte-identical to this field but carries a stable
4489
- * fingerprint for reconciliation. Kept on v2 emit so v1 consumers that
4490
- * only look at `body` keep working.
4491
- */
4492
3878
  body: string
4493
3879
  summary?: ReviewEnvelopeSummary
4494
- /**
4495
- * Per-line comments. Each is either a [`GitHubReviewComment`] or a
4496
- * [`GitLabReviewComment`] depending on `meta.provider`.
4497
- */
4498
3880
  comments: ReviewComment[]
4499
- /**
4500
- * Regex consumers run against every existing PR/MR comment body to
4501
- * extract a fallow-emitted fingerprint marker. Capture group 1 is the
4502
- * fingerprint string (a bare 16-char hex hash for single-finding
4503
- * comments, or `<kind>:<16-char-hex>` for compositions such as
4504
- * `merged:` for same-line collapsed comments).
4505
- *
4506
- * The pattern is anchored with `^` / `$` and relies on multiline
4507
- * matching to anchor at line boundaries inside a multi-line comment
4508
- * body. Multiline is NOT baked into the pattern via `(?m)` (which
4509
- * JavaScript RegExp rejects as `Invalid group`); instead the consumer
4510
- * passes [`Self::marker_regex_flags`] as the flags argument to its
4511
- * regex engine. JavaScript: `new RegExp(env.marker_regex,
4512
- * env.marker_regex_flags)`. Rust: `regex::RegexBuilder::new(pat)
4513
- * .multi_line(flags.contains('m')).build()` (or any equivalent).
4514
- */
4515
3881
  marker_regex?: string
4516
- /**
4517
- * Flags consumers pass alongside [`Self::marker_regex`] when
4518
- * constructing their regex engine. Currently always `"m"` (multiline
4519
- * so the anchored `^` / `$` match at every line boundary within a
4520
- * comment body). Emitting flags as a separate field instead of
4521
- * baking `(?m)` into the pattern keeps the wire compatible with
4522
- * JavaScript RegExp, which rejects inline flag groups outside a
4523
- * `(?flags:X)` grouping.
4524
- */
4525
3882
  marker_regex_flags?: string
4526
3883
  meta: ReviewEnvelopeMeta
4527
3884
  }
4528
3885
  /**
4529
- * Summary block on [`ReviewEnvelopeOutput`]. Always present on v2 emit;
4530
- * `serde(default)` keeps schemars from marking it required so a future
4531
- * Deserialize derivation against v1 historical input synthesizes an empty
4532
- * value rather than erroring.
3886
+ * Summary block on [`ReviewEnvelopeOutput`].
4533
3887
  */
4534
3888
  export interface ReviewEnvelopeSummary {
4535
- /**
4536
- * Markdown body of the summary. Byte-identical to the legacy top-level
4537
- * [`ReviewEnvelopeOutput::body`] field; the duplication is intentional
4538
- * so v1 consumers see no behavior change.
4539
- */
4540
3889
  body: string
4541
- /**
4542
- * FNV-1a 64-bit hash (16 lowercase hex chars) of the summary body
4543
- * BEFORE the trailing fallow-fingerprint marker line is appended.
4544
- * (Computing the hash from the post-marker body would be circular:
4545
- * the marker contains the fingerprint, so the fingerprint cannot
4546
- * depend on the marker.) To reproduce from [`Self::body`], strip the
4547
- * line matching [`ReviewEnvelopeOutput::marker_regex`] together with
4548
- * its leading separator newlines and hash the remainder. Stable
4549
- * across runs that produce the same summary content; consumers
4550
- * upsert the sticky summary comment by matching this fingerprint
4551
- * against the marker_regex extraction of every existing comment body.
4552
- */
4553
3890
  fingerprint: string
4554
3891
  }
4555
3892
  /**
4556
3893
  * GitHub pull-request review comment.
4557
3894
  */
4558
3895
  export interface GitHubReviewComment {
4559
- /**
4560
- * File path the comment targets, repo-root relative.
4561
- */
4562
3896
  path: string
4563
- /**
4564
- * 1-indexed line number the comment targets.
4565
- */
4566
3897
  line: number
4567
3898
  side: GitHubReviewSide
4568
- /**
4569
- * Markdown body of the comment.
4570
- */
4571
3899
  body: string
4572
- /**
4573
- * Stable fingerprint for the comment, used by `fallow ci
4574
- * reconcile-review` to detect carryover comments across PR revisions.
4575
- * For single-finding comments the value is a bare 16-char hex FNV-1a
4576
- * hash. For merged comments (multiple findings on the same path:line)
4577
- * the value is `merged:<16-char hex>` over the sorted constituent
4578
- * fingerprints, so the identity shifts whenever constituent findings
4579
- * change membership. Bundled wrappers and `fallow ci reconcile-review`
4580
- * dedupe on this primary fingerprint only; consumers wanting
4581
- * update-in-place reconciliation (preserving reviewer reply threads
4582
- * across content changes) implement their own identity tracking via
4583
- * `marker_regex`.
4584
- */
4585
3900
  fingerprint: string
4586
- /**
4587
- * True when [`Self::body`] was truncated to fit a downstream provider's
4588
- * note-size budget (today: 65,536 bytes). The body retains the closing
4589
- * fallow-fingerprint marker so reconciliation continues to work after
4590
- * truncation.
4591
- *
4592
- * Co-presence invariant: `truncated == true` always implies the body
4593
- * contains an inline `<!-- fallow-truncated -->` HTML marker and the
4594
- * `> Body truncated by fallow.` blockquote breadcrumb, and vice versa.
4595
- * All three signals are emitted together; consumers may use any one
4596
- * (the typed boolean is the authoritative machine-readable signal).
4597
- */
4598
3901
  truncated?: boolean
4599
3902
  }
4600
3903
  /**
4601
3904
  * GitLab merge-request discussion comment.
4602
3905
  */
4603
3906
  export interface GitLabReviewComment {
4604
- /**
4605
- * Markdown body of the comment.
4606
- */
4607
3907
  body: string
4608
3908
  position: GitLabReviewPosition
4609
- /**
4610
- * Stable fingerprint for the comment. See
4611
- * [`GitHubReviewComment::fingerprint`] for the single vs `merged:`
4612
- * shape contract; semantics are identical across providers.
4613
- */
4614
3909
  fingerprint: string
4615
- /**
4616
- * True when [`Self::body`] was truncated to fit GitLab's note-size
4617
- * budget. See [`GitHubReviewComment::truncated`] for the full
4618
- * co-presence invariant with the inline HTML marker and human
4619
- * blockquote breadcrumb.
4620
- */
4621
3910
  truncated?: boolean
4622
3911
  }
4623
3912
  /**
@@ -4625,30 +3914,12 @@ truncated?: boolean
4625
3914
  * merge-request discussion-position API.
4626
3915
  */
4627
3916
  export interface GitLabReviewPosition {
4628
- /**
4629
- * Merge-request base SHA.
4630
- */
4631
3917
  base_sha?: (string | null)
4632
- /**
4633
- * Merge-request start SHA.
4634
- */
4635
3918
  start_sha?: (string | null)
4636
- /**
4637
- * Merge-request head SHA.
4638
- */
4639
3919
  head_sha?: (string | null)
4640
3920
  position_type: GitLabReviewPositionType
4641
- /**
4642
- * File path on the base side.
4643
- */
4644
3921
  old_path: string
4645
- /**
4646
- * File path on the head side.
4647
- */
4648
3922
  new_path: string
4649
- /**
4650
- * 1-indexed line on the head side.
4651
- */
4652
3923
  new_line: number
4653
3924
  }
4654
3925
  /**
@@ -4657,10 +3928,6 @@ new_line: number
4657
3928
  export interface ReviewEnvelopeMeta {
4658
3929
  schema: ReviewEnvelopeSchema
4659
3930
  provider: ReviewProvider
4660
- /**
4661
- * Check conclusion derived from the underlying findings. Emitted only
4662
- * for GitHub envelopes today.
4663
- */
4664
3931
  check_conclusion?: (ReviewCheckConclusion | null)
4665
3932
  }
4666
3933
  /**
@@ -4671,240 +3938,66 @@ check_conclusion?: (ReviewCheckConclusion | null)
4671
3938
  export interface ReviewReconcileOutput {
4672
3939
  schema: ReviewReconcileSchema
4673
3940
  provider: ReviewProvider
4674
- /**
4675
- * PR / MR target identifier supplied to `fallow ci reconcile-review`.
4676
- * `null` when the command ran without an explicit target.
4677
- */
4678
3941
  target?: (string | null)
4679
- /**
4680
- * Whether the reconcile ran in dry-run mode.
4681
- */
4682
3942
  dry_run: boolean
4683
- /**
4684
- * Number of comments in the supplied review envelope.
4685
- */
4686
3943
  comments: number
4687
- /**
4688
- * Total fingerprints discovered in the supplied envelope.
4689
- */
4690
3944
  current_fingerprints: number
4691
- /**
4692
- * Existing fingerprints already posted on the PR / MR.
4693
- */
4694
3945
  existing_fingerprints: number
4695
- /**
4696
- * Newly-introduced fingerprints (current minus existing).
4697
- */
4698
3946
  new_fingerprints: number
4699
- /**
4700
- * Stale fingerprints (existing minus current).
4701
- */
4702
3947
  stale_fingerprints: number
4703
- /**
4704
- * Identifiers of the new fingerprints (subset of comments).
4705
- */
4706
3948
  new: string[]
4707
- /**
4708
- * Identifiers of the stale fingerprints (subset of existing).
4709
- */
4710
3949
  stale: string[]
4711
- /**
4712
- * Optional warning when the provider API was unreachable or
4713
- * auth-rejected. `null` on the happy path.
4714
- */
4715
3950
  provider_warning?: (string | null)
4716
- /**
4717
- * Resolution comments actually posted (zero on dry runs).
4718
- */
4719
3951
  resolution_comments_posted: number
4720
- /**
4721
- * Stale review threads actually resolved (zero on dry runs).
4722
- */
4723
3952
  threads_resolved: number
4724
- /**
4725
- * Operator-facing retry hint when apply stopped early.
4726
- */
4727
3953
  apply_hint?: (string | null)
4728
- /**
4729
- * Errors collected during apply, one entry per failure.
4730
- */
4731
3954
  apply_errors: string[]
4732
- /**
4733
- * Stale fingerprints whose provider mutation failed.
4734
- */
4735
3955
  failed_fingerprints?: string[]
4736
- /**
4737
- * Stale fingerprints not fully applied after the fail-fast stop.
4738
- */
4739
3956
  unapplied_fingerprints?: string[]
4740
3957
  }
4741
3958
  /**
4742
- * Envelope emitted by `fallow coverage setup --json`. Deterministic
4743
- * agent-readable runtime coverage setup instructions. In workspaces,
4744
- * `members` carries one entry per detected runtime package; `runtime_targets`
4745
- * is the union of all member targets.
4746
- *
4747
- * Constructed at runtime by
4748
- * `crates/cli/src/coverage/mod.rs::build_setup_envelope`; the wire is
4749
- * `serde_json::to_value(&envelope)`. The drift gate keeps this struct
4750
- * aligned with `docs/output-schema.json`.
3959
+ * `fallow coverage setup --json` envelope.
4751
3960
  */
4752
3961
  export interface CoverageSetupOutput {
4753
3962
  schema_version: CoverageSetupSchemaVersion
4754
3963
  framework_detected: CoverageSetupFramework
4755
- /**
4756
- * Detected JavaScript package manager. `null` when none could be
4757
- * resolved.
4758
- */
4759
3964
  package_manager?: (CoverageSetupPackageManager | null)
4760
- /**
4761
- * Union of runtime targets across emitted members.
4762
- */
4763
3965
  runtime_targets: CoverageSetupRuntimeTarget[]
4764
- /**
4765
- * Per-runtime-workspace setup recipes. Pure aggregator roots and
4766
- * build-only library packages are omitted.
4767
- */
4768
3966
  members: CoverageSetupMember[]
4769
- /**
4770
- * Always `null` today. Reserved for a future "config has been written
4771
- * to disk" indicator.
4772
- */
4773
- config_written?: {
4774
- [k: string]: unknown
4775
- }
4776
- /**
4777
- * Shell commands the agent should run from the workspace root.
4778
- */
3967
+ config_written?: unknown
4779
3968
  commands: string[]
4780
- /**
4781
- * Compatibility copy of the primary member's files, with workspace
4782
- * prefixes when the primary member is not the root.
4783
- */
4784
3969
  files_to_edit: CoverageSetupFileToEdit[]
4785
- /**
4786
- * Compatibility copy of the primary member's snippets, with workspace
4787
- * prefixes when the primary member is not the root.
4788
- */
4789
3970
  snippets: CoverageSetupSnippet[]
4790
- /**
4791
- * Optional Dockerfile RUN/COPY snippet to enable the beacon in
4792
- * containerised deployments.
4793
- */
4794
3971
  dockerfile_snippet?: (string | null)
4795
- /**
4796
- * Ordered next-step instructions for the agent / human operator.
4797
- */
4798
3972
  next_steps: string[]
4799
- /**
4800
- * Non-fatal warnings raised during setup detection.
4801
- */
4802
3973
  warnings: string[]
4803
- /**
4804
- * `_meta` block emitted only when `--explain` is passed.
4805
- */
4806
- _meta?: {
4807
- [k: string]: unknown
4808
- }
3974
+ _meta?: unknown
4809
3975
  }
4810
- /**
4811
- * Per-workspace setup recipe inside [`CoverageSetupOutput::members`].
4812
- */
4813
3976
  export interface CoverageSetupMember {
4814
- /**
4815
- * Workspace package name (or root marker for single-package projects).
4816
- */
4817
3977
  name: string
4818
- /**
4819
- * Workspace path relative to the analysed root, or `.` for the root
4820
- * member.
4821
- */
4822
3978
  path: string
4823
3979
  framework_detected: CoverageSetupFramework
4824
- /**
4825
- * Package manager detected for this member.
4826
- */
4827
3980
  package_manager?: (CoverageSetupPackageManager | null)
4828
- /**
4829
- * Runtime targets supported by this member's framework.
4830
- */
4831
3981
  runtime_targets: CoverageSetupRuntimeTarget[]
4832
- /**
4833
- * Files the agent should edit to wire in the beacon.
4834
- */
4835
3982
  files_to_edit: CoverageSetupFileToEdit[]
4836
- /**
4837
- * Code snippets the agent should paste into the edited files.
4838
- */
4839
3983
  snippets: CoverageSetupSnippet[]
4840
- /**
4841
- * Optional Dockerfile snippet specific to this member.
4842
- */
4843
3984
  dockerfile_snippet?: (string | null)
4844
- /**
4845
- * Member-scoped warnings.
4846
- */
4847
3985
  warnings: string[]
4848
3986
  }
4849
- /**
4850
- * Single file to edit inside [`CoverageSetupMember::files_to_edit`] or
4851
- * [`CoverageSetupOutput::files_to_edit`].
4852
- */
4853
3987
  export interface CoverageSetupFileToEdit {
4854
- /**
4855
- * Workspace-relative path to the file to edit.
4856
- */
4857
3988
  path: string
4858
- /**
4859
- * Why the file needs editing (e.g. `"Mount the beacon middleware"`).
4860
- */
4861
3989
  reason: string
4862
3990
  }
4863
- /**
4864
- * Single code snippet inside [`CoverageSetupMember::snippets`] or
4865
- * [`CoverageSetupOutput::snippets`].
4866
- */
4867
3991
  export interface CoverageSetupSnippet {
4868
- /**
4869
- * Short label identifying the snippet (used by the human renderer).
4870
- */
4871
3992
  label: string
4872
- /**
4873
- * Workspace-relative path the snippet should be pasted into.
4874
- */
4875
3993
  path: string
4876
- /**
4877
- * Snippet content (literal source text).
4878
- */
4879
3994
  content: string
4880
3995
  }
4881
- /**
4882
- * Envelope emitted by `fallow coverage analyze --format json`.
4883
- *
4884
- * Focused runtime coverage analysis output. Local mode reads
4885
- * `--runtime-coverage <path>`. Cloud mode requires explicit `--cloud` /
4886
- * `--runtime-coverage-cloud` or `FALLOW_RUNTIME_COVERAGE_SOURCE=cloud`;
4887
- * `FALLOW_API_KEY` alone does NOT select cloud mode.
4888
- *
4889
- * Constructed at runtime in
4890
- * `crates/cli/src/coverage/analyze.rs::print_runtime_json`; the wire is
4891
- * `serde_json::to_value(&envelope)`. The drift gate keeps this struct
4892
- * aligned with `docs/output-schema.json`. Carries its own schema-version
4893
- * discriminator ([`CoverageAnalyzeSchemaVersion`]) because runtime
4894
- * coverage iterates independently of the main JSON contract version.
4895
- */
4896
3996
  export interface CoverageAnalyzeOutput {
4897
3997
  schema_version: CoverageAnalyzeSchemaVersion
4898
3998
  version: ToolVersion
4899
3999
  elapsed_ms: ElapsedMs
4900
4000
  runtime_coverage: RuntimeCoverageReport
4901
- /**
4902
- * `_meta` block with metric / rule definitions, emitted when `--explain`
4903
- * is passed. Populated via the post-pass injection in
4904
- * `print_runtime_json` (matches the pattern used by every other typed
4905
- * envelope; the typed struct sets this to `None` and the JSON layer
4906
- * merges in the `crate::explain::coverage_analyze_meta()` payload).
4907
- */
4908
4001
  _meta?: (Meta | null)
4909
4002
  }
4910
4003
  /**
@@ -4921,38 +4014,12 @@ boundaries: BoundariesListing
4921
4014
  * `boundaries` block carried by [`ListBoundariesOutput`].
4922
4015
  */
4923
4016
  export interface BoundariesListing {
4924
- /**
4925
- * `false` when the project has no `boundaries` configured; `true`
4926
- * otherwise. When `false` every array below is empty and every count
4927
- * is `0` (parity is enforced so consumers can read the counts without
4928
- * first branching on this flag).
4929
- */
4930
4017
  configured: boolean
4931
- /**
4932
- * Length of [`Self::zones`]; emitted alongside the array for parity
4933
- * with `rule_count` / `logical_group_count`.
4934
- */
4935
4018
  zone_count: number
4936
- /**
4937
- * Boundary zones after preset and `autoDiscover` expansion.
4938
- */
4939
4019
  zones: BoundariesListZone[]
4940
- /**
4941
- * Length of [`Self::rules`].
4942
- */
4943
4020
  rule_count: number
4944
- /**
4945
- * Boundary import rules, each `from -> allow[]`.
4946
- */
4947
4021
  rules: BoundariesListRule[]
4948
- /**
4949
- * Length of [`Self::logical_groups`]. Always present (issue #373).
4950
- */
4951
4022
  logical_group_count: number
4952
- /**
4953
- * Pre-expansion `autoDiscover` groups carrying the user-authored parent
4954
- * name and grouping intent (issue #373).
4955
- */
4956
4023
  logical_groups: BoundariesListLogicalGroup[]
4957
4024
  }
4958
4025
  /**
@@ -4960,18 +4027,8 @@ logical_groups: BoundariesListLogicalGroup[]
4960
4027
  * classifies files into a single zone via glob patterns.
4961
4028
  */
4962
4029
  export interface BoundariesListZone {
4963
- /**
4964
- * Zone identifier as referenced in rules (e.g. `app`, `features/auth`).
4965
- */
4966
4030
  name: string
4967
- /**
4968
- * Compiled glob patterns. Children of an `autoDiscover` parent each
4969
- * carry a single pattern like `src/features/auth/**`.
4970
- */
4971
4031
  patterns: string[]
4972
- /**
4973
- * Number of discovered files classified into this zone.
4974
- */
4975
4032
  file_count: number
4976
4033
  }
4977
4034
  /**
@@ -4981,14 +4038,7 @@ file_count: number
4981
4038
  * corresponding [`BoundariesListLogicalGroup::authored_rule`].
4982
4039
  */
4983
4040
  export interface BoundariesListRule {
4984
- /**
4985
- * Source zone the rule applies to.
4986
- */
4987
4041
  from: string
4988
- /**
4989
- * Target zones [`Self::from`] is allowed to import from. Self-imports
4990
- * are always allowed implicitly.
4991
- */
4992
4042
  allow: string[]
4993
4043
  }
4994
4044
  /**
@@ -4998,75 +4048,28 @@ allow: string[]
4998
4048
  * would otherwise flatten it out of [`BoundariesListing::zones`].
4999
4049
  */
5000
4050
  export interface BoundariesListLogicalGroup {
5001
- /**
5002
- * Logical parent zone name as authored by the user.
5003
- */
5004
4051
  name: string
5005
- /**
5006
- * Discovered child zone names in stable directory-sorted order.
5007
- */
5008
4052
  children: string[]
5009
- /**
5010
- * Verbatim `autoDiscover` strings from the user's config (not
5011
- * normalized) so round-trip tooling can match byte-for-byte.
5012
- */
5013
4053
  auto_discover: string[]
5014
4054
  status: LogicalGroupStatus
5015
- /**
5016
- * Position of the parent zone in the user's pre-expansion `zones[]`.
5017
- */
5018
4055
  source_zone_index: number
5019
- /**
5020
- * Sum of `file_count` across [`Self::children`] plus the fallback
5021
- * zone's `file_count` when present.
5022
- */
5023
4056
  file_count: number
5024
- /**
5025
- * Pre-expansion rule keyed on the parent name, when the user wrote
5026
- * one.
5027
- */
5028
4057
  authored_rule?: (AuthoredRule | null)
5029
- /**
5030
- * When the parent zone also carried explicit `patterns`, it stayed in
5031
- * [`BoundariesListing::zones`] as a fallback classifier; this is its
5032
- * name. Equal to [`Self::name`] when present.
5033
- */
5034
4058
  fallback_zone?: (string | null)
5035
- /**
5036
- * Parent zone indices merged into this group when the user declared
5037
- * the same parent name multiple times.
5038
- */
5039
4059
  merged_from?: (number[] | null)
5040
- /**
5041
- * Echo of the parent zone's `root` (subtree scope) as the user wrote
5042
- * it. `None` when the parent had no `root` field.
5043
- */
5044
4060
  original_zone_root?: (string | null)
5045
- /**
5046
- * Parallel to [`Self::children`]: for child at index `i`, the index
5047
- * into [`Self::auto_discover`] of the path that produced it. Empty
5048
- * when only one path was authored (every child trivially maps to
5049
- * index 0). `serde(default)` keeps the schema's `required` array in
5050
- * step with the runtime's `skip_serializing_if` behavior.
5051
- */
5052
4061
  child_source_indices?: number[]
5053
4062
  }
5054
4063
  /**
5055
- * Pre-expansion `from`-rule preserved on a [`LogicalGroup`]. Surfaces the
5056
- * user's original intent (`{ from: "features", allow: ["shared"] }`) even
5057
- * after `expand_auto_discover` rewrote it into per-child rules
5058
- * (`features/auth -> shared`, `features/billing -> shared`).
4064
+ * Pre-expansion rule preserved on a [`LogicalGroup`].
5059
4065
  */
5060
4066
  export interface AuthoredRule {
5061
4067
  /**
5062
- * Pre-expansion `allow` list as the user wrote it.
4068
+ * Authored `allow` list.
5063
4069
  */
5064
4070
  allow: string[]
5065
4071
  /**
5066
- * Pre-expansion `allowTypeOnly` list as the user wrote it. Omitted
5067
- * from JSON output when empty; `serde(default)` keeps the derived
5068
- * schema in lock-step (schemars 1 marks any field with a
5069
- * `serde(default)` attribute as non-required).
4072
+ * Authored `allowTypeOnly` list.
5070
4073
  */
5071
4074
  allow_type_only?: string[]
5072
4075
  }
@@ -5167,31 +4170,9 @@ health_trend?: (HealthTrend | null)
5167
4170
  * back to the report root.
5168
4171
  */
5169
4172
  actions_meta?: (HealthActionsMeta | null)
5170
- /**
5171
- * Resolver mode used when --group-by is active. Present only on grouped
5172
- * output. The top-level `vital_signs`, `health_score`, and `summary` keep
5173
- * the active run scope (for example after --workspace); per-group versions
5174
- * live inside each entry of `groups`.
5175
- */
5176
4173
  grouped_by?: (GroupByMode | null)
5177
- /**
5178
- * Per-group health output, present only when `--group-by` is active.
5179
- * Each group recomputes its own `vital_signs` and `health_score` from
5180
- * the files in that group, mirroring how `--workspace` scopes a single
5181
- * subset.
5182
- */
5183
4174
  groups?: (HealthGroup[] | null)
5184
- /**
5185
- * `_meta` block with metric / rule definitions, emitted when `--explain`
5186
- * is passed (always present in MCP responses).
5187
- */
5188
4175
  _meta?: (Meta | null)
5189
- /**
5190
- * Workspace-discovery diagnostics surfaced during config load
5191
- * (issue #473). Mirror of [`CheckOutput::workspace_diagnostics`] so
5192
- * stand-alone `fallow health --format json` consumers see the same
5193
- * signal.
5194
- */
5195
4176
  workspace_diagnostics?: WorkspaceDiagnostic[]
5196
4177
  }
5197
4178
  /**
@@ -5233,6 +4214,12 @@ files_analyzed: number
5233
4214
  * rendered finding count, not the un-truncated total.
5234
4215
  */
5235
4216
  functions_above_threshold: number
4217
+ /**
4218
+ * Whether CRAP findings in this group share a single coverage-source kind
4219
+ * (`uniform`) or combine Istanbul / estimated / inherited sources
4220
+ * (`mixed`). Absent when no grouped finding carries CRAP source data.
4221
+ */
4222
+ coverage_source_consistency?: (CoverageSourceConsistency | null)
5236
4223
  /**
5237
4224
  * Per-group vital signs recomputed from the files in this group. Absent
5238
4225
  * when --score-only suppressed top-level vital signs.
@@ -5281,19 +4268,15 @@ targets?: RefactoringTargetFinding[]
5281
4268
  actions_meta?: (HealthActionsMeta | null)
5282
4269
  }
5283
4270
  /**
5284
- * Envelope emitted by `fallow dupes --format json` (plus the `dupes` block
5285
- * inside the combined and audit envelopes).
4271
+ * Wire-shape payload for `fallow dupes --format json` (the body that
4272
+ * flattens into [`crate::output_envelope::DupesOutput`] and is also
4273
+ * emitted under the `dupes` / `duplication` key inside the combined and
4274
+ * audit envelopes).
5286
4275
  *
5287
- * The body is the typed [`DupesReportPayload`] flattened into the envelope
5288
- * so the wire shape stays `{ schema_version, version, elapsed_ms,
5289
- * clone_groups, clone_families, stats, ... }` exactly as the existing JSON
5290
- * layer emits. The payload's `clone_groups` and `clone_families` carry
5291
- * typed [`crate::output_dupes::CloneGroupFinding`] /
5292
- * [`crate::output_dupes::CloneFamilyFinding`] wrappers so the `actions[]`
5293
- * field is part of the schema-derived contract.
5294
- * `grouped_by` / `groups` / `total_issues` are populated by the grouped
5295
- * builder; on the ungrouped path they stay `None` and `skip_serializing_if`
5296
- * drops them.
4276
+ * Mirrors [`DuplicationReport`] field-for-field, except `clone_groups`
4277
+ * and `clone_families` carry the typed wrapper envelopes instead of bare
4278
+ * findings, so the schema (and any TS / agent consumer) sees the typed
4279
+ * `actions[]` natively.
5297
4280
  */
5298
4281
  export interface DupesOutput {
5299
4282
  schema_version: SchemaVersion
@@ -5318,29 +4301,8 @@ clone_families: CloneFamilyFinding[]
5318
4301
  */
5319
4302
  mirrored_directories?: MirroredDirectory[]
5320
4303
  stats: DuplicationStats
5321
- /**
5322
- * Resolver mode used for partitioning. Present only when `--group-by` is
5323
- * active.
5324
- */
5325
4304
  grouped_by?: (GroupByMode | null)
5326
- /**
5327
- * Total clone groups across all buckets when `--group-by` is active.
5328
- * Mirrors the grouped check / health envelopes which expose
5329
- * `total_issues` so MCP and CI consumers can read the same key across
5330
- * commands.
5331
- */
5332
4305
  total_issues?: (number | null)
5333
- /**
5334
- * Per-group buckets when `--group-by` is active. Each clone group is
5335
- * attributed to its largest-owner key (most instances; alphabetical
5336
- * tiebreak). Sort: most clone groups first, then alphabetical, with
5337
- * `(unowned)` pinned last.
5338
- *
5339
- * Each bucket's `clone_groups` and `clone_families` carry the typed
5340
- * finding wrappers ([`crate::output_dupes::AttributedCloneGroupFinding`],
5341
- * [`crate::output_dupes::CloneFamilyFinding`]) so the `actions[]`
5342
- * augmentation is part of the schema-derived contract.
5343
- */
5344
4306
  groups?: (DuplicationGroup[] | null)
5345
4307
  /**
5346
4308
  * `_meta` block with metric / rule definitions, emitted when `--explain`
@@ -5466,19 +4428,8 @@ schema_version: SchemaVersion
5466
4428
  version: ToolVersion
5467
4429
  elapsed_ms: ElapsedMs
5468
4430
  grouped_by: GroupByMode
5469
- /**
5470
- * Total number of issues across all groups.
5471
- */
5472
4431
  total_issues: number
5473
- /**
5474
- * One entry per group; each contains the same issue arrays as
5475
- * `CheckOutput` plus the group key and per-group total.
5476
- */
5477
4432
  groups: CheckGroupedEntry[]
5478
- /**
5479
- * `_meta` block with metric / rule definitions, emitted when `--explain`
5480
- * is passed.
5481
- */
5482
4433
  _meta?: (Meta | null)
5483
4434
  }
5484
4435
  /**
@@ -5487,23 +4438,8 @@ _meta?: (Meta | null)
5487
4438
  * `AnalysisResults`.
5488
4439
  */
5489
4440
  export interface CheckGroupedEntry {
5490
- /**
5491
- * Group identifier produced by the resolver. For `package` grouping:
5492
- * workspace package name. For `owner` grouping: the CODEOWNERS team.
5493
- * For `directory` grouping: the top-level directory prefix. For
5494
- * `section` grouping: the GitLab CODEOWNERS section name (or
5495
- * `(no section)` / `(unowned)` for unmatched files).
5496
- */
5497
4441
  key: string
5498
- /**
5499
- * Section default owners (GitLab CODEOWNERS `[Section] @owner1
5500
- * @owner2`). Emitted only when `grouped_by` is `section`. Empty for
5501
- * the `(no section)` and `(unowned)` buckets.
5502
- */
5503
4442
  owners?: (string[] | null)
5504
- /**
5505
- * Total number of issues in this group.
5506
- */
5507
4443
  total_issues: number
5508
4444
  /**
5509
4445
  * Files not reachable from any entry point. Wrapped in
@@ -5746,101 +4682,131 @@ total_delta: number
5746
4682
  previous_total: number
5747
4683
  current_total: number
5748
4684
  }
5749
- /**
5750
- * A blocked-then-cleared containment: fallow stopped a commit until it was fixed.
5751
- */
5752
4685
  export interface ContainmentEvent {
5753
4686
  blocked_at: string
5754
4687
  cleared_at: string
5755
4688
  git_sha?: (string | null)
5756
4689
  blocked_counts: ImpactCounts
5757
4690
  }
4691
+ export interface ResolutionEvent {
4692
+ kind: string
4693
+ path: string
4694
+ symbol?: (string | null)
4695
+ git_sha?: (string | null)
4696
+ timestamp: string
4697
+ }
5758
4698
  /**
5759
- * A genuinely-resolved finding, recorded for the recent-resolutions display.
4699
+ * The `fallow security --format json` envelope. `security_findings` is the
4700
+ * unique required field used for untagged narrowing in `FallowOutput`.
5760
4701
  */
5761
- export interface ResolutionEvent {
4702
+ export interface SecurityOutput {
4703
+ schema_version: SecuritySchemaVersion
5762
4704
  /**
5763
- * The resolved finding's kind, kebab-case (e.g. `"unused-export"`).
4705
+ * Security candidates. Paths are project-root-relative, forward-slash.
5764
4706
  */
5765
- kind: string
4707
+ security_findings: SecurityFinding[]
5766
4708
  /**
5767
- * Workspace-relative, forward-slash path of the file the finding was in.
4709
+ * In-band blind spot: number of `"use client"` files whose transitive
4710
+ * import cone contains a dynamic `import()` the reachability BFS could not
4711
+ * follow. A leak hidden behind such an edge would not be reported, so a
4712
+ * zero finding count with a non-zero value here is NOT a clean bill.
5768
4713
  */
5769
- path: string
4714
+ unresolved_edge_files: number
5770
4715
  /**
5771
- * The finding's symbol (export / member / dependency name), when it has
5772
- * one. `None` for file-level and content-hash-keyed findings (duplication).
4716
+ * In-band blind spot: number of sink-shaped nodes the catalogue detector
4717
+ * could not flatten to a static callee path (dynamic dispatch, computed
4718
+ * members, aliased bindings). A zero finding count with a non-zero value
4719
+ * here is NOT a clean bill.
5773
4720
  */
5774
- symbol?: (string | null)
4721
+ unresolved_callee_sites: number
4722
+ }
5775
4723
  /**
5776
- * Short git SHA of the run that recorded the resolution, when in a git repo.
4724
+ * A local security CANDIDATE for downstream agent verification, NOT a verified
4725
+ * vulnerability. Emitted only by `fallow security`, never under bare `fallow`
4726
+ * or the `audit` gate. There is deliberately no `confidence` or
4727
+ * `signal_strength` field: fallow does not prove exploitability, so the trace
4728
+ * (its hops and length) is the only honest signal.
5777
4729
  */
5778
- git_sha?: (string | null)
4730
+ export interface SecurityFinding {
4731
+ kind: SecurityFindingKind
5779
4732
  /**
5780
- * ISO-8601 timestamp of the recording run.
4733
+ * The catalogue category id (e.g. `"dangerous-html"`). `None` for
4734
+ * `ClientServerLeak`; `Some` for `TaintedSink`.
5781
4735
  */
5782
- timestamp: string
5783
- }
4736
+ category?: (string | null)
5784
4737
  /**
5785
- * Envelope emitted by bare `fallow --format json` (the combined
5786
- * invocation). Wraps the per-analysis sub-results inside a single envelope
5787
- * with the standard `schema_version` / `version` / `elapsed_ms` header.
5788
- *
5789
- * Each sub-result is `Option<...>` so `--only` / `--skip` can suppress a
5790
- * pass without leaving an empty key on the wire. The `check` sub-result is
5791
- * the full [`CheckOutput`] envelope (including its own `schema_version` /
5792
- * `version` / `elapsed_ms`), `dupes` is the typed [`DupesReportPayload`]
5793
- * emitted via `crate::output_dupes::DupesReportPayload::from_report`, and
5794
- * `health` is the bare [`HealthReport`] body: the runtime emit calls
5795
- * `serde_json::to_value(&report)` directly rather than wrapping it in the
5796
- * per-command envelope. The committed schema points `dupes` at
5797
- * `#/definitions/DupesReportPayload` and `health` at
5798
- * `#/definitions/HealthReport` so the documented shape matches the
5799
- * wire; the `committed_property_refs_match_derived_property_refs`
5800
- * drift test enforces the alignment.
4738
+ * The CWE number declared by the matched catalogue entry. `None` for
4739
+ * `ClientServerLeak`; never fabricated beyond the catalogue's value.
5801
4740
  */
5802
- export interface CombinedOutput {
5803
- schema_version: SchemaVersion
5804
- version: ToolVersion
5805
- elapsed_ms: ElapsedMs
4741
+ cwe?: (number | null)
5806
4742
  /**
5807
- * Sectioned `_meta` block emitted only when `--explain` is passed.
5808
- * Contains `check`, `dupes`, and/or `health` keys matching the analyses
5809
- * enabled for the combined run.
4743
+ * File the finding is anchored on (the client boundary). Absolute
4744
+ * internally; JSON strips the project root via `serde_path::serialize`.
5810
4745
  */
5811
- _meta?: (CombinedMeta | null)
4746
+ path: string
5812
4747
  /**
5813
- * Dead-code analysis sub-envelope. Absent when `--skip check`.
4748
+ * 1-based line number of the anchor.
5814
4749
  */
5815
- check?: (CheckOutput | null)
4750
+ line: number
5816
4751
  /**
5817
- * Duplication analysis body (typed [`DupesReportPayload`], not the full
5818
- * `DupesOutput` envelope). Absent when `--skip dupes`. The payload
5819
- * wraps each clone group / family with its typed `actions[]` array via
5820
- * `crate::output_dupes::DupesReportPayload::from_report`.
4752
+ * 0-based byte column offset of the anchor.
5821
4753
  */
5822
- dupes?: (DupesReportPayload | null)
4754
+ col: number
5823
4755
  /**
5824
- * Complexity analysis body (bare `HealthReport`, not the full
5825
- * `HealthOutput` envelope). Absent when `--skip health`.
4756
+ * Agent/human-readable evidence (e.g. the named env var the chain reaches).
5826
4757
  */
5827
- health?: (HealthReport | null)
4758
+ evidence: string
4759
+ /**
4760
+ * Structural import-hop trace from the client boundary to the secret source.
4761
+ * The hop count is the uncalibrated signal; fallow does not prove the path
4762
+ * is exploitable.
4763
+ */
4764
+ trace: TraceHop[]
4765
+ /**
4766
+ * Machine-actionable next steps. Always emitted (possibly empty for
4767
+ * forward-compat). For security candidates this is a single file-level
4768
+ * suppress hint (`auto_fixable: false`); there is no auto-fix because
4769
+ * verification is the agent's job, not fallow's.
4770
+ */
4771
+ actions: IssueAction[]
5828
4772
  }
5829
4773
  /**
5830
- * Sectioned `_meta` block for the bare combined JSON envelope.
4774
+ * One hop in a security finding's structural trace. Stored as an absolute path
4775
+ * internally; JSON serialization strips the project root via
4776
+ * `serde_path::serialize`.
5831
4777
  */
5832
- export interface CombinedMeta {
4778
+ export interface TraceHop {
5833
4779
  /**
5834
- * Dead-code metadata from `crate::explain::check_meta()`.
4780
+ * File on this hop of the import chain.
5835
4781
  */
5836
- check?: (Meta | null)
4782
+ path: string
5837
4783
  /**
5838
- * Duplication metadata from `crate::explain::dupes_meta()`.
4784
+ * 1-based line number. Import-chain hops point at the import site; the
4785
+ * terminal secret-source hop points at the source module when extraction
4786
+ * does not carry a more precise member-access span.
5839
4787
  */
5840
- dupes?: (Meta | null)
4788
+ line: number
4789
+ /**
4790
+ * 0-based byte column offset.
4791
+ */
4792
+ col: number
4793
+ role: TraceHopRole
4794
+ }
5841
4795
  /**
5842
- * Health metadata from `crate::explain::health_meta()`.
4796
+ * Bare `fallow --format json` envelope.
5843
4797
  */
4798
+ export interface CombinedOutput {
4799
+ schema_version: SchemaVersion
4800
+ version: ToolVersion
4801
+ elapsed_ms: ElapsedMs
4802
+ _meta?: (CombinedMeta | null)
4803
+ check?: (CheckOutput | null)
4804
+ dupes?: (DupesReportPayload | null)
4805
+ health?: (HealthReport | null)
4806
+ }
4807
+ export interface CombinedMeta {
4808
+ check?: (Meta | null)
4809
+ dupes?: (Meta | null)
5844
4810
  health?: (Meta | null)
5845
4811
  }
5846
4812
  /**
@@ -5848,23 +4814,10 @@ health?: (Meta | null)
5848
4814
  */
5849
4815
  export interface CodeClimateIssue {
5850
4816
  type: CodeClimateIssueKind
5851
- /**
5852
- * Fallow rule identifier (always starts with `fallow/`).
5853
- */
5854
4817
  check_name: string
5855
- /**
5856
- * Human-readable description of the finding.
5857
- */
5858
4818
  description: string
5859
- /**
5860
- * Free-form categories applied by the report renderer.
5861
- */
5862
4819
  categories: string[]
5863
4820
  severity: CodeClimateSeverity
5864
- /**
5865
- * Stable fingerprint used by CI dashboards to deduplicate findings
5866
- * across runs.
5867
- */
5868
4821
  fingerprint: string
5869
4822
  location: CodeClimateLocation
5870
4823
  }