`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.
9.2 KiB
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 jsonalways parses. - Every report states its coverage.
check,guideandsuggestJSON carrycoverage: {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.skippedcounts files the analyzer does not cover (excluded byanalysis_options.yaml, or outside any analysis context). A state model carries the same block asscanCoverage, socheck --modelreports it too. - Exit codes: 0 success; 1 a gate failed (
check --fail-on,verify --expect-resolved,recall --strict,index --fail-on-error, orguide --yamscould 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 withPIPE_DEBUG=1for 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 getnot run) is an error, not a scan in which everypackage:import isdynamic. - An SDK that exists but cannot be read (for example under a privacy-protected
macOS folder) is refused with a
--sdkremedy. - 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 unresolvedFoo()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.