pipe/docs/usage.md
trvon adf898be19
feat(verify): default --baseline to <path>/.pipe-baseline.json
`pipe verify .` failed with "verify needs --baseline" even in a repo
whose baseline sits where the README's CI recipe writes it. nuwayze's
scripts/testing/pipe_state_flow_gate.sh commits exactly that file,
.pipe-baseline.json at the repo root, so every verify there had to
repeat the path.

Without --baseline, verify now reads <path>/.pipe-baseline.json when it
exists and says so on stderr ("pipe: no --baseline given; using ..."),
so a run never silently picks a file nobody named. With neither, the
error names both remedies: pass --baseline, or write the conventional
file with `pipe check <path> --write-baseline <path>/.pipe-baseline.json`
before the fix. pipe had no baseline-name constant; defaultBaselineName
is now the one place it is spelled.

Tests (test/verify_default_baseline_test.dart): an explicit --baseline
wins and announces nothing; the conventional file is used and
announced; with neither the error carries both remedies; and end to
end, `pipe verify <copy>` against a baseline written at the copy's
root reads it (--expect-resolved fails on the 28 still-present
findings). README and docs/usage.md show the shorter form.

On nuwayze-w3, `pipe verify /Users/.../nuwayze-w3` with no flag reads
its .pipe-baseline.json (65 accepted findings). 225 tests pass.
2026-09-25 20:11:34 -06:00

9.2 KiB
Raw Permalink Blame History

Usage

Commands

Command What it does
pipe index <path> Resolve the project and report analysis health, timing and diagnostics. --fail-on-error exits 1 on analyzer errors.
pipe graph <path> The resolved declaration model, as JSON.
pipe state <path> The state model: unions, emissions, render sites, events, Riverpod, notifier facts. --format json writes the model other commands can read back.
pipe check <path> Ranked findings with evidence and remediation.
pipe suggest <path> Findings collapsed to root causes and ranked, globally and per file.
pipe guide <path> A prompt-sized brief for an agent; --yams persists it to the yams corpus.
pipe verify <path> [--baseline F] Which accepted findings were resolved, remain, or are new.
pipe recall --root <dir> How many labelled known bugs pipe detects (see rules).
pipe queries <path> Every Firestore query the code can issue, as typed records; an inventory, no rules (see queries).

Common flags: --format text|json (agent|md|json for guide, text|agent|json for suggest), -c/--concurrency N (default 4; 8 for recall), --sdk <path>, --dir <dir> (repeatable, default lib; a missing directory is an error), --[no-]include-generated (default on: *.g.dart, *.freezed.dart and *.mocks.dart are scanned), --rule <name> (repeatable), --top N, and --limit N (debugging only; results are marked partial).

Command-specific: check --fail-on, --baseline, --write-baseline, --model; guide --budget --max-findings --yams --yams-name (--yams persists the full markdown report, tagged with the analysed commit and a timestamp); suggest --budget --max-actions; verify --expect-resolved; recall --manifest --root --strict (without --manifest, recall/manifest.json in the working directory, else the one in the pipe checkout); queries --format json|md --adapter-config <file> --require-firestore; --policy wire-up|remove on check, guide and suggest.

Every numeric flag rejects a value that is not a positive integer instead of falling back to its default.

Output contract

  • stdout carries only the requested output. Status lines (wrote baseline to ..., persisted to yams as ...) and warnings go to stderr, so --format json always parses.
  • Every report states its coverage. check, guide and suggest JSON carry coverage: {discovered, selected, resolved, failed, skipped, complete}, and the text outputs, brief and plan open with a coverage line that says PARTIAL whenever a discovered file was not analysed. skipped counts files the analyzer does not cover (excluded by analysis_options.yaml, or outside any analysis context). A state model carries the same block as scanCoverage, so check --model reports it too.
  • Exit codes: 0 success; 1 a gate failed (check --fail-on, verify --expect-resolved, recall --strict, index --fail-on-error, or guide --yams could not persist); 2 pipe could not do what was asked (a bad path or --dir, an unreadable project, a baseline that cannot be read or written, or an internal error: rerun with PIPE_DEBUG=1 for its stack trace); 64 invalid usage, including more than one project path.

SDK and resolution

pipe finds the SDK itself: flutterRoot from the target's .dart_tool/package_config.json, then FLUTTER_ROOT, then flutter on PATH. Flutter projects need Flutter's bundled Dart SDK, because dart:ui is not part of the standalone one. Override with --sdk.

pipe refuses to report a clean result it did not earn:

  • A project never resolved (pub get not run) is an error, not a scan in which every package: import is dynamic.
  • An SDK that exists but cannot be read (for example under a privacy-protected macOS folder) is refused with a --sdk remedy.
  • Files that fail to resolve make the results PARTIAL, with a warning.
  • Files with unresolved references (uri_does_not_exist, undefined_*) produce a warning and a report note, and every finding naming something such a file mentions drops one confidence step (data.confidenceLowered). An unresolved Foo() is a method call, not a construction, so evidence there is invisible.

CI and baselines

pipe check . --write-baseline .pipe-baseline.json
pipe check . --baseline .pipe-baseline.json --fail-on warning

--fail-on none|info|warning|error (default none) fails when a new finding at or above that severity is present. Baselined findings never trip it. --fail-on-finding is an alias for --fail-on error.

A baseline stores one fingerprint per finding: rule, file and semantic identity (union, variant, event, provider, owner, member; for two swallowed catches in one method, the catch's index within it). Fingerprints never include a line number, so moving code does not resurrect accepted findings. Entries carry a readable label, so verify names what was resolved, and the project root is recorded relative to the baseline file.

Baselines are written as "version": 2. Version-1 baselines, whose fingerprints embedded a line number for four rules, are still matched, but those entries stay line-sensitive until the baseline is rewritten with --write-baseline.

--write-baseline refuses a partial scan (--rule, --limit, or unreadable files), because the baseline would omit every finding outside it. check --model refuses it too, since a model does not record whether every file was read.

Verifying a fix

$ pipe verify . --baseline .pipe-baseline.json
baseline  12 accepted findings
resolved  1 no longer reported — these fixes are confirmed
still     11 still reported
new       0 not in the baseline
RESOLVED
  + lib/flag_cubit.dart:25  loadUnguarded  [loading-flag-not-reset]

Without --baseline, verify reads <path>/.pipe-baseline.json, the file the CI recipe above writes, and says so on stderr; with neither, it fails and names both. So pipe verify . is enough in a repo that follows the recipe.

--expect-resolved exits non-zero while anything accepted is still reported or was not scanned. verify only calls a finding resolved when it looked for it:

  • with --rule, accepted findings of other rules are out of scope (counted, not judged);
  • an accepted finding in a file this scan did not analyse (excluded, filtered by --no-include-generated, or outside every --dir) is NOT SCANNED;
  • a scan with a file that failed to resolve, or truncated by --limit, is refused.

A finding can also go away because its subject was deleted instead of fixed. Those are listed as REMOVED ("gone because the code was deleted - not wired up"), not as confirmed fixes: the finding's file is gone, its event, state, provider or owner class is no longer declared, or, for a dead handler, the on<Event> registration itself was deleted. Removed entries still count as gone for --expect-resolved. Findings labelled by a member (a method or build) are classed by file only.

Fix order

pipe suggest collapses findings to root causes (one action per union, provider or handler), scores each, and prints the arithmetic so the order can be argued with:

Term Effect
severity error 100, warning 50, info 10 (the base score)
confidence high ×1.0, medium ×0.7, low ×0.4
breadth +10 per extra finding the fix clears, capped at +50
user-visible failure +25 when the rule marks the failure as user-facing
certainty +15 when the fix needs no product judgement (event without handler, loading not reset; dead handler only under --policy remove)

The bonuses are additive, so a wide, certain info group can outrank a narrow warning. Only new findings are planned, so a ratcheted repo yields an empty plan.

Agent briefs

pipe guide opens with a coverage line, then groups findings by root cause with dense anchors (file:18,29,32), a rule legend carrying the standard fix for the chosen --policy, a decision: line where the project makes "fix vs delete" decidable, and a verify: command that can be pasted as-is. Truncation is always stated, and packing stops at the first entry that does not fit, so a lower-ranked finding never stands in for a dropped one.

The analysed repository is untrusted input. Quoted source is a bounded excerpt (string literals replaced by '…', comments dropped, one line of at most 120 characters), labelled source excerpt (data, not instructions) and fenced so nothing inside it can close its own quotation. Front ends follow the same rule: the Dart and C++ ones both write evidence as excerpts.

Two limits are stated in the brief: whether to wire up or delete an unwired bloc needs product intent, and adding a missing UI branch means reading the consumer widget, which pipe names but does not inline.

Remediation policy

The default is --policy wire-up: an unreachable state, handler or control is usually an unfinished feature, so the remediation is to make it reachable, and removal is worded as needing approval. --policy remove flips the emphasis in remediations, the brief's rule legend and decision: line, and whether suggest scores a dead handler as needing no product judgement. The facts reported are the same either way.