Repository navigation
Expand file tree
/
Copy pathruff.toml
More file actions
264 lines (251 loc) · 14.6 KB
/
Copy pathruff.toml
File metadata and controls
264 lines (251 loc) · 14.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# Ruff configuration for AIFactory.
#
# The fleet-wide strict baseline (full select set, mypy, complexity caps) lives,
# pinned and vendored, in `standards/` — see standards/README.md. That baseline
# is consumed by the DIFF-SCOPED ratchet job (.github/workflows/cq-ratchet.yml),
# which holds new/touched code to the full bar (Factory standards 4.6).
#
# This file is the REPO-WIDE config used by the (blocking) `ruff check` in
# ci.yml. The whole tree cannot pass the strict baseline yet (thousands of legacy
# violations), so this config keeps documented legacy carve-outs. Per the
# standard those carve-outs are a ratchet backlog (#652), never a permanent
# loosening, and may only shrink over time (tighten-only).
# The Python this repo TARGETS (#1211). Absent, ruff inferred a version old
# enough to classify `tomllib` (stdlib since 3.11) as third-party, and the two
# configs then wanted OPPOSITE things from the same import block:
#
# $ ruff check --fix scripts # this config
# -import tomllib
# from pathlib import Path
# +import tomllib
#
# $ ruff check --config standards/ruff.toml scripts/cq_ratchet.py --select I
# scripts/cq_ratchet.py:63:1: I001 Import block is un-sorted or un-formatted
#
# Each config's autofix was the other config's violation. That is what kept
# `scripts/` out of ci.yml's repo-wide `ruff check` -- it could not be added
# while the two disagreed, so the directory that holds the ratchet itself was
# lint-gated only by the diff-scoped ratchet.
#
# Matched to standards/ruff.toml deliberately. The Python version is not a
# strictness knob: the strict config may be TIGHTER than the repo-wide one,
# never a DIFFERENT SHAPE. Every carve-out below stays exactly as it was.
target-version = "py311"
# Vendored verification-core copies (Factory#158): byte-exact mirrors of the
# Factory hub canonical (scripts/), governed by the byte-exact drift gate
# (verification-core-drift workflow), NOT this repo's style bar. The repo-wide
# formatter here defaults to line-length 88; the hub canonical is formatted at
# line-length 100, so reformatting these under this config would diverge from
# the canonical and break the drift gate. Excluded for the same reason the hub
# excludes its shared/factory-github vendored mirror. (The diff-scoped strict
# ratchet still lints them via standards/ruff.toml at the fleet line-length.)
extend-exclude = [
# Byte-exact mirror of the Factory hub canonical scripts/ratchet_helpers.py,
# kept identical by the verification-core drift gate. The hub formats at 100;
# letting the local formatter touch it would silently break byte-exactness
# and the next re-vendor (Factory#403).
"scripts/ratchet_helpers.py",
"apps/backend/core/factory_sandbox.py",
"apps/backend/core/nix_provisioner.py",
# RFC-0005 paved road: nix_provisioner's sibling — the declarative
# language-descriptor loader, vendored byte-exact together with
# core/languages/*.yaml (the descriptors it globs). Same hub canonical
# terms as the modules above.
"apps/backend/core/language_descriptors.py",
# RFC-0014 (#664): byte-exact mirror of the hub canonical
# scripts/cost_router_core.py, formatted at the fleet line-length (100). The
# AIFactory-native budget logic that consumes it lives in
# budget_enforcement.py, which IS linted here.
"apps/backend/core/cost_router_core.py",
# RFC-0016 (#190): same deal — byte-exact mirror of the hub canonical
# scripts/artifact_store.py at the fleet line-length (100). It was vendored
# but never excluded, so the local formatter could silently break a
# byte-exact re-vendor (Factory#400).
"apps/backend/core/artifact_store.py",
# Factory#477: byte-exact mirror of the hub canonical scripts/job_dispatch.py
# at the fleet line-length (100). It was vendored, gated by nothing and
# excluded from nothing, so the two copies drifted in both directions and the
# #1107 pod-label defect had to be fixed twice. Now gated; letting the local
# 88-column formatter touch it would break the next re-vendor.
"apps/backend/core/job_dispatch.py",
# Factory#638: the far half of job_dispatch.trace_env — the bootstrap the
# dispatched Job calls to join the run's trace and emit. This WAS this repo's
# own core/tracing_bootstrap.py until TFactory's verify Job needed the same
# emitter; it is now the hub canonical, vendored here and in TFactory, so the
# local 88-column formatter must leave it alone like the others.
"apps/backend/core/job_tracing.py",
# Vendored byte-exact mirror of the Factory hub canonical, kept identical by
# the factory_common drift gate (cq-factory-common-drift.yml). Formatting it
# here would drift it from the hub, so ruff must leave it alone.
"apps/backend/factory_common/",
# The second vendored copy of the same hub canonical, at the same .hub-sha.
# apps/web-server is the sys.path root for `server.*`, so it cannot import
# the apps/backend copy; see apps/web-server/factory_common/README.md.
# Excluded for exactly the same reason as the copy above.
"apps/web-server/factory_common/",
# Same reason, same deal: the vendored factory-github provider layer is a
# byte-exact mirror of the hub canonical (formatted at the fleet
# line-length 100) and is kept identical by factory-github-drift.yml.
# Reformatting it under this config's default 88 would break that gate on
# the next re-vendor, which is exactly what the gate exists to prevent.
"apps/backend/runners/github/",
# Factory#726: the whole-repo security-sink lint engine, vendored byte-exact
# from the hub (shared/factory-seclint/) and kept identical by
# security-lint.yml's drift step. Same reason as every entry above -- the hub
# formats at 100, this config's formatter defaults to 88, and letting it
# touch the copy would break the drift step on whitespace. Note the shape of
# that failure: the formatter would happily "fix" its own complaint, and the
# gate that lost would be the byte-exact one.
"security-lint/",
]
[lint]
# Enable common rule sets
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # Pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
"TID251", # flake8-tidy-imports: banned-api (architecture import boundary, #261)
# #1484: `asyncio.create_task(coro())` on a line of its own hands the task
# to a loop that keeps only a WEAK reference to it, so a GC cycle between
# awaits can collect it mid-flight -- no exception, no log, and usually a
# response that already reported success (#1479 was one such watcher). The
# shared strict baseline in standards/ruff.toml selects all of RUF, so the
# DIFF-SCOPED ratchet has always gated this rule on changed files; this line
# adds it to the BLOCKING repo-wide check too. Affordable because the tree
# is clean: the consequential sites are anchored via server/background.py
# and the four remaining fire-and-forget UI/telemetry emits carry a per-line
# `# noqa: RUF006` stating what is lost if one is dropped.
"RUF006", # store a reference to the return value of asyncio.create_task
# #1624/#1740: aware UTC timestamps, gated repo-wide (tests exempt below)
"DTZ005", # datetime.now() without tz
"DTZ006", # datetime.fromtimestamp() without tz
]
ignore = [
"E501", # line too long (handled by formatter)
"B008", # function call in default argument
"B904", # raise from err (too many to fix now)
"B905", # zip without strict parameter
"C401", # unnecessary generator
"C416", # unnecessary list comprehension
"E402", # module level import not at top of file
# F841 (unused local variable) is NO LONGER blanket-ignored (Factory coding
# standards section 1.10). It is enforced repo-wide; the handful of legacy
# hotspots that still trip it are carved out per-file in the "legacy F841
# ratchet backlog" block below and must be cleaned when next touched.
"W293", # blank line contains whitespace
"B007", # loop control variable not used
"F821", # undefined name (forward refs in type hints used as strings)
"UP035", # deprecated typing imports (legacy modules use typing.List/Dict)
]
[lint.isort]
# The repo root holds the artifact workflow's `spec/` folder (intent/ spec/
# plan/). Ruff's first-party detection treats any root directory as a module,
# so with `spec/` present it reclassified the backend's own `spec` package and
# demanded a different import order in runners/spec_runner.py -- a red
# `backend (ruff + pytest)` on every PR that carries artifacts. Pin it to the
# group it sorts in today so the order no longer depends on which folders exist.
known-third-party = ["spec"]
# ---------------------------------------------------------------------------
# Feature Architecture Standard — enforced import boundary (issue #261)
# ---------------------------------------------------------------------------
# Domain/application logic must stay free of heavy transport/runtime deps so it
# can be loaded by other processes (e.g. the FastAPI web-server) WITHOUT pulling
# in the Claude Agent SDK. See guides/architecture/feature-architecture-standard.md.
#
# Mechanism: the agent SDK (`claude_agent_sdk`) is a banned import (TID251).
# The ban is then exempted everywhere EXCEPT the pure-logic leaf modules in
# `apps/backend/qa/`, which are the codified boundary (see #260: the web-server
# loads qa/review_cycle.py + qa/review_redrive.py by file path precisely because
# importing the qa package would drag in the SDK). Adding a new SDK import to a
# qa leaf module will fail CI until it is either moved to an adapter module or
# added to the documented exception list below.
[lint.flake8-tidy-imports.banned-api]
"claude_agent_sdk".msg = "Pure-logic modules (apps/backend/qa leaf modules) must not import the Claude Agent SDK; keep SDK usage in adapter modules (reviewer/fixer/loop). See guides/architecture/feature-architecture-standard.md."
[lint.per-file-ignores]
"__init__.py" = ["F401"] # unused imports in __init__.py
# --- legacy F841 ratchet backlog -------------------------------------------
# The blanket F841 ignore was dropped (Factory standards 1.10). The safe,
# auto-fixable cases were cleaned in the cq-phase0 pass; the assignments listed
# here have a side-effect-bearing RHS (function call / constructor) so ruff
# cannot remove them mechanically and removing them by hand risks dropping the
# call. They are tracked as a ratchet backlog (#652) and must be resolved when
# the file is next touched. The diff-scoped ratchet job (cq-ratchet.yml) and the
# shared baseline both enforce F841 with NO carve-out, so no NEW F841 can land.
"apps/backend/agents/parallel_integration.py" = ["F841"]
"apps/backend/analysis/ci_discovery.py" = ["F841"]
"apps/backend/cli/mcp_commands.py" = ["F841"]
"apps/backend/context/builder.py" = ["F841"]
"apps/backend/core/workspace/display.py" = ["F841"]
"apps/backend/core/workspace/finalization.py" = ["F841"]
"apps/backend/integrations/bmad/subagents/requirements_analyst.py" = ["F841"]
"apps/backend/integrations/graphiti/providers_pkg/validators.py" = ["F841"]
"apps/backend/memory/graphiti_helpers.py" = ["F841"]
"apps/backend/merge/file_merger.py" = ["F841"]
"apps/backend/merge/merge_pipeline.py" = ["F841"]
"apps/backend/merge/semantic_analysis/js_analyzer.py" = ["F841"]
"apps/backend/planner_lib/generators.py" = ["F841"]
"apps/backend/prompts_pkg/prompt_generator.py" = ["F841"]
"apps/backend/runners/github/services/parallel_followup_reviewer.py" = ["F841"]
"apps/backend/runners/github/services/parallel_orchestrator_reviewer.py" = ["F841"]
"apps/backend/runners/github/test_context_gatherer.py" = ["F841"]
"apps/backend/runners/github/test_rate_limiter.py" = ["F841"]
"apps/backend/services/orchestrator.py" = ["F841"]
"apps/backend/spec/pipeline/orchestrator.py" = ["F841"]
# --- SDK-ban exemptions (TID251) -------------------------------------------
# Adapter / transport / agent-bearing trees that LEGITIMATELY use the SDK.
# The ban is only meant to protect the qa pure-logic leaf modules, so the rest
# of the codebase is exempt. Keep these globs broad: the boundary under test is
# apps/backend/qa, not these areas.
"apps/backend/agents/*" = ["TID251"]
"apps/backend/core/*" = ["TID251"]
"apps/backend/providers/*" = ["TID251"]
"apps/backend/runners/*" = ["TID251"]
"apps/backend/mcp_server/*" = ["TID251"]
"apps/backend/analysis/*" = ["TID251"]
"apps/backend/spec_agents/*" = ["TID251"]
"apps/backend/spec/*" = ["TID251"]
"apps/backend/ideation/*" = ["TID251"]
"apps/backend/prediction/*" = ["TID251"]
"apps/backend/review/*" = ["TID251"]
"apps/backend/services/*" = ["TID251"]
"apps/backend/integrations/*" = ["TID251"]
"apps/web-server/*" = ["TID251"]
# Documented exceptions WITHIN the qa boundary: these are the agent-bearing
# adapter modules of the qa package (they run live agent sessions and so must
# import the SDK). The pure-logic leaf modules — review_cycle.py and
# review_redrive.py — are intentionally NOT listed and stay SDK-free.
"apps/backend/qa/fixer.py" = ["TID251"]
"apps/backend/qa/reviewer.py" = ["TID251"]
"apps/backend/qa/loop.py" = ["TID251"]
"apps/backend/qa/qa_loop.py" = ["TID251"]
"tests/*" = [
"B011", # assert false in tests
"B017", # assert raises bare Exception (acceptable in tests)
"E712", # == True/False (occasionally clearer in test assertions)
"E722", # bare except (acceptable in test cleanup)
"F401", # unused imports (availability checks)
"F811", # redefined-while-unused (pytest fixture argument shadowing)
"F841", # unused locals (tests often bind for clarity / future assertions)
"TID251", # tests may import the agent SDK directly
]
"test_*.py" = ["F401", "F811"]
# The shared test carve-out from standards/ruff.toml (Factory#730). `extend`
# does not deep-merge per-file-ignores -- defining this table locally replaces
# the baseline's, so the carve-out is repeated verbatim here, the same
# technique PFactory's root ruff.toml already uses. "S" is not in this repo's
# `select` above, so this is a no-op today and stays that way until it is
# adopted -- at which point it is already correct instead of silently
# reopening a hole standards/ruff.toml closed. (Measured 2026-08-13: this
# repo's own config, whole-repo, with `--select S`: 10,763 S101 findings --
# every one of them a test-tree file the vendored carve-out never reached.)
"**/test_*.py" = ["S101", "PLR2004", "DTZ005", "DTZ006"]
"**/*_test.py" = ["S101", "PLR2004", "DTZ005", "DTZ006"]
"**/tests/**" = ["S101", "PLR2004", "DTZ005", "DTZ006"]
[format]
quote-style = "double"
indent-style = "space"
line-ending = "auto"