Commit graph

25 commits

Author SHA1 Message Date
trvon
31607b5bcb
fix(queries): resolve a subdirectory path to its enclosing package
Some checks failed
Lint Commits / commitlint (pull_request) Successful in 16s
Tests / Static Analysis (pull_request) Has been cancelled
Tests / Tests (pull_request) Has been cancelled
Found by dogfooding on nuwayze: `pipe queries lib` from the repo root
failed with

  no resolved package graph at .dart_tool/package_config.json
    remedy: run `flutter pub get` or `dart pub get` in .../nuwayze-w3/lib

It failed even with the package resolved, because the path was taken
as the project root: pipe looked for lib/.dart_tool/package_config.json,
which never exists, and the remedy sent the user to run pub get in a
directory that has no pubspec.

enclosingPackage() (project.dart) splits a path into the package that
contains it (the path itself when it holds a pubspec.yaml, else the
nearest ancestor that does) and the directory it names under that
root. `pipe queries` loads the package root and scans only that
directory, so `pipe queries lib/services` is `--dir lib/services` on
the package; an explicit --dir with such a path is relative to it.
Without a resolved graph the error now names the package root.

loadProject's own remedy uses the same lookup, so `pipe check lib`
(and the other commands, which still take the path as the root) now
says the path is inside the package at <root> and suggests passing
that with `--dir lib`, instead of pub get in lib.

Tests: enclosingPackage on a root, a nested directory, a nested
package and a missing path; loadProject's remedy for a directory
inside a package; and `pipe queries` on the fixture's lib/data
(root reported as the package, one file discovered), with
`lib --dir data`, and on an unresolved package where the remedy names
the root, not lib.

On nuwayze-w3: `pipe queries lib` exits 0 with 313 queries over 847
files, identical to `pipe queries .`; `pipe queries lib/services` scans
161 files and every one of its 212 queries sits under lib/services.
233 tests pass.
2026-09-25 20:16:23 -06:00
trvon
fb055c9978
merge: bring main into the audit and state-map work
Some checks failed
Lint Commits / commitlint (pull_request) Successful in 19s
Tests / Static Analysis (pull_request) Has been cancelled
Tests / Tests (pull_request) Has been cancelled
main gained six PRs (#1-#6) while the audit fixes and the state-map seam
were in progress. Four of main's commits fixed the same bugs this branch
did, independently; where they overlap, the more complete version wins:

- Fingerprints: main's (a0844bb) - catchSite (`member#index` among all
  catches), union/owner as identity, a legacy fingerprint so version-1
  baselines still match, baseline "version": 2. This branch's `catch`
  ordinal is dropped; its fingerprint_test still guards the result.
- verify --rule: main's verifyAnalysis (cf9e0b7), which also reports
  findings in skipped files as NOT SCANNED.
- Unreadable SDK: both kept - this branch's up-front probe gives the
  early, specific error; main's catch around the analyzer (d6b6bcb) is
  the backstop.
- Riverpod: main's "a ref.listen is not a render" (f6411dc) and this
  branch's AsyncError patterns / stored-value tracking compose; the
  fixture keeps both sets of cases.

Ported onto the model-only rules (the seam), since main edited code this
branch had moved into front-end facts:
- excerpt.dart's literal-free, bounded quoting (a prompt-injection
  guard) now also covers front-end facts: SwallowedFailure.handler and
  NonSubscribingRead.excerpt, and the C++ front end elides string
  literals in handler evidence the same way.
- ScanCoverage: runChecks stays source-free; the model carries coverage
  (`scanCoverage` in the state JSON, written by both front ends), so
  `check --model` states coverage like a source check.
- main's softened wording (rendered-not-emitted "probably unreachable",
  the remove-policy dead-handler text, notifier-field-unread) and the
  backticked flag message.

Docs: main's README grew the Firestore query inventory and the output
contract; the README stays short, the inventory moves to
docs/queries.md, and the contract, exit codes, verify and baseline
details join docs/usage.md.

Checked against origin/main on resolved mirrors of the dogfood apps:
nuwayze (102), dex (0) and word_alive (0) identical; tick differs only
by the intended riverpod stored-value fix. On the fixture every
difference is one of this branch's fixes. 214 tests pass.
2026-09-24 07:33:53 -06:00
trvon
1f60197a15
feat(queries): inventory firestore query surfaces as typed records
Add `pipe queries`, which lists every Firestore query a project can issue:
scope, collection, path pattern, filters with operators and value types,
composites, orderBy, limit, cursors, realtime, aggregation, in-loop, plus a
confidence and the reasons it is not exact. Receivers are matched by static
type and cloud_firestore library URI, never by name.

Queries are followed through chains, local variables (flow-insensitive with
two cuts), fields, getters and one level of helper methods with arguments
bound. An --adapter-config (YAML or JSON) maps declarative query APIs such as
a server-side gateway onto the same schema.

Inventory only: no rules, findings or baseline changes. Every emitted name is
validated against a safe character set so no source text reaches the output.
2026-09-23 18:03:09 -06:00
trvon
0d891919e9
feat(check): run the rules on a state model written by any front end
Third step of the state-map seam, and the proof that it holds.

stateFlowFromJson rebuilds a StateFlow from its toJson form (computed
members - totals, coverage differences, Riverpod gaps - are recomputed),
and `pipe check --model <file>` runs every rule on such a file instead
of analysing Dart. A front end for another language now only has to
write that JSON to get every rule, --baseline ratcheting and --fail-on
gating. StateUnion.toJson now carries uiReadsBaseFields (only when
true), which the round trip showed it had been dropping.

Checked byte for byte, through a file on disk: `pipe state --format
json` then `pipe check --model` reports exactly what `pipe check` does
on the fixture and on resolved mirrors of nuwayze (102 findings), tick,
dex and word_alive. state_map_roundtrip_test.dart asserts the same in
process (model JSON is idempotent; findings identical; every rule fires,
so the comparison is not vacuous), and model_cli_test.dart covers the
CLI path.

--model refuses --write-baseline: a model does not record whether its
front end read every file, so the baseline could silently be partial -
the hazard the source path already refuses. A malformed model is a
PipeException, not a crash.

134 tests pass.
2026-09-22 14:31:03 -06:00
trvon
c3f1fb6319
refactor(rules): provider rules judge facts; runChecks takes only the model
Second step of the state-map seam. The five ChangeNotifier/provider rules
walked resolved units from runProviderRules. Their recognisers move to
the Dart front end (dart_provider_facts.dart) and emit a NotifierModel
of neutral facts (facts.dart):

- NotifierFact / NotifierMethod / FieldMutation: an observable object
  that must announce its own changes, its methods, what each calls,
  mutates, and whether it notifies directly. Deliberately general enough
  for a Qt QObject or a hand-rolled observer subject.
- consumer sites, provider declarations, member uses per file.
- NonSubscribingRead: every read made while building without
  subscribing; whether the type is observable is now the rule's filter,
  not the recogniser's.
- ProvidersCreatedInBuild: providers constructed in a build method.

The transitive notify closure, the observable/lifecycle/wrapper
judgements and all messages stay in provider_rules.dart, which no longer
imports the analyzer.

runChecks now takes only a StateFlow: no Project, no FileOutcome. The
scanned directories ride on the model (StateFlow.directories) for the
dead-handler scope claim. facts.dart is exported publicly.

No behaviour change, checked byte for byte: `check --format json` is
identical on the fixture and on resolved mirrors of nuwayze, tick, dex
and word_alive. 128 tests pass.
2026-09-22 14:23:39 -06:00
trvon
ea9d51078a
fix(guide,suggest): make verify hints commands that can be pasted as-is
Every brief entry and plan action ended with `verify: pipe check --rule X`.
Pasted, that checks the current directory, over lib/ only, against no
baseline - not the run that produced the finding. Run from anywhere but the
project root, or after a scan with --dir or --baseline, it answered a
different question.

The hint is now built by checkCommand(): `pipe check <root> --rule X`, plus
`--baseline <path>` and every `--dir` when the run used them, with paths
shell-quoted when needed.
2026-09-22 12:22:03 -06:00
trvon
34325c7e16
fix(guide): make the rule legend's fixes follow --policy
`ruleFix` was a constant map that ignored `--policy`. Under the default
wire-up policy the brief's legend still said "Remove the dead handler, or
dispatch the event" and "delete the variant and its emits" as equal choices,
directly above findings whose remediation said removal needs explicit
approval - the legend contradicted the policy the README describes.

ruleFixFor(policy) now returns the legend's fixes for a policy: under wire-up
the choice-bearing rules (emitted-not-rendered, rendered-not-emitted,
event-without-handler, dead-handler, notifier-field-unread) lead with making
the code reachable and say removal needs explicit approval; under remove they
keep offering both. `ruleFix` remains, as the default policy's map.
2026-09-22 12:22:03 -06:00
trvon
e2d84f5e75
fix(guide): persist the yams report with its caution and provenance
`guide --yams` persists the markdown report, not the brief, and the markdown
had neither the brief's caution line ("static reachability claims ... confirm
each against the cited file") nor the [severity/confidence] of each finding -
so a report read back from the corpus presented every finding as equally
certain, with no framing. Its metadata still carried a stale `owner=pi` and
nothing to date or reproduce it.

The markdown now opens with the caution line, headings read
`### N. [severity/confidence] rule — location`, and the metadata records
`generated=<ISO-8601 UTC>` and, when the project is a git checkout,
`commit=<HEAD>`. `owner=pi` is gone.
2026-09-22 12:05:12 -06:00
trvon
749aa53348
feat(report): state scan coverage in every check, guide and suggest output
A report listed findings without saying how much of the project they were
drawn from, so "0 findings" from 3 of 40 files looked like a clean project -
and an agent handed a brief had no way to know otherwise.

check, guide and suggest JSON now carry a `coverage` block
{discovered, selected, resolved, failed, skipped, complete}. The guide brief,
the markdown report, the suggest agent plan and both text outputs open with a
one-line coverage note, which says PARTIAL whenever a discovered file was not
analysed.

The note lives in the brief's header, which budget trimming never touches. The
"Coverage (what was actually examined)" caveats section was the first thing
dropped under a tight budget, silently; when it no longer fits, a one-line
pointer now says how many caveats were left out.
2026-09-22 12:05:11 -06:00
trvon
9eb3d98d4d
fix(verify): a finding in an excluded file is not scanned, not resolved
The analyzer skips a file that analysis_options `exclude:` names (contextFor
throws, which pipe records as `skipped`). The verify gate refused a scan only
when files FAILED, and partialScanWarning named only failures, so a skipped
file's accepted findings were simply absent from the fresh report - and verify
listed them as RESOLVED, "these fixes are confirmed", for code nobody read.

Excluded files are a normal part of a project, so they do not abort verify.
Instead each accepted finding whose file was not analysed - skipped, failed,
filtered out by --no-include-generated, or outside every scanned --dir - is
reported as NOT SCANNED: neither resolved nor still present. allResolved (and
so --expect-resolved) now requires that list to be empty too.

partialScanWarning counts skipped files, so a scan with excluded files is
announced as PARTIAL on stderr like one with failures.
2026-09-22 11:58:20 -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
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
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
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
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
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