This repository is a uv workspace:
one package per distribution under packages/, and a root that is never built or published.
The root depends on every package, owns the dependency groups they share, and holds
repository-level policy — the lock file, the lint and type-check configuration, the task
definitions and the CI workflows.
| package | distribution | what it is |
|---|---|---|
packages/sphinx-needs |
sphinx-needs |
the Sphinx extension for managing requirements and specifications — documentation, README |
packages/sphinx-mounts |
sphinx-mounts |
the Sphinx extension that mounts external source trees into a build without copying or symlinking — documentation, README |
packages/sphinx-codelinks |
sphinx-codelinks |
fast source-code traceability for sphinx-needs — it scans source files for comment markers, turns them into needs, and links documentation to exact source lines — documentation, README |
packages/sphinx-test-reports |
sphinx-test-reports |
test results as needs: JUnit/ctest/googletest XML and tox-envreport JSON become needs in a build, and a test-reports command turns the same reports into a needs.json without running Sphinx — documentation, README |
The question comes up, so here is the reasoning. The proposal and its discussion are in #1803.
One repository, because the extensions are developed against sphinx-needs as it is now: a change to sphinx-needs and the extension change it calls for land together, tested against each other at the same commit, with one lock file, one CI, one lint and typing configuration and one issue tracker.
Still one distribution per package, and not one sphinx-needs with an extra per feature,
because:
- An install is whole or absent. An extension's dependencies — parser grammars, a libclang binding, a build tool — land only on the people who asked for that extension, and never half-present on everyone else.
- A Python extra is not a feature flag. It is a set of additional requirements, and
nothing records that it was requested; a bundled feature has to probe its own imports at
run time, in its
setup(), its console scripts and its type checking, and a user who later uninstalls the dependency finds out at build time. sphinx-needs pays that once, for matplotlib, and does not want to pay it per extension. A distribution boundary is resolved by the installer, visible to the type checker and versioned. - Each package keeps its own version and cadence. An extension can change its interface without a sphinx-needs major release, and ship a fix without waiting for the next sphinx-needs release.
- Not every package depends on sphinx-needs. sphinx-mounts does not, and an extension's analysis engine or command line can be useful without it.
The coupling that remains is a policy, and tooling keeps it honest: a package that depends
on sphinx-needs requires the current release as its floor and caps at the next major (the
workspace check enforces the range), the release workflow tests every built wheel against
its siblings as published, and uv run poe release-plan says what is pending and in
which order.
Two commands are enough to get started, from this directory:
uv sync --frozen # every package, plus the shared development and test dependencies
uv run poe # list every task, with its helpNo group has to be named: the test tooling is a group of the workspace root, and the root's
default dev group includes it.
Tasks that act on the whole repository are named plainly (lint); tasks that act on one
package end in that package's short name (test-needs, docs-needs, docs-mounts).
Contributions are very welcome — see
the contributing guide, and AGENTS.md for
the repository's layout in more detail (CLAUDE.md only imports it).