1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
//! Types that describe fallow's JSON output contract.
//!
//! Today the JSON serialization layer (`crates/cli/src/report/json.rs`) builds
//! its output via `serde_json::json!` macros. The types defined here are the
//! schema-side counterpart of that output: they document, with Rust's type
//! system, the augmentations the JSON layer adds to each per-finding struct
//! (the `actions` array on every finding, the optional `introduced` flag in
//! audit-mode sub-results).
//!
//! The `schema-emit` binary derives `JsonSchema` for these types (gated by the
//! `schema` cargo feature) so the public `docs/output-schema.json` stays in
//! sync with the Rust source of truth. A future refactor will route the JSON
//! emission path through these types directly, eliminating the drift class
//! between the augmentation list here and the `serde_json::json!` builders.
use Serialize;
/// A suggested action attached to a finding in the JSON output. Each finding
/// carries an `actions` array; consumers (agents, IDE clients, CI bots) can
/// dispatch on the `type` discriminant to choose the right remediation.
///
/// The discriminator is `type` (snake_case `type` field), the payload uses the
/// matching kebab-case identifier per variant.
///
/// ## `auto_fixable` is per-finding, not per action type
///
/// Every action variant carries an `auto_fixable: bool` field. The value is
/// evaluated PER FINDING, not per action type: the same action type may
/// appear with `auto_fixable: true` on one finding and `auto_fixable: false`
/// on another, depending on per-instance guards in the `fallow fix` applier.
/// Agents that filter on `auto_fixable: true` must branch on the bool of
/// each individual finding's action, not on the action `type` alone.
///
/// Current per-instance flips:
///
/// - `remove-catalog-entry` (`unused-catalog-entries`): `true` only when the
/// finding's `hardcoded_consumers` array is empty. When a workspace
/// package still pins a hardcoded version of the same package, `fallow fix`
/// skips the entry to avoid breaking `pnpm install`, and the action is
/// emitted with `auto_fixable: false`.
/// - `remove-dependency` vs `move-dependency` (dependency findings): when the
/// finding's `used_in_workspaces` array is non-empty, the primary action
/// flips to `move-dependency` with `auto_fixable: false` (`fallow fix` will
/// not remove a dependency that another workspace imports). On findings
/// without cross-workspace consumers the action stays `remove-dependency`
/// with `auto_fixable: true`.
/// - `add-to-config` for `ignoreExports` (`duplicate-exports`): `true` when
/// `fallow fix` can safely apply the action without further user setup.
/// That is: a fallow config file exists on disk, OR no config exists AND
/// the working directory is NOT inside a monorepo subpackage (in which
/// case the applier creates `.fallowrc.json` from `fallow init`'s
/// framework-aware scaffolding and layers the new rules on top).
/// `false` inside a monorepo subpackage with no workspace-root config
/// (the applier refuses to fragment per-package configs across the
/// monorepo and points at the workspace root instead).
/// - `update-catalog-reference` (`unresolved-catalog-references`): always
/// `false` today (the catalog-switching applier is not wired in yet); the
/// field is non-singleton so that future enablement does not require a
/// schema change.
///
/// All `suppress-line` and `suppress-file` actions are uniformly
/// `auto_fixable: false`. The field is non-singleton on the wire so that a
/// future auto-applier (e.g. an LLM-driven suppression writer) can promote
/// individual variants without a schema bump.
/// A code-change fix. `type` is one of the kebab-case identifiers in
/// [`FixActionType`].
/// Discriminant string for [`FixAction`]. Kebab-case per the JSON output
/// contract.
/// Inline-comment suppression for a single finding line.
/// Singleton discriminant for [`SuppressLineAction`].
/// Scope marker for line suppressions that span multiple locations.
/// File-wide suppression placed at the top of the source file.
/// Singleton discriminant for [`SuppressFileAction`].
/// Edit a fallow config file (`.fallowrc.json`, `fallow.toml`, etc.) to
/// add the offending value to an `ignore*` rule.
/// Singleton discriminant for [`AddToConfigAction`].
/// 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.
/// Single `ignoreExports` rule entry. The fallow config accepts an array of
/// these under the `ignoreExports` key.
/// 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>`.