opencodekit 0.23.1 → 0.23.2

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.
@@ -0,0 +1,644 @@
1
+ # Fallow: Critical Gotchas
2
+
3
+ Common pitfalls and their correct solutions when working with fallow.
4
+
5
+ ---
6
+
7
+ ## `fix` Requires `--yes` in Non-TTY Environments
8
+
9
+ The `fix` command prompts for confirmation in interactive terminals. In agent subprocesses, CI pipelines, or piped input (non-TTY), the `--yes` flag is mandatory. Without it, `fix` exits with code 2 and an error.
10
+
11
+ ```bash
12
+ # WRONG: fix exits with code 2 in non-TTY
13
+ fallow fix --format json --quiet
14
+
15
+ # CORRECT: always use --dry-run first, then --yes
16
+ fallow fix --dry-run --format json --quiet # preview
17
+ fallow fix --yes --format json --quiet # apply
18
+ ```
19
+
20
+ Always preview with `--dry-run` before applying. This is a destructive operation that modifies source files.
21
+
22
+ ---
23
+
24
+ ## Don't Create Config Unless Needed
25
+
26
+ Fallow works with zero configuration for most projects thanks to 118 auto-detecting framework plugins. Creating an unnecessary config file can mask issues or override detection behavior.
27
+
28
+ ```bash
29
+ # WRONG: creating config for a standard Next.js project
30
+ fallow init
31
+ # This may override auto-detected settings
32
+
33
+ # CORRECT: run analysis first with zero config
34
+ fallow dead-code --format json --quiet
35
+ # Only create config if you need to customize rules, ignore patterns, or entry points
36
+ ```
37
+
38
+ Only create a config when you need to:
39
+ - Change rule severity levels for incremental adoption
40
+ - Add custom ignore patterns or ignore dependencies
41
+ - Specify additional entry points not auto-detected
42
+ - Configure duplication detection settings
43
+
44
+ ---
45
+
46
+ ## Use `--format json` for Agent Consumption
47
+
48
+ Human-formatted output contains ANSI colors, progress bars, and timing info. Never parse it programmatically.
49
+
50
+ ```bash
51
+ # WRONG: parsing human output
52
+ fallow dead-code | grep "unused"
53
+
54
+ # CORRECT: use structured JSON
55
+ fallow dead-code --format json --quiet
56
+ ```
57
+
58
+ The `--quiet` flag suppresses progress bars on stderr. Without it, stderr output may interfere with stdout parsing.
59
+
60
+ ---
61
+
62
+ ## `--changed-since` Shows Only New Issues
63
+
64
+ The `--changed-since` flag limits analysis to files modified since a git ref. It only reports issues in those files, not all issues in the project. Works with both `dead-code` and `dupes`.
65
+
66
+ ```bash
67
+ # This only shows issues in files changed since main
68
+ fallow dead-code --format json --quiet --changed-since main
69
+
70
+ # Same for duplication — only clone groups involving changed files
71
+ fallow dupes --format json --quiet --changed-since main
72
+
73
+ # This shows ALL issues in the project
74
+ fallow dead-code --format json --quiet
75
+ ```
76
+
77
+ Don't use `--changed-since` when auditing the full project. Use it for PR checks and incremental CI.
78
+
79
+ ---
80
+
81
+ ## Filter Flags Are Additive
82
+
83
+ Issue type filter flags (`--unused-exports`, `--unused-files`, etc.) are inclusive. They select which issue types to show. Using multiple flags shows the union.
84
+
85
+ ```bash
86
+ # Shows only unused exports
87
+ fallow dead-code --format json --quiet --unused-exports
88
+
89
+ # Shows unused exports AND unused files
90
+ fallow dead-code --format json --quiet --unused-exports --unused-files
91
+
92
+ # Shows ALL issue types (default when no filter is specified)
93
+ fallow dead-code --format json --quiet
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Syntactic Analysis: No TypeScript Compiler
99
+
100
+ Fallow uses Oxc for pure syntactic analysis. It does not run the TypeScript compiler. This means:
101
+
102
+ - **Fully dynamic imports** (`import(variable)`) are not resolved. Only static strings, template literals with static prefixes, `import.meta.glob`, and `require.context` patterns
103
+ - **Value-level type narrowing** is not performed. Fallow can't know that `if (x instanceof Foo)` means `Foo` is "used"
104
+ - **Conditional exports** based on runtime values are not analyzed
105
+ - **Function overload signatures are deduplicated**: TypeScript function overloads (multiple signatures for the same function name) are merged into a single export. They are not reported as separate unused exports
106
+
107
+ ```typescript
108
+ // RESOLVED: static pattern with prefix
109
+ import(`./locales/${lang}.json`);
110
+
111
+ // RESOLVED: import.meta.glob
112
+ const modules = import.meta.glob('./modules/*.ts');
113
+
114
+ // NOT RESOLVED: fully dynamic
115
+ const mod = import(someVariable);
116
+ ```
117
+
118
+ If fallow falsely flags something due to dynamic patterns, use inline suppression:
119
+
120
+ ```typescript
121
+ // fallow-ignore-next-line unused-export
122
+ export const dynamicallyUsed = createHandler();
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Re-Export Chains Are Resolved
128
+
129
+ Fallow fully resolves `export *` and named re-export chains through barrel files. An export consumed through a chain of barrel files is NOT falsely flagged.
130
+
131
+ ```typescript
132
+ // src/utils.ts
133
+ export const helper = () => {}; // NOT flagged, used via barrel chain
134
+
135
+ // src/index.ts (barrel)
136
+ export * from './utils';
137
+
138
+ // src/app.ts
139
+ import { helper } from './index'; // Resolves through the chain
140
+ ```
141
+
142
+ If an export IS flagged as unused despite being in a barrel file, it means no downstream consumer actually imports it. The barrel file re-exports it, but nobody uses it from there.
143
+
144
+ ---
145
+
146
+ ## Exit Code 1 vs 2
147
+
148
+ | Code | Meaning | Action |
149
+ |------|---------|--------|
150
+ | 0 | No error-severity issues | Success |
151
+ | 1 | Error-severity issues found | Review findings |
152
+ | 2 | Runtime error (`fix` without `--yes` in non-TTY, invalid config) | Fix config or add `--yes` |
153
+
154
+ Exit code 1 is triggered by issues with `"error"` severity in the rules config. Without a rules section, all issue types default to `"error"`. Use the rules system to control which issues fail CI:
155
+
156
+ ```jsonc
157
+ // Only fail on unused files and deps, warn on everything else
158
+ {
159
+ "rules": {
160
+ "unused-files": "error",
161
+ "unused-dependencies": "error",
162
+ "unused-exports": "warn",
163
+ "unused-types": "warn"
164
+ }
165
+ }
166
+ ```
167
+
168
+ ---
169
+
170
+ ## `--fail-on-issues` Promotes Warn to Error
171
+
172
+ The `--fail-on-issues` flag promotes all `warn`-severity rules to `error` for that run. This means exit code 1 for ANY reported issue.
173
+
174
+ ```bash
175
+ # With rules: { "unused-exports": "warn" }
176
+
177
+ # This exits 0 even with warn-level findings
178
+ fallow dead-code --format json --quiet
179
+
180
+ # This exits 1 if ANY issue is found (warn promoted to error)
181
+ fallow dead-code --format json --quiet --fail-on-issues
182
+ ```
183
+
184
+ Use `--fail-on-issues` for strict CI gates. Use the rules system for gradual adoption.
185
+
186
+ ---
187
+
188
+ ## Baseline Comparison Tracks Issue Identity
189
+
190
+ Baselines track issues by identity (file + issue type + name), not by count. Adding a new unused export while fixing an old one doesn't cancel out.
191
+
192
+ ```bash
193
+ # Save current state as baseline
194
+ fallow dead-code --format json --quiet --save-baseline fallow-baselines/dead-code.json
195
+
196
+ # Later: only fail on NEW issues not in the baseline
197
+ fallow dead-code --format json --quiet --baseline fallow-baselines/dead-code.json --fail-on-issues
198
+ ```
199
+
200
+ Commit the baseline file to your repo. Update it periodically as you fix existing issues.
201
+
202
+ ---
203
+
204
+ ## Duplication Modes Affect What's Detected
205
+
206
+ The detection mode significantly affects results. Choose based on your needs:
207
+
208
+ ```bash
209
+ # strict: exact token match only
210
+ fallow dupes --format json --quiet --mode strict
211
+ # Catches: copy-pasted code with zero changes
212
+
213
+ # mild (default): syntax normalized
214
+ fallow dupes --format json --quiet --mode mild
215
+ # Catches: whitespace and semicolon differences
216
+
217
+ # weak: literal values normalized
218
+ fallow dupes --format json --quiet --mode weak
219
+ # Catches: same structure with different strings/numbers
220
+
221
+ # semantic: identifier names normalized
222
+ fallow dupes --format json --quiet --mode semantic
223
+ # Catches: same logic with renamed variables
224
+ ```
225
+
226
+ `semantic` mode produces the most findings but may include false positives where similar structure is coincidental.
227
+
228
+ ---
229
+
230
+ ## Workspace Flag Scopes Output, Not Analysis
231
+
232
+ The `--workspace` flag scopes **output** to a single package, but the full cross-workspace module graph is still built. This means:
233
+
234
+ - Imports from other workspace packages are still resolved
235
+ - Re-export chains crossing package boundaries are still tracked
236
+ - Only issues IN the specified package are reported
237
+
238
+ ```bash
239
+ # Analyze everything, show only issues in "my-package"
240
+ fallow dead-code --format json --quiet --workspace my-package
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Production Mode Excludes Test Files
246
+
247
+ `--production` excludes test/dev files and only analyzes production scripts. This changes what's reported:
248
+
249
+ - Test files (`*.test.*`, `*.spec.*`, `*.stories.*`, `__tests__/**`) are excluded
250
+ - Only `start`, `build`, `serve`, `preview`, `prepare` scripts are analyzed
251
+ - Unused devDependencies are NOT reported (forced to `off`)
252
+ - Type-only production dependencies ARE reported (should be devDependencies)
253
+
254
+ ```bash
255
+ # WRONG: using --production for a full audit
256
+ fallow dead-code --format json --quiet --production
257
+ # Misses test-file dead code and devDependency issues
258
+
259
+ # CORRECT: use --production only for production-focused CI
260
+ fallow dead-code --format json --quiet --production --fail-on-issues
261
+ ```
262
+
263
+ ---
264
+
265
+ ## Watch Mode Is Not for Agents
266
+
267
+ The `watch` command starts an interactive file watcher that never exits. Never use it in agent workflows.
268
+
269
+ ```bash
270
+ # WRONG: this will hang forever
271
+ fallow watch
272
+
273
+ # CORRECT: run one-shot analysis
274
+ fallow dead-code --format json --quiet
275
+ ```
276
+
277
+ ---
278
+
279
+ ## Suppressing Duplication False Positives
280
+
281
+ Code duplication has its own suppression token: `code-duplication`. Use it for intentionally similar code (e.g., test helpers, generated patterns).
282
+
283
+ ```typescript
284
+ // WRONG: using the wrong token
285
+ // fallow-ignore-file unused-export
286
+ // This suppresses dead code, not duplication
287
+
288
+ // CORRECT: suppress duplication for a specific line
289
+ // fallow-ignore-next-line code-duplication
290
+ const handler = createStandardHandler(config);
291
+
292
+ // CORRECT: suppress all duplication in a file
293
+ // fallow-ignore-file code-duplication
294
+ ```
295
+
296
+ This is separate from the dead code suppression tokens. See the full list of valid tokens in the [CLI Reference](cli-reference.md#inline-suppression-comments).
297
+
298
+ ---
299
+
300
+ ## Decorated Members Are Skipped By Default
301
+
302
+ Class members with decorators (NestJS `@Get()`, Angular `@Input()`, TypeORM `@Column()`, etc.) are excluded from unused member detection by default. Decorator-driven frameworks consume these via reflection at runtime, so reporting them as unused would be a false positive.
303
+
304
+ ```typescript
305
+ class UserController {
306
+ @Get('/users')
307
+ getUsers() { ... } // NOT flagged, has decorator
308
+ }
309
+ ```
310
+
311
+ ### Opt specific decorators out via `ignoreDecorators`
312
+
313
+ If you use utility decorators that DO NOT imply reflective use (Playwright's `@step("label")`, internal labeling decorators like `@measure`, `@log`, `@retry`), list their names in the `ignoreDecorators` config option so the methods carrying them are checked for usage like undecorated methods.
314
+
315
+ ```jsonc
316
+ // .fallowrc.json
317
+ {
318
+ "ignoreDecorators": ["@step"]
319
+ }
320
+ ```
321
+
322
+ Conservative semantics: a method carrying any decorator NOT in the list still gets skipped. So `@step` + `@Inject` on the same method stays treated as framework-managed. Matching rule: entries containing `.` (`"decorators.log"`) match the full dotted path; bare entries (`"step"` or `"decorators"`) match the leftmost segment, so a single bare `"decorators"` entry collapses an entire `@decorators.*` namespace. Both `"@step"` and `"step"` round-trip equivalently. Unmatched entries (a decorator name in the config that never appears in your codebase) surface as a one-time warning at end of run.
323
+
324
+ The default empty list preserves today's skip-all behavior, so existing NestJS / Angular / TypeORM projects see no change.
325
+
326
+ ---
327
+
328
+ ## JSDoc Visibility Tags Keep Exports Alive
329
+
330
+ Exports annotated with `/** @public */`, `/** @internal */`, `/** @beta */`, `/** @alpha */`, or `/** @api public */` are never reported as unused. This is designed for library authors whose exports are consumed by external projects not visible to fallow.
331
+
332
+ ```typescript
333
+ // NOT flagged: @public annotation
334
+ /** @public */
335
+ export const createWidget = () => {};
336
+
337
+ // NOT flagged: @internal annotation
338
+ /** @internal */
339
+ export const resetState = () => {};
340
+
341
+ // NOT flagged: @beta annotation
342
+ /** @beta */
343
+ export const experimentalFeature = () => {};
344
+
345
+ // NOT flagged: @alpha annotation
346
+ /** @alpha */
347
+ export const unstableApi = () => {};
348
+
349
+ // NOT flagged: @api public variant
350
+ /** @api public */
351
+ export interface WidgetConfig {}
352
+
353
+ // STILL flagged: line comments don't count
354
+ // @public
355
+ export const notProtected = () => {};
356
+ ```
357
+
358
+ Only `/** */` JSDoc block comments are recognized. Line comments (`// @public`) are ignored.
359
+
360
+ ---
361
+
362
+ ## `@expected-unused` JSDoc Tag for Intentional Dead Code
363
+
364
+ Exports annotated with `/** @expected-unused */` are treated as intentionally unused. They are excluded from unused export detection AND tracked for staleness. If the export later becomes used (imported by another module), fallow reports the `@expected-unused` tag as stale via the `stale-suppressions` rule.
365
+
366
+ ```typescript
367
+ // NOT flagged as unused: @expected-unused annotation
368
+ /** @expected-unused */
369
+ export const deprecatedHelper = () => {};
370
+
371
+ // If something starts importing deprecatedHelper,
372
+ // fallow reports the @expected-unused tag as stale
373
+ ```
374
+
375
+ Use `@expected-unused` instead of `// fallow-ignore-next-line` when you want fallow to notify you if the export becomes referenced again. The `stale-suppressions` rule (default: `warn`) controls severity.
376
+
377
+ Only `/** */` JSDoc block comments are recognized. The tag works on all export types.
378
+
379
+ ---
380
+
381
+ ## Stale Suppression Detection
382
+
383
+ Fallow detects `// fallow-ignore` comments and `@expected-unused` JSDoc tags that no longer match any issue. This prevents suppression comments from silently hiding issues that have been resolved or moved.
384
+
385
+ ```typescript
386
+ // STALE: the export below is actually used now
387
+ // fallow-ignore-next-line unused-export
388
+ export const helper = () => {}; // imported in app.ts
389
+ ```
390
+
391
+ Use `--stale-suppressions` to filter for only stale suppression findings. The `stale-suppressions` rule defaults to `warn`. Set to `error` in CI to enforce suppression hygiene:
392
+
393
+ ```jsonc
394
+ {
395
+ "rules": {
396
+ "stale-suppressions": "error"
397
+ }
398
+ }
399
+ ```
400
+
401
+ ---
402
+
403
+ ## JSDoc `import()` Types Count as References
404
+
405
+ Types referenced only from JSDoc `import()` annotations are tracked as type-only imports, so the referenced exports are not flagged as unused. This works for plain JavaScript files that want TypeScript types without converting to `.ts`.
406
+
407
+ ```js
408
+ // src/app.js
409
+
410
+ /**
411
+ * @param cfg {import('./types.ts').Config}
412
+ * @returns {import('./types.ts').Result}
413
+ */
414
+ function boot(cfg) {
415
+ return { ok: true };
416
+ }
417
+ ```
418
+
419
+ Fallow treats `Config` and `Result` in `./types.ts` as used. Works with `@param`, `@returns`, `@type`, `@typedef`, `@callback`, union annotations (`{import('./a').A | import('./b').B}`), nested member access, bare package specifiers, and parent-relative paths. Only `/** */` blocks are scanned.
420
+
421
+ ---
422
+
423
+ ## JSX `<script src>` and `<link href>` Are Asset References
424
+
425
+ Inside JSX/TSX files, lowercase intrinsic `<script src="...">` and `<link rel="stylesheet|modulepreload" href="...">` are tracked as asset references, same as in plain HTML files. This is needed for SSR frameworks like Hono where layout components emit HTML via JSX.
426
+
427
+ ```tsx
428
+ // src/layout.tsx
429
+
430
+ export const Layout = () => (
431
+ <html>
432
+ <head>
433
+ <link rel="stylesheet" href="/static/style.css" />
434
+ <script src="/static/app.js"></script>
435
+ </head>
436
+ <body><h1>Hello</h1></body>
437
+ </html>
438
+ );
439
+ ```
440
+
441
+ Fallow marks `static/style.css` and `static/app.js` as reachable. Root-relative paths (starting with `/`) resolve from the source file's parent directory first, then the project root, matching how Vite/Parcel/Hono serve static assets. Only `StringLiteral` attribute values are captured: expression containers (`href={someVar}`) and capitalized React-style components (`<Script>`, `<Link>`) are intentionally ignored because they have component-specific semantics.
442
+
443
+ ---
444
+
445
+ ## GraphQL `#import` Documents Are Tracked
446
+
447
+ GraphQL `.graphql` and `.gql` files can keep nearby fragment documents reachable with relative `#import` comments. Fallow tracks `./` and `../` specifiers, including extensionless imports that resolve through `.graphql` and `.gql`; package-style specifiers are ignored.
448
+
449
+ ```graphql
450
+ # src/query.graphql
451
+
452
+ #import "./fragments/user-fields"
453
+
454
+ query CurrentUser {
455
+ currentUser {
456
+ ...UserFields
457
+ }
458
+ }
459
+ ```
460
+
461
+ Fallow marks `src/fragments/user-fields.graphql` or `src/fragments/user-fields.gql` as reachable when either file exists. A typo in the relative path is reported as an unresolved import instead of silently dropping the edge.
462
+
463
+ ---
464
+
465
+ ## Library Packages: Use `publicPackages` Instead of Visibility Tags
466
+
467
+ In monorepos, shared library packages have exported APIs consumed by external consumers not visible to fallow. Instead of annotating every export with `/** @public */` (or `@internal`, `@beta`, `@alpha`), use the `publicPackages` config to mark entire workspace packages as public libraries. Exports and exported enum/class members from these packages are excluded from unused API detection.
468
+
469
+ ```jsonc
470
+ {
471
+ "publicPackages": ["@myorg/shared-lib", "@myorg/ui-kit"]
472
+ }
473
+ ```
474
+
475
+ This is the correct solution for library false positives in monorepos. Only use JSDoc visibility tags (`/** @public */`, `/** @internal */`, etc.) for individual exports in application packages.
476
+
477
+ ---
478
+
479
+ ## Dynamically Loaded Files: Use `dynamicallyLoaded`
480
+
481
+ Files loaded at runtime via plugin systems, locale directories, or lazy module patterns are not statically reachable from entry points. Use `dynamicallyLoaded` to mark these files as always-used.
482
+
483
+ ```jsonc
484
+ {
485
+ "dynamicallyLoaded": ["plugins/**/*.ts", "locales/**/*.json"]
486
+ }
487
+ ```
488
+
489
+ ```bash
490
+ # WRONG: suppressing individual files
491
+ # fallow-ignore-file unused-file (in each plugin file)
492
+
493
+ # CORRECT: declare the pattern in config
494
+ # { "dynamicallyLoaded": ["plugins/**/*.ts"] }
495
+ ```
496
+
497
+ This is preferable to adding inline suppression comments to every dynamically loaded file.
498
+
499
+ ---
500
+
501
+ ## Class Instance Members Are Tracked
502
+
503
+ Fallow tracks class member usage through instance variables. If you instantiate a class and call methods on the instance, those members are correctly marked as used.
504
+
505
+ ```typescript
506
+ class MyService {
507
+ greet() { return 'hello'; } // NOT flagged: used via instance
508
+ unused() { return 'bye'; } // Flagged: never called
509
+ }
510
+
511
+ const svc = new MyService();
512
+ svc.greet();
513
+ ```
514
+
515
+ This also handles whole-object instance patterns (`Object.values(svc)`, `{ ...svc }`, `for..in`) conservatively (all members marked as used). The tracking is scope-unaware, so same-named variables in different scopes may produce false negatives (not false positives).
516
+
517
+ ---
518
+
519
+ ## Type-Only Dependencies Should Be devDependencies
520
+
521
+ In `--production` mode, fallow detects production dependencies that are only imported via `import type`. Since TypeScript types are erased at runtime, these packages should be in `devDependencies` instead.
522
+
523
+ ```typescript
524
+ // If "zod" is in dependencies (not devDependencies):
525
+ import type { ZodSchema } from 'zod'; // Flagged as type-only dependency
526
+
527
+ // This is a real import, not type-only:
528
+ import { z } from 'zod'; // NOT flagged
529
+ ```
530
+
531
+ ```bash
532
+ # Detect type-only dependencies (reported automatically with --production)
533
+ fallow dead-code --format json --quiet --production
534
+
535
+ # Suppress for a specific dependency
536
+ # fallow-ignore-next-line type-only-dependency
537
+ ```
538
+
539
+ The `type-only-dependencies` rule defaults to `warn`. Suppress with `"type-only-dependencies": "off"` in your rules config if you intentionally keep type-only packages in production dependencies.
540
+
541
+ ---
542
+
543
+ ## Test-Only Dependencies Should Be devDependencies
544
+
545
+ Fallow detects production dependencies that are only imported from test files (`*.test.*`, `*.spec.*`, `__tests__/**`). Since these packages are never used in production code, they should be in `devDependencies` instead.
546
+
547
+ ```typescript
548
+ // If "msw" is in dependencies (not devDependencies):
549
+ // src/handlers.test.ts
550
+ import { setupServer } from 'msw/node'; // Flagged as test-only dependency
551
+
552
+ // src/app.ts — no imports of "msw" here
553
+ ```
554
+
555
+ ```bash
556
+ # Detect test-only dependencies (reported automatically)
557
+ fallow dead-code --format json --quiet
558
+
559
+ # Suppress for a specific dependency
560
+ # fallow-ignore-next-line test-only-dependency
561
+ ```
562
+
563
+ The `test-only-dependencies` rule defaults to `warn`. Suppress with `"test-only-dependencies": "off"` in your rules config if you intentionally keep test-only packages in production dependencies.
564
+
565
+ ---
566
+
567
+ ## GitLab CI: `FALLOW_COMMENT` vs `FALLOW_REVIEW`
568
+
569
+ These are separate features and can be used independently or together:
570
+
571
+ - **`FALLOW_COMMENT: "true"`** — posts a single summary comment on the MR with issue counts and a findings table
572
+ - **`FALLOW_REVIEW: "true"`** — posts inline code review comments on the exact MR diff lines where issues were found
573
+
574
+ ```yaml
575
+ # WRONG: expecting inline review comments from FALLOW_COMMENT
576
+ variables:
577
+ FALLOW_COMMENT: "true"
578
+ # This only posts a summary comment, not inline annotations
579
+
580
+ # CORRECT: use FALLOW_REVIEW for inline diff comments
581
+ variables:
582
+ FALLOW_REVIEW: "true"
583
+
584
+ # CORRECT: use both for summary + inline
585
+ variables:
586
+ FALLOW_COMMENT: "true"
587
+ FALLOW_REVIEW: "true"
588
+ ```
589
+
590
+ Both require a `GITLAB_TOKEN` CI/CD variable (project access token with `api` scope). `CI_JOB_TOKEN` is read-only for MR notes in the official GitLab API, so it is not enough for summary comments or inline discussions.
591
+
592
+ ---
593
+
594
+ ## License Errors Include a Machine-Readable Code Suffix
595
+
596
+ `fallow license refresh` and `fallow license activate --trial` can fail with a backend error. The CLI always appends the raw HTTP status and the backend error code after the human hint, so scripts can grep for the code without parsing prose:
597
+
598
+ ```
599
+ fallow license refresh: your stored license is too stale to refresh. Reactivate with: fallow license activate --trial --email <addr> (HTTP 401, code token_stale)
600
+ ```
601
+
602
+ Stable codes the CLI surfaces today:
603
+
604
+ | Code | Operation | Meaning |
605
+ |------|-----------|---------|
606
+ | `token_stale` | `refresh` | Stored JWT is more than 45 days past its `exp`. Reactivate. |
607
+ | `invalid_token` | `refresh` | Stored JWT is missing required claims (e.g. `sub`). Reactivate. |
608
+ | `unauthorized` | `refresh` or `trial` | Auth failed. Reactivate. |
609
+ | `rate_limit_exceeded` | `trial` | Trial endpoint is capped at 5 per hour per IP. Wait or use a different network. |
610
+
611
+ To detect a rate-limited trial signup in CI:
612
+
613
+ ```bash
614
+ if fallow license activate --trial --email "$EMAIL" 2>&1 | grep -q "code rate_limit_exceeded"; then
615
+ echo "Trial rate-limited; fallback to cached FALLOW_LICENSE" >&2
616
+ fi
617
+ ```
618
+
619
+ Unknown codes fall back to the backend `message` field when present, otherwise the raw body, so existing scripts that match on HTTP status alone still work.
620
+
621
+ ---
622
+
623
+ ## GitLab CI: Auto `--changed-since` in MR Pipelines
624
+
625
+ The official GitLab CI template automatically sets `--changed-since origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME` in merge request pipelines. You do not need to set `FALLOW_CHANGED_SINCE` manually unless you want a different ref.
626
+
627
+ ```yaml
628
+ # UNNECESSARY: changed-since is auto-detected in MR pipelines
629
+ variables:
630
+ FALLOW_CHANGED_SINCE: "origin/main"
631
+
632
+ # CORRECT: let the template auto-detect
633
+ # (no FALLOW_CHANGED_SINCE needed — it reads the MR target branch)
634
+ ```
635
+
636
+ Override `FALLOW_CHANGED_SINCE` only when you need a specific ref (e.g., a release branch) or want to disable auto-detection by setting it to an empty string.
637
+
638
+ ---
639
+
640
+ ## GitLab CI: Package Manager Detection
641
+
642
+ The GitLab CI template auto-detects the project's package manager from lockfiles (`package-lock.json` for npm, `pnpm-lock.yaml` for pnpm, `yarn.lock` for yarn). MR comments and review comments use the correct commands for the detected manager.
643
+
644
+ This means review comments will show `pnpm remove lodash` instead of `npm uninstall lodash` in a pnpm project. No configuration is needed — detection is automatic.