Skip to content
Fallow home
All docs pages

fallow decision-surface

Get the few structural decisions in a change that need a human judgment call, each as a question with the expert to ask. CLI reference for fallow decision-surface, the top layer of the review brief printed alone.

A large diff often contains only one or two choices that are expensive to undo. fallow decision-surface finds those structural decisions and gives you each one as a question for the right reviewer.

Each decision has:

  • a judgment question
  • a signal_id, a deterministic anchor from the module graph
  • the routed expert to ask
  • the named trade-off

decision-surface prints the top layer of the review brief without the rest of the brief.

fallow decision-surface --base main
fallow decision-surface --base main --format json --quiet
fallow decision-surface --base main --max-decisions 5

fallow decision-surface always exits 0 when the analysis runs. It never blocks CI, and its output has no verdict. A runtime error, such as a base ref that does not exist, exits 2. fallow audit is the command that exits non-zero on a fail verdict. Use decision-surface to find the one or two things that need a human judgment call. Use audit to find out if CI blocks the change.

When to reach for it

Three commands inspect changed code. Each one answers a different question.

You want to knowCommandGates?
Will CI block this PR?fallow auditYes (exits non-zero on a fail verdict)
Where do I look, across the whole change?fallow review (the full brief: subtract, focus, structure, direct)No (always exits 0)
Which few decisions deserve a human judgment call?fallow decision-surface (only the structure job)No (always exits 0)

fallow decision-surface runs the same changed-code analysis as fallow review, but prints only the decisions. It drops the rest of the brief: the subtract list, the weighted focus map, and the walkthrough contract. Use it when you want only the few structural choices in the diff.

What a decision is

A change can touch hundreds of lines and still contain only one or two choices that are expensive to undo. The decision surface isolates those choices. Each entry is a framed judgment question with the context that a reviewer or coding agent needs to answer it. It is not a recommendation. Fallow never tells you which option to choose.

The decision surface has exactly three categories:

CategoryMeaning
coupling-boundaryA new dependency edge that crosses zones.
public-api-contractA new export in the public API, or a changed contract that modules outside this diff use.
dependencyA new third-party dependency, or a declared dependency that moves across a major version (or a 0.x minor). Fallow makes one batched decision per manifest per kind, weighted by the number of in-repo modules that import the affected packages.

Fallow never reports these changes as dependency decisions:

  • minor and patch bumps
  • non-numeric ranges (workspace, file, git, tags)

The importer count is a union over the batched packages, so a module that imports two bumped packages counts once. For entries outside dependencies, the question names the section (dev, optional, peer). When a package is in several sections, the decision uses its dependencies range and anchor line.

Fallow ranks the decisions by consequence (blast radius x reversibility) and caps the list at 3 to 5 decisions. The default is 4. Set the cap with --max-decisions. There is no pagination. The cap is deliberate: a reviewer can keep a few structural questions in mind, but not a long list of findings.

What each decision carries

FieldTypeWhat it is
questionstringThe judgment, as a question for a human.
category"coupling-boundary" | "public-api-contract" | "dependency"The kind of structural choice.
expertstring[]Who to ask, from ownership routing. Empty when no ownership signal is available for the anchor file.
bus_factor_onebooleanPresent and true when only one person qualifies as owner of the anchor file.
signal_idstringThe deterministic anchor for this decision, taken from the module graph (sig: and a hash). It is stable across runs on the same tree. Use it as the key to re-attach review comments.
signal_keystringThe raw graph key that signal_id hashes, for example package.json::left-pad.
anchor_file, anchor_linestring, integerThe project-relative file and the 1-based line of the anchor. 0 means the head of the file.
tradeoffstringThe structural cost, stated as a fact and never as a recommendation. Example: "Couples app to infra; 4 in-repo modules already depend on this anchor".
internal_consumer_countintegerThe number of in-repo modules outside the diff that already depend on the anchor. This is your reversibility signal: a higher count means the choice is more expensive to undo. Fallow displays this number. The ranking uses blast and consequence.
blast, consequenceintegerThe ranking inputs: the number of modules beyond the diff that the decision affects, and that number times a weight per category. Fallow sorts on consequence, highest first.
previous_signal_idstringPresent only when the anchor file was renamed. It holds the signal_id from before the rename, so a review tool can re-attach an earlier comment across a git mv.
actionsarrayask-expert when expert is not empty, and suppress with the comment to paste, except on dependency decisions.

The human output shows the question and the trade-off. Read reversibility from the consumer count. Fallow never labels a decision as a one-way or two-way door.

To suppress a coupling-boundary or public-api-contract decision, add // fallow-ignore-next-line decision-surface <category> above the decision's anchor. The suppress action holds the exact comment. A dependency decision anchors on package.json, which cannot hold a comment. It has no suppress action, but ask-expert still applies. Suppression removes a decision from the output. It does not change the verdict.

How an agent consumes it

Fallow builds the decision surface for coding agents. Request JSON output:

fallow decision-surface --base main --format json --quiet

A typical agent flow:

  1. Read decisions[] in order.
  2. For each decision, show the question and the expert list to the human, not the raw diff.
  3. Use internal_consumer_count to choose how strongly to flag the decision. 0 means a cheap choice that is easy to undo. A high count means many modules depend on the anchor, so the choice is expensive to undo.
  4. Quote the tradeoff text word for word. It is a fact, so the agent can pass it on without comment.
  5. Attach each comment to signal_id. If the run reports a previous_signal_id, move the earlier comment across the rename. Do not post a duplicate.

fallow builds the decisions from the module graph only. It never adds PR prose to them. The decisions show the actual structural change, not a description of it.

Picking the comparison point

decision-surface scopes to changed code in the same way as fallow audit. Use --base (or its alias --changed-since) to set the ref that "changed" compares against.

# Against the merge-base with the default branch (auto-detected)
fallow decision-surface

# Explicit base ref
fallow decision-surface --base main

# Decisions embedded in the last 5 commits
fallow decision-surface --base HEAD~5

Options

FlagDescription
--base <REF>Git ref to compare against (for example main, HEAD~5, or a commit SHA). Alias for --changed-since. Default: the git merge-base with the branch's upstream or the remote default (origin/main). To pin the base without a flag, set FALLOW_AUDIT_BASE.
--changed-since <REF>Alias for --base. Compares against changes since this git ref.
--max-decisions <N>Maximum number of decisions in the output. Default 4. Fallow clamps values to the 3 to 5 band and does not reject values outside it.
-f, --format <FORMAT>Output format: human (default) or json. Other values print the human output. Fallow writes the human output to stderr.
-r, --root <PATH>Project root directory.
-c, --config <PATH>Path to a fallow config file.
-w, --workspace <NAME>Scope output to selected workspace packages. Accepts exact names, globs, and !-prefixed negations.
-q, --quietSuppress progress and status output on stderr. In human format, this also hides the decisions, so use it with --format json.

See global flags for the full list.

Examples

# Auto-detect base; render the decisions for a human
fallow decision-surface

# Explicit base ref
fallow decision-surface --base main

# Raise the cap
fallow decision-surface --base main --max-decisions 5

Example output

Decisions to make (2):
  1. [dependency] `package.json` adds 1 third-party dependency (`left-pad`),
       imported by 1 in-repo module. What does each replace, and who owns the
       new surface?
     trade-off: Takes on 1 new maintenance and supply-chain surface; 0 in-repo
       modules outside this diff already import the added packages.
     ask: alice (bus-factor 1)
  2. [public-api-contract] This change adds 2 exports to the public API surface.
       Intended as maintained contracts, or should they stay internal?
     trade-off: Adds 2 maintained contracts; 0 in-repo modules already consume
       this surface, and any external consumers become a contract you cannot
       remove without a breaking change.
     ask: alice (bus-factor 1)

When the change has no structural decision, the output is Decisions: none (no consequential structural decision in this change).

JSON output

fallow decision-surface --format json returns the decisions[] array. Each entry has the fields in What each decision carries. signal_count is the number of signal IDs that fallow emitted, including decisions below the cap. truncated is present when fallow collapsed decisions below the cap. fallow review --format json returns the same decisions in decisions.decisions[], without actions.

{
  "kind": "decision-surface",
  "schema_version": 1,
  "version": "3.31.0",
  "command": "decision-surface",
  "decisions": [
    {
      "signal_id": "sig:efab2fe6cef7b663",
      "category": "public-api-contract",
      "question": "This change adds 2 exports to the public API surface. Intended as maintained contracts, or should they stay internal?",
      "anchor_file": "src/index.ts",
      "anchor_line": 1,
      "signal_key": "src/index.ts::y|src/index.ts::z",
      "blast": 0,
      "consequence": 0,
      "expert": ["alice"],
      "bus_factor_one": true,
      "internal_consumer_count": 0,
      "tradeoff": "Adds 2 maintained contracts; 0 in-repo modules already consume this surface, and any external consumers become a contract you cannot remove without a breaking change.",
      "actions": [
        { "type": "ask-expert", "description": "Ask alice to make this call", "auto_fixable": false },
        {
          "type": "suppress",
          "description": "Suppress this decision if it is settled",
          "command": "// fallow-ignore-next-line decision-surface public-api-contract",
          "auto_fixable": false
        }
      ]
    }
  ],
  "signal_count": 1
}

MCP tool

The decision_surface MCP tool gives an agent the same decisions through a structured tool call:

{
  "tool": "decision_surface",
  "arguments": {
    "base": "main",
    "max_decisions": 4
  }
}

base and max_decisions map to the CLI flags with the same names. The MCP tool also returns dependency decisions, because both routes use the same manifest diff. See MCP integration for the full tool list and setup.

See also