fallow 3.35.0 → 3.37.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.
- package/README.md +1 -1
- package/capabilities.json +25 -9
- package/issue-registry.json +8 -4
- package/package.json +14 -11
- package/rule-pack-schema.json +264 -0
- package/schema.json +43 -17
- package/skills/fallow/SKILL.md +2 -2
- package/skills/fallow/references/cli-reference.md +13 -12
- package/skills/fallow/references/gotchas.md +3 -1
- package/skills/fallow/references/patterns.md +2 -2
- package/types/output-contract.d.ts +176 -59
- package/scripts/lazy-verify.test.js +0 -564
- package/scripts/platform-package.test.js +0 -26
- package/scripts/run-binary.test.js +0 -293
- package/scripts/sentinel-path.test.js +0 -240
- package/scripts/verify-binary.test.js +0 -809
|
@@ -440,7 +440,7 @@ The `--entry-weight` JSON output carries `entry_weight.entries[]`, one row per r
|
|
|
440
440
|
|
|
441
441
|
To gate eager growth in CI, save a baseline file on the main branch with `fallow list --entry-weight --save-regression-baseline <PATH>`. A later run with `--regression-baseline <PATH>` adds `entry_weight.regression`: per-entry `baseline_eager_bytes`, `current_eager_bytes`, `new_eager_packages` and `exceeded`. The comparison is report-only until you add `--fail-on-regression`; then an entry that grew more than `--tolerance` (bytes, or a percentage such as `5%`) exits 1. A new entry never fails the gate.
|
|
442
442
|
|
|
443
|
-
The `--workspaces` JSON output carries `workspaces[]` (name, project-root-relative path, `is_internal_dependency` bool) plus `workspace_diagnostics[]`. Each diagnostic has a `kind` discriminator (for example `undeclared-workspace`, `malformed-package-json`, `glob-matched-no-package-json`, `workspace-glob-outside-root`, `workspace-pattern-dir-missing`, `nested-project-root`, `malformed-tsconfig`, `tsconfig-reference-dir-missing`, `malformed-pnpm-workspace-yaml`, `skipped-large-file`, `skipped-minified-file`, `skipped-source-dotdir`, `source-read-failure`, `npm-lock-override-resolution-skipped`) with a typed payload (`error`, `pattern`, or none), and a `path` that is project-root-relative with forward slashes on every envelope that carries the array. The same `workspace_diagnostics[]` array is also surfaced on the `fallow dead-code --format json`, `fallow dupes --format json`, and `fallow health --format json` envelopes, at the top level of the bare combined `fallow --format json` envelope, on `fallow audit --format json` under `dead_code`, and on the `audit-brief` envelope shared by `fallow review --format json` and `fallow audit --brief --format json`, also under `dead_code` (omitted when empty). The combined carrier is the envelope root, not a section, so `--skip check`, `--only health`, and `--only dupes` all still report what their analyses recorded. The combined root is the union of what every analysis in the run recorded, deduplicated on the whole `kind` (typed payload included) plus `path`, so two overlapping globs still report the same package-less directory once per `pattern` (a declared glob's no-op `./` prefix is normalised away, so one glob written `"./apps/**"` in `package.json` and `apps/**` in `pnpm-workspace.yaml` stays one entry): a combined run walks the project once per analysis, and a per-analysis `production` mode (`production: { deadCode, health, dupes }`, `--production-health`) can give those walks different file sets, so only the union reports what the run as a whole saw. Each analysis contributes the workspace-discovery list its own config load produced, the same list `fallow list --workspaces` reports, so the combined root can carry an `undeclared-workspace` or `glob-matched-no-package-json` entry that the standalone `dead-code`, `check`, `health`, and `dupes` envelopes, which read the process diagnostics registry instead, do not. `fallow audit --format json` and the `audit-brief` envelope are on the same broad side: they fold the dead-code analysis's own list into their `dead_code.workspace_diagnostics[]`, so they too report an `undeclared-workspace` entry the standalone envelopes miss. The CLI and the programmatic route (MCP code mode, NAPI, embedders) agree on everything an analysis records: both folds close with the same process-registry read, which covers what an analysis records after its section captured its list (a `source-read-failure`, or the analysis-stage kinds a health run's own dead-code precompute records) and skips `skipped-large-file`, `skipped-minified-file`, and `skipped-source-dotdir`, since those reach an envelope only from the walk that recorded them. The analysis-stage kinds (`malformed-pnpm-workspace-yaml`, `bun-lockb-override-resolution-skipped`, `bun-lock-override-resolution-skipped`, `pnpm-lock-override-resolution-skipped`, `npm-lock-override-resolution-skipped`, `bun-resolutions-shadowed-by-overrides`, `pnpm-workspace-overrides-ignored`, `boundaries-not-configured`, `rule-packs-not-configured`) are recorded by the dead-code analyze pass, so they only appear on runs that include it: `fallow dupes --format json` and `fallow --only dupes` report the workspace-discovery and source-discovery kinds alone. A malformed ROOT `package.json` exits 2 at config load; everything else warns and continues. The dead-code result adds two more kinds, each with a `pattern` payload and `path: "."`: `ignore-dependencies-glob-unmatched` (an `ignoreDependencies` glob matched no declared dependency; omitted when the run reports no dependency finding, for example with `--unused-files` or `--file`) and `ignore-findings-pattern-unmatched` (an `ignoreFindings` pattern matched no finding). SARIF carries the same entries under `invocations[0].toolConfigurationNotifications[]` of the dead-code run. Markdown, `github-summary`, `pr-comment-github`, `pr-comment-gitlab` and the summary `body` of `review-github` and `review-gitlab` add an `Unmatched config patterns` section. Human, compact, CodeClimate and `github-annotations` print a stderr note. `fallow report --from` uses the same place as the live run of that format.
|
|
443
|
+
The `--workspaces` JSON output carries `workspaces[]` (name, project-root-relative path, `is_internal_dependency` bool) plus `workspace_diagnostics[]`. Each diagnostic has a `kind` discriminator (for example `undeclared-workspace`, `malformed-package-json`, `glob-matched-no-package-json`, `workspace-glob-outside-root`, `workspace-pattern-dir-missing`, `nested-project-root`, `malformed-tsconfig`, `tsconfig-reference-dir-missing`, `malformed-pnpm-workspace-yaml`, `skipped-large-file`, `skipped-minified-file`, `skipped-source-dotdir`, `source-read-failure`, `npm-lock-override-resolution-skipped`) with a typed payload (`error`, `pattern`, or none), and a `path` that is project-root-relative with forward slashes on every envelope that carries the array. The same `workspace_diagnostics[]` array is also surfaced on the `fallow dead-code --format json`, `fallow dupes --format json`, and `fallow health --format json` envelopes, at the top level of the bare combined `fallow --format json` envelope, on `fallow audit --format json` under `dead_code`, and on the `audit-brief` envelope shared by `fallow review --format json` and `fallow audit --brief --format json`, also under `dead_code` (omitted when empty). The combined carrier is the envelope root, not a section, so `--skip check`, `--only health`, and `--only dupes` all still report what their analyses recorded. The combined root is the union of what every analysis in the run recorded, deduplicated on the whole `kind` (typed payload included) plus `path`, so two overlapping globs still report the same package-less directory once per `pattern` (a declared glob's no-op `./` prefix is normalised away, so one glob written `"./apps/**"` in `package.json` and `apps/**` in `pnpm-workspace.yaml` stays one entry): a combined run walks the project once per analysis, and a per-analysis `production` mode (`production: { deadCode, health, dupes }`, `--production-health`) can give those walks different file sets, so only the union reports what the run as a whole saw. Each analysis contributes the workspace-discovery list its own config load produced, the same list `fallow list --workspaces` reports, so the combined root can carry an `undeclared-workspace` or `glob-matched-no-package-json` entry that the standalone `dead-code`, `check`, `health`, and `dupes` envelopes, which read the process diagnostics registry instead, do not. `fallow audit --format json` and the `audit-brief` envelope are on the same broad side: they fold the dead-code analysis's own list into their `dead_code.workspace_diagnostics[]`, so they too report an `undeclared-workspace` entry the standalone envelopes miss. The CLI and the programmatic route (MCP code mode, NAPI, embedders) agree on everything an analysis records: both folds close with the same process-registry read, which covers what an analysis records after its section captured its list (a `source-read-failure`, or the analysis-stage kinds a health run's own dead-code precompute records) and skips `skipped-large-file`, `skipped-minified-file`, and `skipped-source-dotdir`, since those reach an envelope only from the walk that recorded them. The analysis-stage kinds (`malformed-pnpm-workspace-yaml`, `bun-lockb-override-resolution-skipped`, `bun-lock-override-resolution-skipped`, `pnpm-lock-override-resolution-skipped`, `npm-lock-override-resolution-skipped`, `bun-resolutions-shadowed-by-overrides`, `pnpm-workspace-overrides-ignored`, `boundaries-not-configured`, `rule-packs-not-configured`) are recorded by the dead-code analyze pass, so they only appear on runs that include it: `fallow dupes --format json` and `fallow --only dupes` report the workspace-discovery and source-discovery kinds alone. A malformed ROOT `package.json` exits 2 at config load; everything else warns and continues. The dead-code result adds two more kinds, each with a `pattern` payload and `path: "."`: `ignore-dependencies-glob-unmatched` (an `ignoreDependencies` glob matched no declared dependency; omitted when the run reports no dependency finding, for example with `--unused-files` or `--file`) and `ignore-findings-pattern-unmatched` (an `ignoreFindings` pattern matched no finding). It also adds three kinds for an entry that matched but changed nothing, with the same payload: `entry-pattern-redundant` (every file the `entry` pattern matches is already a runtime entry point from another source), `ignore-dependencies-entry-redundant` (the `ignoreDependencies` entry hid no dependency finding; same gate as the unmatched glob) and `ignore-patterns-entry-redundant` (the `ignorePatterns` glob excluded no file that a built-in ignore, `.gitignore` or the file type filter does not already skip; a glob for build output has an effect only after a build). A `--production` run reports none of the three. SARIF carries the same entries under `invocations[0].toolConfigurationNotifications[]` of the dead-code run. Markdown, `github-summary`, `pr-comment-github`, `pr-comment-gitlab` and the summary `body` of `review-github` and `review-gitlab` add an `Unmatched config patterns` section, and a `Redundant config entries` section for the three redundant kinds. Human, compact, CodeClimate and `github-annotations` print a stderr note. `fallow report --from` uses the same place as the live run of that format.
|
|
444
444
|
|
|
445
445
|
The `--boundaries` JSON output carries `boundaries.logical_groups[]` alongside the existing `zones[]` / `rules[]` arrays. Each logical-group entry surfaces a user-authored `autoDiscover` parent zone (which expansion otherwise flattens into per-child zones like `features/auth` / `features/billing`): `name`, `children`, `auto_discover` (verbatim user strings), `status` (`ok` / `empty` / `invalid_path`), `source_zone_index`, summed `file_count`, optional `authored_rule` (the pre-expansion `{ allow, allowTypeOnly }` keyed on the parent), optional `fallback_zone` cross-reference when the parent also kept its own `patterns` (Bulletproof case), optional `merged_from` (parent zone indices when the user declared the same parent name twice; surfaces the duplicate in JSON instead of only in `tracing::warn!`), optional `original_zone_root` (echo of the parent's `root` subtree scope for monorepo patchers), and optional `child_source_indices` (parallel to `children`, attributing each child to a specific `auto_discover` entry when multiple paths were authored). The full shape is documented in `docs/output-schema.json` under `ListBoundariesOutput`.
|
|
446
446
|
|
|
@@ -526,7 +526,7 @@ Human output groups paths under "Shared with your team (commit these)" and "Loca
|
|
|
526
526
|
{
|
|
527
527
|
"kind": "agent-install",
|
|
528
528
|
"schema_version": 1,
|
|
529
|
-
"fallow_version": "3.
|
|
529
|
+
"fallow_version": "3.37.0",
|
|
530
530
|
"root": "/abs/path",
|
|
531
531
|
"mode": "install",
|
|
532
532
|
"dry_run": false,
|
|
@@ -737,7 +737,7 @@ fallow health --format json --quiet --trend
|
|
|
737
737
|
{
|
|
738
738
|
"kind": "health",
|
|
739
739
|
"schema_version": 7,
|
|
740
|
-
"version": "3.
|
|
740
|
+
"version": "3.37.0",
|
|
741
741
|
"elapsed_ms": 32,
|
|
742
742
|
"summary": {
|
|
743
743
|
"files_analyzed": 482,
|
|
@@ -1148,7 +1148,7 @@ A complexity finding matches its base finding by file path and function name. Th
|
|
|
1148
1148
|
|
|
1149
1149
|
Growth of a metric that the base finding already exceeded, in the same severity tier, makes the finding `worsened`, not introduced.
|
|
1150
1150
|
|
|
1151
|
-
An unchanged or decreased metric keeps the finding inherited, also when the exceeded category changes (for example from `both` to `cyclomatic`). When one file has more than one finding with the same function name, each base finding matches at most one head finding. Head findings with unchanged metric values and an unchanged line count match first. Head findings with unchanged metric values match next. The other findings then match in line order.
|
|
1151
|
+
An unchanged or decreased metric keeps the finding inherited, also when the exceeded category changes (for example from `both` to `cyclomatic`). When one file has more than one finding with the same function name, each base finding matches at most one head finding. Head findings with unchanged metric values and an unchanged line count match first. Head findings with unchanged metric values match next. Then head findings with unchanged cyclomatic and cognitive values match, also when the CRAP score changed, because a change of the coverage input changes the CRAP score. The other findings then match in line order.
|
|
1152
1152
|
|
|
1153
1153
|
A matched finding that is not introduced is `worsened` when a measured value is higher than in the base finding. The measured values are cyclomatic, cognitive, CRAP (only when both sides have a score) and the line count of the function. The line count must grow by at least 5 lines. It includes comments and blank lines, so the minimum keeps a finding inherited after a short comment or a formatter rewrap. Each complexity finding carries `attribution` (`introduced`, `worsened` or `inherited`). When a value grew, it also carries `worsened_metrics` with the `metric`, the `base` value and the `head` value. An introduced finding that matched a base finding also carries `worsened_metrics`. `attribution.worsened` gives the active mode. `attribution.complexity_worsened` counts the worsened findings, which are also in `complexity_inherited`. `--worsened` (config `audit.worsened`) decides their effect on the verdict:
|
|
1154
1154
|
|
|
@@ -1212,7 +1212,7 @@ fallow audit \
|
|
|
1212
1212
|
{
|
|
1213
1213
|
"kind": "audit",
|
|
1214
1214
|
"schema_version": 7,
|
|
1215
|
-
"version": "3.
|
|
1215
|
+
"version": "3.37.0",
|
|
1216
1216
|
"command": "audit",
|
|
1217
1217
|
"verdict": "fail",
|
|
1218
1218
|
"changed_files_count": 12,
|
|
@@ -1300,7 +1300,7 @@ fallow flags --format json --quiet --workspace my-package
|
|
|
1300
1300
|
```json
|
|
1301
1301
|
{
|
|
1302
1302
|
"schema_version": 7,
|
|
1303
|
-
"version": "3.
|
|
1303
|
+
"version": "3.37.0",
|
|
1304
1304
|
"elapsed_ms": 116,
|
|
1305
1305
|
"feature_flags": [],
|
|
1306
1306
|
"total_flags": 0
|
|
@@ -1401,7 +1401,7 @@ fallow security --gate newly-reachable --changed-since origin/main
|
|
|
1401
1401
|
{
|
|
1402
1402
|
"kind": "security",
|
|
1403
1403
|
"schema_version": "4",
|
|
1404
|
-
"version": "3.
|
|
1404
|
+
"version": "3.37.0",
|
|
1405
1405
|
"elapsed_ms": 42,
|
|
1406
1406
|
"config": {
|
|
1407
1407
|
"rules": {
|
|
@@ -1430,7 +1430,7 @@ fallow security --gate newly-reachable --changed-since origin/main
|
|
|
1430
1430
|
{
|
|
1431
1431
|
"kind": "security",
|
|
1432
1432
|
"schema_version": "4",
|
|
1433
|
-
"version": "3.
|
|
1433
|
+
"version": "3.37.0",
|
|
1434
1434
|
"elapsed_ms": 42,
|
|
1435
1435
|
"config": {
|
|
1436
1436
|
"rules": {
|
|
@@ -2245,7 +2245,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
2245
2245
|
{
|
|
2246
2246
|
"kind": "dead-code",
|
|
2247
2247
|
"schema_version": 7,
|
|
2248
|
-
"version": "3.
|
|
2248
|
+
"version": "3.37.0",
|
|
2249
2249
|
"elapsed_ms": 45,
|
|
2250
2250
|
"total_issues": 12,
|
|
2251
2251
|
"entry_points": {
|
|
@@ -2405,7 +2405,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
|
|
|
2405
2405
|
{
|
|
2406
2406
|
"kind": "dupes",
|
|
2407
2407
|
"schema_version": 7,
|
|
2408
|
-
"version": "3.
|
|
2408
|
+
"version": "3.37.0",
|
|
2409
2409
|
"elapsed_ms": 82,
|
|
2410
2410
|
"total_clones": 15,
|
|
2411
2411
|
"total_lines_duplicated": 230,
|
|
@@ -2449,11 +2449,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
|
|
|
2449
2449
|
{
|
|
2450
2450
|
"kind": "combined",
|
|
2451
2451
|
"schema_version": 7,
|
|
2452
|
-
"version": "3.
|
|
2452
|
+
"version": "3.37.0",
|
|
2453
2453
|
"elapsed_ms": 159,
|
|
2454
2454
|
"check": {
|
|
2455
2455
|
"schema_version": 7,
|
|
2456
|
-
"version": "3.
|
|
2456
|
+
"version": "3.37.0",
|
|
2457
2457
|
"elapsed_ms": 45,
|
|
2458
2458
|
"total_issues": 12,
|
|
2459
2459
|
"unused_files": [],
|
|
@@ -2664,6 +2664,7 @@ preset = "bulletproof"
|
|
|
2664
2664
|
- `publicPackages`: workspace packages that are public libraries; exported API surface from these packages is not flagged as unused
|
|
2665
2665
|
- `entry`: glob patterns for files that are manual entry points. A single `*` stays within one path segment; use `**` to match across directories
|
|
2666
2666
|
- `dynamicallyLoaded`: glob patterns for files loaded at runtime (plugin dirs, locale files); treated as always-used. A single `*` stays within one path segment; use `**` to match across directories
|
|
2667
|
+
- `markupExtensions`: file extensions without the dot (for example `["tpl", "erb"]`) of server-side template files whose text counts as CSS class references for `unreferenced-css-class` in `fallow health --css`. `liquid`, `njk`, `hbs`, `handlebars`, `twig`, and `blade.php` are always read. Any class-like word in a template counts as used, so templates are more lenient than Markdown. These files never join discovery or the module graph. A value that ends in a script, stylesheet, data or asset extension (also `module.css` or `d.ts`) is a config error
|
|
2667
2668
|
- Path globs in other fields (`ignorePatterns`, `ignoreFindings`, `overrides[].files`, `ignoreExports[].file`, `duplicates.ignore`, `health.ignore`, boundary zones, rule-pack and `framework[]` globs) use the same rule: a single `*` stays within one path segment. Write `**/*.test.ts` to match test files at any depth
|
|
2668
2669
|
- `cache.dir`: override the persistent extraction cache directory. `FALLOW_CACHE_DIR` wins over this config field, and `--no-cache` disables caching entirely
|
|
2669
2670
|
- `cache.maxSizeMb`: cap the serialized extraction cache size in megabytes. `FALLOW_CACHE_MAX_SIZE` wins over this config field
|
|
@@ -179,7 +179,9 @@ Error-severity findings can trigger exit code 1. Default severity varies by rule
|
|
|
179
179
|
}
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
-
Exit code 1 always means that an enforced gate failed. A load warning or a workspace diagnostic (for example `node-modules-missing` or a tsconfig `extends` that does not resolve) never changes the exit code. The only
|
|
182
|
+
Exit code 1 always means that an enforced gate failed. A load warning or a workspace diagnostic (for example `node-modules-missing` or a tsconfig `extends` that does not resolve) never changes the exit code. The only exceptions are `source-parse-degraded` and `source-nesting-too-deep` with `--fail-on-parse-error`. To find the gate, read `gate_outcomes` in the JSON output and look for an entry with `status: "fail"` and `enforced: true`. Under `--quiet` and in every machine format, fallow also prints one stderr line that names the failed gates, for example `[X] Exit code 1: gate health-findings (3 at or above error) failed.` On `health`, the `complexity-*` rules default to `error`, so each complexity finding fails the run. Set them to `warn` or pass `--report-only` to report without a failure.
|
|
183
|
+
|
|
184
|
+
Bare `fallow` in a machine format (`json`, `sarif`, `codeclimate`, the GitHub formats, and the comment and review formats) exits 0 on findings. `fallow dead-code --format json` exits 1 on the same findings. The bare machine run reports only, because the GitHub Action and the GitLab template read its JSON output and not its exit code. Its `gate_outcomes` entries then have `status: "fail"` and `enforced: false`, and stderr has one line, also under `--quiet`, for example `[W] Exit code 0: gate error-severity-findings (2 at or above error) failed, but bare fallow does not enforce it in --format json. To exit 1 on it, pass --fail-on-issues.` To gate on a bare machine run, pass `--fail-on-issues` or `--ci`.
|
|
183
185
|
|
|
184
186
|
---
|
|
185
187
|
|
|
@@ -784,11 +784,11 @@ Prefer `fallow hooks install --target agent` to install this file. The script is
|
|
|
784
784
|
Behavior you can rely on:
|
|
785
785
|
- The handler walks up from the session directory to the nearest directory that holds `.claude/hooks/fallow-gate.sh` and runs the audit there, so a session in a package directory audits the install root. The walk stops at the first `.git` entry. When it finds no script, the handler runs the script under `$CLAUDE_PROJECT_DIR` from the session directory.
|
|
786
786
|
- The hook process can start in a directory that is not the session directory, for example in the main checkout when the session works in a nested git worktree. The script reads the session directory from the `cwd` field of the hook input and finds the audit root from it with the same walk. When the walk finds no script, for example with the user-scope script in `$HOME`, the audit root is the directory of the hook process when it is in the git work tree of the session. Else it is the git top level of the session directory, or the session directory outside a git repository. The audit root is never `$HOME`. The script compares physical paths, so a symlink does not change the walk. When the hook input has no usable `cwd`, the audit runs in the directory of the hook process.
|
|
787
|
-
- A `git commit` or `git push` can name another work tree. The script audits only that target when the command has the strict form `[cd <dir> &&] git [-C <dir>]... commit|push [<option or name>]...` and git reports another work tree for the target (`git rev-parse --show-toplevel --absolute-git-dir` gives a different top level and a different git directory than for the session directory). For a push, the common git directory must differ too: linked worktrees share refs, so a push from another linked worktree of the session repository audits the session tree and the target. A target in the same work tree keeps the session audit, because `git commit` commits the whole index. Each word in the strict form must be inert: a `-C` directory resolves physically as in git, a `cd` directory starts with `/`, `./` or `../`, a double-quoted word holds no `$`, backquote or backslash, and only a short list of options such as `-m`, `--amend`, `-S` and `-u` is allowed. Any other command audits the session tree as before, plus each directory that it names in another work tree. The gate blocks when one audit fails.
|
|
787
|
+
- A `git commit` or `git push` can name another work tree. The script audits only that target when the command has the strict form `[cd <dir> &&] git [-C <dir>]... commit|push [<option or name>]...` and git reports another work tree for the target (`git rev-parse --show-toplevel --absolute-git-dir` gives a different top level and a different git directory than for the session directory). For a push, the common git directory must differ too: linked worktrees share refs, so a push from another linked worktree of the session repository audits the session tree and the target. The one exception is a push that cannot send the session branch: the options are only `-u`, `-q`, `--set-upstream`, `--force-with-lease` and `--force-if-includes`, `git rev-parse --symbolic-full-name` gives the checked-out branch of the target for each refspec source, that branch is not the session branch, and a push without a refspec has `push.default` unset or `simple`, `current`, `upstream` or `nothing` and no remote `push` or `mirror` setting. That push audits only the target. A git error during this check keeps the session audit. A target in the same work tree keeps the session audit, because `git commit` commits the whole index. Each word in the strict form must be inert: a `-C` directory resolves physically as in git, a `cd` directory starts with `/`, `./` or `../`, a double-quoted word holds no `$`, backquote or backslash, and only a short list of options such as `-m`, `--amend`, `-S` and `-u` is allowed. Any other command audits the session tree as before, plus each directory that it names in another work tree. The gate blocks when one audit fails.
|
|
788
788
|
- Runs only when the intercepted command is a `git commit` or `git push`, including invocations that pass git-level options before the subcommand (`git -c user.name=x commit`, `git --no-pager commit`, `git -C dir push`, `git --git-dir=/x push`); anything else exits 0. Set `FALLOW_GATE_DEBUG=1` to log skipped commands to stderr.
|
|
789
789
|
- Resolves `fallow` from PATH first, then `npx --no-install fallow` as a fallback. Skips with a stderr notice if neither is available or if `jq` is missing.
|
|
790
790
|
- Enforces a version floor via `FALLOW_GATE_MIN_VERSION`. The installed gate script holds the default floor (currently `2.85.0`). Fallow maintainers raise that default by hand; `fallow hooks install` does not set it to the installed version. Binaries below the floor are blocked with an upgrade hint. Set the env var to the empty string to disable the check.
|
|
791
|
-
- Runs `fallow audit --format json --quiet --explain --gate-marker agent
|
|
791
|
+
- Runs `fallow audit --format json --quiet --explain --gate-marker agent`. On verdict=`fail`, it writes a short block message to stderr: the line `fallow-gate: blocked by fallow <version> at <binary>`, the verdict, one line for each finding that caused the block (`<kind> <path>:<line> <name>`, at most 50 lines), and the path of the full JSON report. Under the default `gate = "new-only"` the listed findings are the ones with `introduced: true`; under `gate = "all"` they are all findings that are not warnings. The full report goes to `.fallow/gate-report.json` in the audited tree, or to `FALLOW_GATE_REPORT` when set (a relative path resolves against the audited tree). Set `FALLOW_GATE_OUTPUT=json` to write the full JSON envelope to stderr instead, preceded by `fallow-gate: blocked by fallow <version> at <binary>` so the responsible binary is always identifiable. The gate marker lets local Impact record blocked-then-cleared agent gate events when Impact is enabled.
|
|
792
792
|
- On runtime error (`{"error": true, ...}`) or unexpected non-zero exit, fails open with a one-line stderr notice; warn verdicts pass through silently.
|
|
793
793
|
|
|
794
794
|
### `.codex/hooks.json`
|