DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path inspection-briefs/loomworks-b48-quality-tooling-step-0-findings-v0_1.md

Loomworks — B-48 quality tooling Step 0 inspection findings — v0.1

Version. 0.1 Date. 2026-08-06 Author. Claude Code (inspection session). Operator: Marvin Percival. Target. /Users/dunin7/loomworks-engine, branch main, HEAD 8992179a946a052c65ec2a58b1ca92301f7787cc, clean tree throughout. Charter. standing-notes/dunin7-standing-authorization-charter-v0_1. R-5 inspection run. Status. Read-only. No fix, no change request, no design decision. This document reports what each checker currently finds and where; it does not say what a gate should check.


0. Scope note on this inspection

Two commands were actually run against the repo (ruff check . and mypy, both read-only static analysis — no database, no server, no write to any tracked file). Everything below is measured from those two runs plus git blame, not estimated or carried from the build list.


1. The two checkers — name and exact invocation

Both are configured in pyproject.toml and shipped in the dev optional-dependency group; neither is invoked anywhere in .github/workflows/tests.yml, which runs pytest -q only. The workflow file's own header comment confirms this explicitly: "It does NOT run ruff or mypy (B-48's decision, not yet gated)."

Ruff (linter)

``` [tool.ruff] line-length = 100 target-version = "py312"

[tool.ruff.lint] select = ["E", "F", "W", "I", "N", "UP", "B", "SIM"] ``` Eight rule families selected: pycodestyle errors/warnings (E/W), Pyflakes (F), isort (I), pep8-naming (N), pyupgrade (UP), flake8-bugbear (B), flake8-simplify (SIM). No per-rule ignores, no per-path excludes, no extend-immutable-calls (relevant below, §5).

Mypy (type checker)

``` [tool.mypy] python_version = "3.12" strict = true ``` strict = true turns on the full strict flag set (disallow-untyped-defs, no-implicit-optional, warn-return-any, etc.). No files, packages, or exclude key — the config does not scope what gets checked, only how strictly.


2. Current full run — totals

Ruff: 3,618 findings, 36 distinct rule codes, across 780 of 829 tracked .py files (94%).

2,277 auto-fixable outright (ruff check . --fix), a further 67 reachable only with --unsafe-fixes2,344 of 3,618 (64.8%) mechanically fixable without a human judgment call on each one.

Full category breakdown (rule code, count, [*] = safely auto-fixable, [ ] = manual, [-] = fix exists but disabled by default):

| Count | Code | Rule | Fixable | |---:|---|---|---| | 825 | UP017 | datetime-timezone-utc | ✓ | | 800 | B008 | function-call-in-default-argument | — | | 672 | I001 | unsorted-imports | ✓ | | 303 | F401 | unused-import | ✓ | | 261 | UP045 | non-pep604-annotation-optional | ✓ | | 227 | E501 | line-too-long | — | | 131 | UP037 | quoted-annotation | ✓ | | 92 | B904 | raise-without-from-inside-except | — | | 38 | F841 | unused-variable | disabled | | 36 | N818 | error-suffix-on-exception-name | — | | 32 | E402 | module-import-not-at-top-of-file | — | | 25 | UP035 | deprecated-import | ✓ | | 24 | F821 | undefined-name | — | | 24 | SIM117 | multiple-with-statements | disabled | | 17 | SIM105 | suppressible-exception | — | | 17 | UP041 | timeout-error-alias | ✓ | | 15 | N802 | invalid-function-name | — | | 11 | B017 | assert-raises-exception | — | | 9 | F811 | redefined-while-unused | disabled | | ≤7 each | N806, N815, SIM102, SIM300, B905, N814, E702, F541, SIM103, SIM108, N812, UP012, UP047, B007, N801, N811, SIM110, SIM222, UP007, UP042 | (20 further codes, 36 findings total) | mixed |

Mypy: 604 errors (src-only, primary scope), across 171 of 374 source files (46%).

Category breakdown (src-only):

| Count | Category | |---:|---| | 147 | type-arg — generic type missing its type argument (e.g. bare dict, Callable) | | 114 | no-untyped-def — function missing a parameter or return type annotation | | 80 | arg-type — argument's type doesn't match the parameter's declared type | | 70 | import-untyped — importing a module with no type stubs / py.typed marker | | 52 | call-arg — call site missing a required argument or passing an unexpected one | | 31 | no-any-return | returning Any from a function with a non-Any declared return type | | 21 | no-untyped-call — calling an untyped function from typed code | | 20 | union-attr — attribute access on a value that could be None (or another union member lacking it) | | 18 | attr-defined | 13 unused-ignore | 13 name-defined | 12 assignment | ≤3 each: var-annotated, return-value, no-redef, prop-decorator, override, operator, list-item, index, call-overload |

Secondary context — whole-repo scope (mypy ., includes tests/, migrations/, scripts/): 6,482 errors across 605 of 957 checked files. The tests/ tree is not part of the distributed package and strict-mode annotation requirements are a different question there than in src/; this inspection does not take a position on which scope a gate should use, only reports both numbers so the choice is informed.

Mypy has no [*]-style auto-fix — none of its findings are mechanically resolvable the way most of ruff's are.


3. File-level distribution — scattered, not clustered

Ruff. Top 10 files by finding count carry 583 of 3,618 (16.1%). 780 of 829 tracked Python files (94%) have at least one finding. This is broad scatter, not a small hot zone.

| Findings | % of total | File | Dominant codes | |---:|---:|---|---| | 100 | 2.8% | src/loomworks/sales_tools/prompts.py | E501 ×100 (100%) | | 86 | 2.4% | src/loomworks/orchestration/router.py | UP045 ×51, UP037 ×20, I001 ×6 | | 73 | 2.0% | src/loomworks/api/routers/engagements.py | B008 ×63 (86%), B904 ×9 | | 64 | 1.8% | src/loomworks/api/routers/renders.py | B008 ×63 (98%) | | 55 | 1.5% | src/loomworks/api/routers/assertions.py | B008 ×48 (87%), B904 ×7 | | 53 | 1.5% | src/loomworks/api/deps.py | B008 ×27, I001 ×10, UP017 ×8 | | 46 | 1.3% | src/loomworks/api/routers/considerations.py | B008 ×46 (100%) | | 38 | 1.1% | src/loomworks/api/routers/memberships.py | B008 ×32 (84%), B904 ×5 | | 35 | 1.0% | src/loomworks/notifications/service.py | UP045 ×23, UP017 ×8 | | 33 | 0.9% | src/loomworks/api/routers/shape_events.py | B008 ×32 (97%) |

Six of these ten files' apparent concentration is almost entirely one rule — B008 — firing on ordinary FastAPI route-parameter declarations (Depends(...), Path(...), etc.) in every router file that has more than a handful of endpoints. That is a distribution artifact of the framework, not a defect hot spot; see §5.

Mypy (src-only). Top 10 files carry 202 of 604 (33.4%) — more concentrated than ruff's spread, but still short of a small isolated cluster. 171 of 374 source files (46%) have at least one error.

| Errors | File | |---:|---| | 34 | src/loomworks/api/deps.py | | 26 | src/loomworks/api/routers/sales_tools.py | | 25 | src/loomworks/engagement/boundary.py | | 23 | src/loomworks/api/routers/seed_conversation.py | | 19 | src/loomworks/orchestration/router.py | | 18 | src/loomworks/orchestration/routers/converse.py | | 17 | src/loomworks/orchestration/prompt.py | | 15 | src/loomworks/uploads/executor.py | | 13 | src/loomworks/credit/seam.py | | 12 | src/loomworks/engagement/composition_orchestrator.py |


4. Age signal — via git blame on every flagged line

Blamed every flagged line (all 3,618 ruff findings; all 636 mypy src-only finding lines — some lines carry more than one mypy error) against its introducing commit's author date. Repo history spans 2026-04-13 (earliest commit) to 2026-08-06 (HEAD, this inspection's baseline).

Ruff, by month the flagged line was introduced (all 3,618 findings matched):

| Month | Findings | % | |---|---:|---:| | 2026-04 | 1,388 | 38.4% | | 2026-05 | 1,283 | 35.5% | | 2026-06 | 505 | 14.0% | | 2026-07 | 271 | 7.5% | | 2026-08 | 171 | 4.7% |

Mypy src-only, by month (all 636 finding-lines matched):

| Month | Findings | % | |---|---:|---:| | 2026-04 | 193 | 30.3% | | 2026-05 | 255 | 40.1% | | 2026-06 | 111 | 17.5% | | 2026-07 | 57 | 9.0% | | 2026-08 | 20 | 3.1% |

Reading this. For both tools, the first two months of the project (April–May) account for roughly two-thirds to three-quarters of everything currently flagged (73.9% ruff, 70.4% mypy) — the backlog is mostly old. But it is not only old: June–August combined still accounts for 26.2% of ruff findings and 29.6% of mypy findings, including 171 ruff findings and 20 mypy errors on lines introduced in August alone, the same month as this inspection. Recent work is still adding to both backlogs, not just failing to shrink an inherited one.


5. Concrete examples — what "everything currently reported" would mean

Ruff — UP017 (825, the single largest category)

datetime.timezone.utcdatetime.UTC. Purely a Python-3.11+ spelling modernization; zero behavioral difference; auto-fixable. Examples:

Ruff — B008 (800, the second-largest category)

All 800 instances, checked without sampling, are FastAPI's own dependency-injection idiomDepends(...) (540), Path(...) (228, including 4 under a local alias PathParam), Query(...) (18), Body(...) (7), File(...) (3), and 2 instances of a Pydantic request-model default (body: AdvanceStageRequest = AdvanceStageRequest()). Examples:

Calling a function in an argument default is exactly what FastAPI's dependency-injection system requires at every route signature; this is the pattern the rule exists to catch in ordinary code, firing on the one framework where it's the documented, required way to write a function signature. Ruff's own documentation names fastapi.Depends/fastapi.Query et al. as the standard case for the extend-immutable-calls setting, which this repo's [tool.ruff.lint] block does not set.

Ruff — I001 (672, third-largest)

Import-block ordering/formatting (isort-equivalent). Auto-fixable. 95 of the 672 (14%) are in migrations/versions/*.py — one-off, never-re-run migration scripts rather than living application code. Examples:

Mypy — type-arg (147, the largest mypy category)

A generic type used without its type parameter — dict instead of dict[str, Any], Callable instead of Callable[..., None]. Examples:

Mypy — no-untyped-def (114, second-largest)

A function with no parameter or return annotation at all — the base case strict = true exists to close. Examples:

Mypy — import-untyped (70, fourth-largest)

All 70 instances are the same underlying fact, checked without sampling: importing stele.registry or stele.webauthn — the sibling DUNIN7 package this repo depends on via [tool.uv.sources] stele = { path = "../stele", editable = true } — which ships no py.typed marker and no separate stub package. Every call site importing from stele in this repo trips it identically. This is a gap in a different DUNIN7 repository, not a defect any change in loomworks-engine can close on its own. Examples:

Mypy — genuine substantive findings, for contrast

Not everything mypy reports is a framework or annotation-completeness artifact. src/loomworks/orchestration/router.py alone carries several that read as real type mismatches, not missing annotations:


6. Precedent elsewhere in DUNIN7 — what other repos gate on

Surface repo (loomworks, TypeScript/Next.js) — gated, and gated on framework defaults. eslint.config.mjs uses eslint-config-next's core-web-vitals and typescript presets verbatim — the framework's own recommended rule set, not a hand-curated allowlist — with exactly one override: @typescript-eslint/no-unused-vars relaxed to recognize the codebase's existing leading-underscore-means-deliberately-unused convention (dated to CR-2026-166 D-4, with the override's reasoning recorded in the config file itself). Wired into CI (eslint, tsc, build, vitest all run on every push per CR-2026-167). The one override exists to match an established codebase convention, not to suppress a category of findings wholesale.

Marketing repo (loomworks-marketing, Astro) — no precedent. No eslint config file, no lint script in package.json, no CI workflow beyond what ships inside node_modules. Nothing to compare against.

Sibling Python repos — no precedent, same gap. Checked stele and stele-agentic-id, the only other Python codebases in DUNIN7. Neither has a ruff or mypy section in its pyproject.toml, and stele's own CI workflow (.github/workflows/test.yml) runs pytest -q only — the identical pattern this engine repo has before B-48. There is no DUNIN7 Python precedent for what a ruff/mypy gate should check; the surface repo's TypeScript precedent (gate on framework defaults, override narrowly and only to match an established convention) is the only existing example in the org, and it is a different toolchain.


DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — B-48 quality tooling Step 0 inspection findings — v0.1 — 2026-08-06