make lint was Python-only (PY_SRC never included flowsint-app) —
frontend prettier/eslint lived solely in flowsint-app's own yarn
scripts, disconnected from the repo's single lint entry point. Both
are 0-errors clean today (eslint's ~130 findings are all warnings),
same bar the Python side is already held to, so added as blocking
rather than a separate opt-in step. CONTRIBUTING.md updated to match.
Calling \`uv run ruff\` twice per changed file (up to ~500 subprocess
spawns on this branch) was flaky: some invocations intermittently
returned empty output, making the byte-for-byte comparison fail and
the file wrongly counted as semantically changed. Resolving the
binary path once via \`uv run which ruff\` and invoking it directly
in the loop is both correct and faster. Verified stable across
repeated runs.
alembic/ was never in PY_SRC, so it never went through black/isort/
flake8 before, or ruff after — 31 files were unformatted. Adds it to
PY_SRC and reformats via ruff format + check --fix, plus a per-file-
ignore for env.py's star-import-from-models pattern (standard Alembic
autogenerate setup: needs every model's metadata visible under one
name, not enumerable without duplicating the model list here).
The ratchet gates mypy on files this branch "touches" vs BASE_REF,
to avoid blocking every PR on the ~2200 pre-existing errors. The ruff
adoption reformatted the whole tree, so every .py file differs from
main by formatting alone — the ratchet degraded into gating the
entire backlog on this one PR.
A file now only counts as changed if re-running ruff (format + fix)
on the BASE_REF version doesn't reproduce today's content
byte-for-byte. Verified locally: 578 errors across 31 files dropped
to the files actually edited this session.
One tool instead of three, one config instead of four duplicated
blocks. black/isort/flake8 dropped from every package's dev deps,
ruff added once; .flake8 deleted, [tool.black]/[tool.isort] blocks
deleted from all 4 sub-package pyproject.toml files.
Config lives once, at the workspace root: ruff walks up from any file
it's checking until it finds a pyproject.toml with a [tool.ruff]
table (verified empirically — a package's own pyproject.toml lacking
that table doesn't stop the search), so the root-level [tool.ruff]
applies to all four packages without duplication. That's the one real
win over the old per-package black/isort blocks, which had to be
copy-pasted four times and kept in sync by hand.
Same rule surface as before, not more: select = E/F/W/I (pycodestyle
+ pyflakes + isort-equivalent), matching exactly what flake8 checked.
ruff has other rule groups (UP/pyupgrade, B/bugbear) that would
surface real things — deliberately left off. Turning those on finds
new debt and is a separate decision with its own diff to review, not
a side effect of swapping the tool underneath an unchanged rule set.
Same E501 exclusion carried over too, same reasoning: the formatter
is the sole authority on line length.
known-first-party carries over unchanged (same isort concern this
repo already fixed once — see "app" vs "flowsint_api" in the ratchet
history) and the wildcard-import per-file-ignore for
flowsint_core/__init__.py still applies, verified directly.
Six files reformatted by `ruff format` where it disagreed with black
on edge cases (import grouping, ternary line-breaking, blank lines
after a class statement) — all cosmetic, no behavior change, reviewed
diff by diff. One real ruff-check finding (an import block ruff's
isort-equivalent wanted sorted differently) — took ruff check --fix.
Verified: `make lint` clean, `make test` green (all 4 packages),
lint-staged tested with a real staged-file run (not just config
inspection) to confirm `uv run ruff format` / `uv run ruff check --fix`
actually execute, mypy ratchet target unaffected (still fires on its
own pre-existing backlog, unrelated to this change).
flake8/mypy/eslint were configured (or, for eslint, declared) but no CI
job and no pre-commit hook ever executed them — black/isort/flake8 had
no line-length agreement either (flake8 defaulted to 79, black to 88).
- .flake8: align max-line-length with black, ignore black-conflicting
codes, carve out the one file with an intentional wildcard-export API
- Makefile: `make lint` (black/isort/flake8, blocking — repo is clean),
`make typecheck` (mypy, ratcheted to files the branch touched — ~2200
pre-existing errors workspace-wide, not fixed blindly in one pass)
- .github/workflows/lint.yml: runs the above in CI, plus the frontend
equivalents (prettier/eslint blocking where clean, eslint/tsc
ratcheted where not)
- .github/workflows/tests.yml: add the vitest job that never existed
- flowsint-app/.eslintrc.cjs: parser was declared but never installed
(yarn lint threw immediately) — install @typescript-eslint/*, add
react-hooks/recommended; no-explicit-any set to warn (real backlog,
not fixed blindly)
- lint-staged + husky pre-commit: mirrors the CI checks that are
actually clean today (black/isort/flake8, prettier). eslint is left
out of pre-commit on purpose — its backlog would block unrelated
commits; it stays a CI-only ratchet until paid down.
Single entrypoint: CI calls `make test`, same as local development,
so the two can't drift. uv handles Python (.python-version) and
dependency sync; cache keyed on uv.lock.
Also add flowsint-api to `make test` — its suite existed but was
never wired in.
Frontend baked VITE_API_URL into the bundle at build time, so
published images pointed at whatever URL CI had — "fail to fetch"
for every other deployment. Bundle now defaults to same-origin
relative URLs and the frontend nginx proxies /api/ to the api
service over the Docker network; CORS becomes irrelevant.
- docker-compose.prod.yml: use ghcr images (FLOWSINT_VERSION,
default latest), drop build sections; bind postgres/redis/neo4j/
api ports to 127.0.0.1 — only 5173 faces the network
- nginx: /api/ reverse proxy, request-time DNS resolution, SSE
support (proxy_buffering off)
- Makefile: prod pulls instead of building; remove dead deploy
targets referencing nonexistent docker-compose.deploy.yml
- README: network/server deployment section (secrets checklist,
version pinning, TLS via reverse proxy)