Commit graph

77 commits

Author SHA1 Message Date
trvon
639c2d168e
fix(cli): reject malformed numeric options instead of silently defaulting
Some checks failed
Tests / Static Analysis (push) Has been cancelled
Tests / Tests (push) Has been cancelled
Eight sites read a numeric option as `int.tryParse(...) ?? <default>`, so a typo
was answered with a different number and a success exit:

  $ pipe check . --top 2o     # showed 25 rows, exit 0
  $ pipe guide . --budget abc # used 6000 chars, exit 0

Meanwhile --concurrency and --limit already rejected bad input with a clear
message, so the CLI disagreed with itself about whether a bad number is an error.
Every numeric option now goes through readPositiveInt(): --top, --budget,
--max-actions and --max-findings included. This is the same principle as the rest
of the tool - do not silently substitute a value the user did not ask for.

Second, related defect in `suggest`: each numeric option was read inside the one
output branch that uses it (--top in the text branch, --max-actions in the agent
branch, --budget in the agent branch), so a malformed value was ignored entirely
on the other paths - `suggest --top abc --format agent` exited 0. All three are
now read and validated before the format switch, so the value is rejected
regardless of which branch runs.

Found by executing every command with deliberately bad flag values, which is part
of the execution-first sweep that also turned up the broken `graph`. Two external
read-audits reported no defects in this area.

95 tests (was 91), dart analyze clean, determinism re-confirmed byte-identical
across -c 1/4/8 for check/state/graph/suggest.
2026-09-20 21:48:14 -06:00
trvon
03df9d01de
fix(cli): repair graph, which crashed on every invocation
Some checks failed
Tests / Static Analysis (push) Has been cancelled
Tests / Tests (push) Has been cancelled
`pipe graph` was completely non-functional. GraphCommand had no constructor, so
it never called _addCommonOptions, yet its body reads --dir, --sdk,
--concurrency, --include-generated, --limit and --format. It therefore registered
only --help:

  $ pipe graph lib
  Unhandled exception:
  Invalid argument(s): Could not find an option named "--dir".   (exit 255)
  $ pipe graph lib --format json
  Could not find an option named "--format".                     (exit 64)

So a documented command died on an unhandled exception instead of a clean CLI
error, and its JSON output was unreachable. Fixed by registering the common
options like every other project command.

Found by smoke-testing every command, which is what the two external audit
rounds never did: this round's auditor listed "run pipe graph and pipe check" as
a NEXT area to audit and never ran graph, and reported no confirmed defects.

Guard: test/cli_options_test.dart asserts every project command registers every
common option it reads (recall is exempted - it deliberately targets a corpus
root and registers its own equivalents). It fails without this fix (+0 -2).

Also, from the same round's unconfirmed suspicions, both of which reproduced:
- recall: an unrecognised `status` is now rejected instead of silently read as
  `open`. `status` decides whether a case is a bug pipe must find or a permanent
  regression guard, so a typo like "fix" defaulted to `open`, printed MISSED, and
  sent the reader hunting for a regression that was really a manifest typo.
- recall: MISSED and REGRESSED entries now print their `note`. The manifest
  already carried the explanation for the two nuwayze misses ("wired up on branch
  wire/nuwayze-* (unmerged)") but the report never showed it, so an expected miss
  looked exactly like a regression in pipe.

Recorded as NOT a defect: `pipe verify` exiting 0 while listing "new" findings.
`--expect-resolved` is documented - and implemented - as failing only on
baselined findings that are still reported; `verify` fails correctly in that case
(exit 1). It has no fail-on-new flag, which is a gap for CI use rather than a
contract violation, so the documented behaviour was left alone.

90 tests pass (was 84), dart analyze clean.
2026-09-20 21:43:36 -06:00
trvon
1e7d7b72d5
fix(providers): drop the dangling "by type" clause when there are no declarations
Some checks failed
Tests / Static Analysis (push) Has been cancelled
Tests / Tests (push) Has been cancelled
A project with ChangeNotifier classes but no provider declarations printed:

  provider rules examined 10 ChangeNotifier class(es) (0 consumed by a
  Consumer/Selector), and 36 observable mutator(s) were checked for a notify
  path. Provider declarations by type: .

That trailing "Provider declarations by type: ." is a dangling colon and full
stop. It reads as a truncated sentence rather than as "this app declares none",
which is how it surfaced on word_alive. The clause is now omitted when the list
is empty, and still lists kinds most-common-first when it is not.

Found by reading an external auditor's raw tool output after its run died on an
API rate limit before it could write a report - its observations were still
worth salvaging even though it never reached a conclusion.

The note is extracted into providerRuleNote() so both branches are directly
testable; before this the empty case was only reachable through a real project.
2026-09-20 21:34:42 -06:00
trvon
3c91db2946
fix: act on the external audit - 5 defects, 5 vacuous tests, a real fixture exclusion bug
Some checks failed
Tests / Static Analysis (push) Has been cancelled
Tests / Tests (push) Has been cancelled
An independent Gemini 3.8 Flash audit of this repo found real problems. Each was
reproduced before being fixed.

Confirmed defects:
- tool/adjudicate.py crashed with KeyError once R001 started grouping variants
  under 'variants' instead of 'variant'. Fixing that exposed a second bug of my
  own: it then divided verified VARIANTS by FINDINGS and reported 216% precision.
  Counts are now coherent (findings vs variant-level checks) and a clean repo
  reports 0/0 rather than a fake 100%.
- GuideCommand called _readPolicy but never registered --policy, so
   exited 64 despite the README documenting it.
- loading-flag-not-reset treated a constructor-invoked method as inheriting the
  loading flag without checking flag.defaultsTrue, so a default-FALSE flag was
  reported as in flight.
- Finding.fingerprint embedded the line number for rules whose only identity was
  , so moving code broke baseline entries.  is now a
  discriminating key; only loading-not-reset, which can emit two findings from one
  method, carries an explicit catchLine.
- suggest grouped unrelated provider-read-in-build findings by file; the group key,
  label and target list now use valueType/call.

Recall defect found while checking the auditor's own claim: UI variant mentions
were collected project-wide by unqualified name, so ExportSuccess - which appears
in FOUR unions on nuwayze - was cross-credited. Mentions are now scoped per union
to files that also name the state type or its owner, which surfaced
ArtistStoreManagementState[ExportSuccess] as a real finding.

Five negative controls could not fail (an event that is never handled can never be
reported as unhandled; a union with no render sites can never be reported as
rendered-not-emitted). Added fixture cases that make each one bite: HandledTap
(handled AND dispatched), PatternState render sites, GuardedCubit (guarded loading
path), QuietCubit._warmUp (constructor-invoked await on a default-false flag).

Root cause of a recurring mystery: this repo EXCLUDED test/fixtures/** from
analysis, which made any analyzer running in the repo context parse the fixture
without resolving its imports and report phantom undefined-class errors. I had
dismissed these as stale cache several times; they were my own exclusion. The
exclude is gone and the fixture governs itself. A new test asserts the fixture has
zero analyzer errors, not merely that it resolves, so IR distortion cannot hide.

84 tests (was 81).
2026-09-20 20:45:58 -06:00
trvon
883c661d7b
feat: default remediation policy is to WIRE IT UP, not to delete
Some checks failed
Tests / Static Analysis (push) Has been cancelled
Tests / Tests (push) Has been cancelled
An unreachable state, handler or control is usually an unfinished feature rather
than dead weight, so pipe now leads with making it reachable: render the missing
states, add the missing branch, dispatch the event from a control. Removal is the
exception and is worded as needing explicit approval instead of being offered as
an equal alternative.

New FixPolicy (wire-up default | remove), threaded through runChecks,
runProviderRules, analyzeProject and buildGuidance, with --policy on check, guide
and suggest. It changes every remediation that presents a choice, the brief's
decision line, and the dead-handler section heading ('Unreachable handlers - wire
up the control' vs 'Cleanly deletable').

It is a policy, not a finding: the same facts are reported either way.
81 tests, including 6 new ones asserting the default and that remove flips it.
2026-09-20 18:41:07 -06:00
trvon
c391171e6a
fix: attribute typed variable patterns, and stop treating base-field reads as unrendered
Some checks failed
Tests / Static Analysis (push) Has been cancelled
Tests / Tests (push) Has been cancelled
Two real defects found by an audit of nuwayze, both confirmed against the code.

1. Recall: the switch 'case ChatListLoaded value => value' binds the type on a
   DeclaredVariablePattern, not the object pattern ChatListLoaded() this matcher
   looked for. An exhaustive switch was therefore invisible and two variants were
   wrongly reported as never rendered. Both the mention collector and the branch
   collector now handle declared variable patterns.

2. Precision: the UI often reads a union's payload off the BASE class
   (state.currentBooking, state.isRefreshing), so the data reaches the user even
   though the variant is never matched. StateUnion now records uiReadsBaseFields
   and those findings drop to info with an explanation instead of warning. That
   removed 5 false-positive warnings on nuwayze.

3. recall/manifest.json gains status=open|fixed. A fixed case that is reported
   again is a REGRESSION, so repaired bugs become permanent guards against the
   real code instead of deleted expectations. The three bugs fixed earlier today
   (dex x2, word_alive) are now fixed-status guards:
   recall --strict -> 5/5 accounted for, 0 regressions.

nuwayze: 111 findings, 11 warnings (was 16). 75 tests, fixture regression guard
patternView covers the typed-pattern case.
2026-09-20 18:10:56 -06:00
trvon
88ed85b43a
feat: add notifier-field-unread and provider-created-in-build, bound the brief header
Some checks failed
Tests / Static Analysis (push) Has been cancelled
Tests / Tests (push) Has been cancelled
Completes the provider/ChangeNotifier rule set:

- notifier-field-unread: a public getter on a notifier the UI consumes that
  nothing reads, so the state it exposes cannot reach the user. Once per
  notifier. nuwayze: 3 (UserProvider.isInitializing,
  AppLocaleProvider.preferredLocale, and _AgentHubController with 8 members).

- provider-created-in-build: a provider constructed in build without .value, so
  create reruns on every rebuild. Reported once per build method, not once per
  provider: nuwayze's AppProviders.build constructs 45 of them and that is one
  decision, not 45 bugs. Deliberately info, not warning, because a root
  StatelessWidget rarely rebuilds and static analysis cannot tell rebuild
  frequency apart.

Also fixes a real defect the budget test caught: the brief's 'by rule:' line
grew with the rule count and crowded out the findings it summarises. It is now
capped at the top 6 with a '+N more' suffix, the omission note is reserved for
up front so the artifact stays inside its budget, and the overflow message is
shorter.

75 tests. Verified the fixture with pipe's own resolver (0 errors) and validated
that check by injecting a type error into a copy (errors=1).
2026-09-20 17:59:54 -06:00
trvon
2c7bea9ef0
feat: provider package support - model all provider declaration types, add provider-read-in-build
Some checks failed
Tests / Static Analysis (push) Has been cancelled
Tests / Tests (push) Has been cancelled
Adds the provider half of the state-management surface:
- collects every provider-package declaration type (Provider,
  ChangeNotifierProvider, ListenableProvider, ValueListenableProvider,
  FutureProvider, StreamProvider, StateProvider, ProxyProvider family) and
  reports the counts by type in the coverage note
- new rule provider-read-in-build: a Listenable read with context.read or
  Provider.of(listen: false) directly in a build body, so the widget never
  subscribes and can render stale state

447 raw context.read calls in nuwayze reduce to 1 finding. The first four were
hand-checked and every one was a read inside a closure (a BlobListener-style
listener callback or a Selector builder), so the collector now refuses to
descend into nested functions; that costs recall on reads inside Builder and is
recorded in the README rather than hidden.

Also fixes a third fingerprint collision: valueType/call were missing from the
finding identity, so two provider-read-in-build findings in one file shared a
baseline fingerprint. Identity now requires a discriminating key and falls back
to the line only when there is none.
2026-09-20 17:46:30 -06:00
trvon
d87c0bf8e0
perf: group never-matched state markers by marker class instead of one finding per variant
Some checks failed
Tests / Static Analysis (push) Has been cancelled
Tests / Tests (push) Has been cancelled
The union-observed-but-variant-never-matched tier was one finding per variant,
so 67 findings described the same fix across 21 unions. Group by marker class
instead: failures and loading markers stay warnings because an unrendered
failure means the user gets no feedback, other markers drop to info.

nuwayze: 146 findings -> 108, and the actionable core is now visible: 14
warnings (2 dormant decisions + 12 missing-feedback groups), down from 49.

Also records a negative result in the README: tiering these by 'variant declares
no fields beyond its base' was tried and rejected, because 67/67 qualified and a
signal true of the whole population cannot discriminate.
2026-09-20 17:37:06 -06:00
trvon
5d0a704bc2
perf: collapse a fully unobserved state union into one decision-level finding
Some checks failed
Tests / Static Analysis (push) Has been cancelled
Tests / Tests (push) Has been cancelled
Dogfooding after fixing the three reported bugs showed that high-confidence
emitted-not-rendered findings can be deliberately dormant scaffolding rather
than bugs: on nuwayze both such blocs were already documented as unreachable
in the repository's own action contract, and the CommentsBloc consumer had
been deleted on purpose when a StreamBuilder UI replaced it.

Reporting nine near-identical findings for one product decision buries the
findings that are real regressions, so an unobserved union now yields a single
finding listing its variants, and the remediation says to check the
repository's docs and action contracts before deleting anything.

nuwayze: 159 findings -> 146 (15 variant findings collapse to 2 decisions).
2026-09-20 17:18:51 -06:00
trvon
567a0b8a45 chore: adopt the yams repo layout
Some checks failed
Tests / Static Analysis (push) Has been cancelled
Tests / Tests (push) Has been cancelled
Mirrors the conventions used in ../yams: a sectioned .gitignore, thin
.githooks dispatchers over runnable scripts/, release-please config and
manifest, conventional-commit linting, CODEOWNERS, a PR template with a
rule-precision checkbox, and tests + commitlint workflows. CI calls
scripts/lint.sh so local hooks and CI cannot drift.
2026-09-20 17:03:37 -06:00
pipe-loop
b83fca1e30 feat: suggest command - ranked fix order per file with transparent scoring 2026-09-20 16:22:29 -06:00
pipe-loop
3cdb32f89d feat: provider rules (notifier-never-notifies, notifier-inert-consumer) with observable-field gate and coverage notes 2026-09-20 16:19:02 -06:00
pipe-loop
3ebc78d1a7 feat: add verify command - prove fixes, not just find problems 2026-09-20 16:07:09 -06:00
pipe-loop
f2949dfbfd feat: rule contracts + recall measurement (5/5 real bugs) + real --fail-on gate 2026-09-20 16:00:10 -06:00
pipe-loop
a773407684 fix: do not claim space pressure when there are simply no rules 2026-09-20 14:42:19 -06:00
pipe-loop
9b4707b5c3 fix: accurate state-owner summary, no-findings brief, skip empty legend 2026-09-20 14:41:42 -06:00
pipe-loop
513ce76150 feat: baseline/ratchet support, CI gate over new findings, README with precision table 2026-09-20 14:40:17 -06:00
pipe-loop
73fdcfeb95 feat: Riverpod support - provider model, riverpod-async-gap rule, tiered by value type 2026-09-20 14:35:40 -06:00
pipe-loop
6859d7e78c feat: phase 5 dogfood - model flag-based state, add loading-flag-not-reset, fixture tests 2026-09-20 14:29:21 -06:00
pipe-loop
bd667093ee feat: name first consumer per finding, add cleanly-deletable dead-handler section 2026-09-20 14:15:17 -06:00
pipe-loop
34a7921b66 feat: dense edit anchors, reserve legend space, derive legend from shown groups 2026-09-20 14:13:56 -06:00
pipe-loop
97f99d88a1 feat: decide wire-vs-delete from owner references, fix legend set, add by-rule aggregate 2026-09-20 14:05:53 -06:00
pipe-loop
f4748f9b1c feat: group guide findings by root cause, add decision + verify lines 2026-09-20 14:04:16 -06:00
pipe-loop
5d7af95c6f feat: phase 4 agent-facing guide output + yams emission, 15 tests 2026-09-20 14:02:43 -06:00
pipe-loop
80daa12ff8 feat: phase 3 ranked breakage detectors, rules engine, fixture tests 2026-09-20 13:56:52 -06:00
pipe-loop
8fcc0cde42 feat: phase 1 ingestion harness + Phase 2 state-flow IR 2026-09-20 13:46:54 -06:00