aSPARK reads Flask: 21 of 21 sources check out, and one lead didn't
We ran /charter's discovery pass and /next-steps on a fresh clone of Flask. Every file:line it cited resolves, the graph builds in seconds, and the Product Owner proposed a gap the docs really have. One side note looked like a bug and wasn't.
aSPARK is already in use on a number of projects beyond its author's own. Most of them are closed source, so there is nothing we can link to. This series adds what those projects can't: runs on well-known open-source code that anyone can check for themselves. We take a repository, run aSPARK on a fresh clone, and publish what came out: the good, the wrong, and how we checked. First up is Flask.
Nothing here was sent to the Flask project. No issue, no pull request. Everything ran on a local clone at a pinned commit, pallets/flask@d73fa1c (2026-09-08).
What we ran
/charter, discovery pass. On a repository that already has code, the Facilitator doesn't interview you about what the project is. It reads it and writes §9 Project Context of the constitution: a product brief and a system picture of seven entries, each with thefile:lineit came from.- The confirmation round.
/charterthen asks a human to confirm the picture once. That was us. Because we are not the Flask team, we answered the four product questions as a fictional team working on Flask itself. Those answers are marked as ours below. - aspark-graph on the same clone: size, build time, determinism, and which modules sit at the centre.
/next-steps: the Product Owner surveys the project and proposes one next feature. It writes nothing; it is a recommendation.
What aSPARK wrote about Flask
The system picture, as written (lightly shortened):
- Entry points: the
Flaskclass and theflaskCLI (src/flask/app.py:110,src/flask/cli.py:1122,pyproject.toml:81-82). - Module structure: an I/O-free core in
sansio/, the WSGI layer on top,json/as a subpackage. - Data model:
not found. There is no persistence insrc/, by design: "Flask will never have a database layer" (docs/design.rst:210). - Test practice: pytest via tox across Python 3.10 to 3.15 and PyPy, warnings treated as errors, plus mypy and pyright over
tests/type_check/. - Conventions: every change ships a test, docs, a
CHANGESentry andversionchanged; ruff and codespell. - How to run:
uv run --group dev tox run. - Known pain points: async views run on threads (no ASGI), and the
RequestContexttoAppContextmerge is mid-deprecation (docs/design.rst:191-202,CHANGES.rst:8-16).
It also found one small contradiction in the repository: the pull request template points to a CONTRIBUTING.rst that does not exist in the repo; the contribution guide lives on the Pallets website.
The profile it proposed, and we confirmed: a library with a cli, plus handles-auth, because Flask implements signed-cookie sessions with secret-key rotation (src/flask/sessions.py:309). That switches on the security lens for later phases.
The fact check: 21 of 21
A picture with sources is only worth something if the sources are real. A short script resolved every file:line in §9 against the clone: the file must exist, the line must be in range, and the cited line is printed so a person can judge whether it says what the entry claims.
21 of 21 citations resolve. We then read the cited lines: they point at what their entry describes. A few cite a file by its first line rather than a specific statement (the module structure entry points at sansio/scaffold.py:1), which is weaker evidence but not wrong. The strongest is docs/design.rst:210, which reads, verbatim, "Flask will never have a database layer."
The whole discovery pass took a little under three minutes.
The graph
aspark-graph builds a deterministic graph of the code (and of the delivery trail, which Flask doesn't have):
- 1,663 code entities, 171 import edges.
- Built three times, byte-identical every time (
sha256 7a6150df…), in a few seconds each on a laptop under load. - The centre of the codebase:
flask/__init__.pyis imported 52 times,globals.py17 times,helpers.pyandwrappers.py10 times each.
Without a .spark/ trail there are no stories yet, so questions like "which stories does this change touch?" have nothing to answer. That layer grows with every loop a team runs.
The proposal: a 3.2 upgrade guide
/next-steps recommended one feature, size S, docs only: an upgrade guide for Flask 3.2's context merge and the new dispatch signatures.
The reasoning, which we checked line by line:
- In 3.2, twelve overridable methods take
ctx: AppContextas their first parameter.Flask.__init_subclass__detects old-style overrides and warns (src/flask/app.py:261-306). - The list of those twelve methods, and the exact rule for what counts as the new signature, exist only in the source. The docs carry two
deprecated:: 3.2notes (docs/api.rst:309-316) and no migration page. docs/shell.rststill teaches building aRequestContextand callingpush()/pop(), the API that is now a deprecated alias.- 3.2.0 is unreleased. And many extension test suites treat warnings as errors, as Flask's own does, so an undocumented deprecation arrives as a red CI run on upgrade day.
It rejected two alternatives with reasons: answering the one open issue (a hosting-provider listing, a maintainer policy call) and planning the 4.0 removal (premature before the migration path is documented).
It also raised a fair question for the team rather than answering it: redirect now defaults to 303 instead of 302 (CHANGES.rst:22-26) without a deprecation warning. Flask's changelog argues GET and POST behave as before. The tension is only with a rule our fictional team wrote into its constitution ("deprecate before changing behaviour"). That is exactly the kind of thing a Product Owner should put in front of the people who decide.
The lead that didn't hold
The Product Owner added a side note: __init_subclass__ calls setattr(Flask, …), so the base class gets patched the first time any subclass uses an old signature, which "may affect other subclasses". That sounds like a bug.
We read the code. It isn't one. The replacement wrapper, add_ctx (src/flask/app.py:98-107), only inserts the context when a call is missing it; calls that already pass one go through unchanged. Patching the base class is deliberate, so that an old-style override calling super() without the context still works. New-style subclasses see no difference.
We are publishing this on purpose. An agent that reads code quickly will sometimes read it wrong, and it will sound confident when it does. That is why every aSPARK phase ends in a gate a person decides, and why every claim in this post was checked against the source before it went out.
What this shows, and what it doesn't
- It shows that
/chartercan produce a sourced picture of an unfamiliar, mature codebase in minutes, with verifiable sources rather than impressions. - It does not show that the upgrade guide is what the Flask maintainers want. They know their roadmap; we read a snapshot.
- The product answers are ours, as a fictional team. The profile and constitution were confirmed by us, not by Pallets.
- aspark-graph resolved Flask's Python imports well. On the two other repositories we tried before choosing Flask, Express (CommonJS
require) and ripgrep (Rustusepaths), it resolved almost none. The next episodes will pick projects where that is less of a factor, and the gap goes on the graph's backlog.
Next in the series: another well-known repository, same procedure, same fact check. If you maintain a project and would like us to run aSPARK on it, or not, tell us.