treegraphduals 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/.gitignore +5 -1
  2. treegraphduals-0.2.0/CHANGELOG.md +63 -0
  3. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/CITATION.cff +2 -2
  4. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/PKG-INFO +32 -11
  5. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/README.md +23 -2
  6. treegraphduals-0.2.0/docs/adr/0001-spec-0-support-policy.md +83 -0
  7. treegraphduals-0.2.0/docs/adr/0002-spec-0-windows-per-minor-version.md +46 -0
  8. treegraphduals-0.2.0/docs/adr/0003-no-free-threaded-ci-jobs.md +55 -0
  9. treegraphduals-0.2.0/docs/adr/0004-docs-build-runs-tests-once-and-reports-failures.md +60 -0
  10. treegraphduals-0.2.0/docs/adr/0005-security-fixes-for-latest-release-only.md +59 -0
  11. treegraphduals-0.2.0/docs/adr/0006-ai-provenance-tracking.md +126 -0
  12. treegraphduals-0.2.0/docs/adr/README.md +27 -0
  13. treegraphduals-0.2.0/docs/adr/template.md +24 -0
  14. treegraphduals-0.2.0/docs/ai_development.rst +189 -0
  15. treegraphduals-0.2.0/docs/ai_provenance.rst +57 -0
  16. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/docs/conf.py +7 -1
  17. treegraphduals-0.2.0/docs/contributing.rst +175 -0
  18. treegraphduals-0.2.0/docs/definitions.md +93 -0
  19. treegraphduals-0.2.0/docs/index.rst +112 -0
  20. treegraphduals-0.2.0/docs/installation.rst +26 -0
  21. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/pyproject.toml +20 -9
  22. treegraphduals-0.2.0/tests/test_ai_provenance.py +711 -0
  23. treegraphduals-0.2.0/tests/test_guard_github.py +222 -0
  24. treegraphduals-0.2.0/uv.lock +1609 -0
  25. treegraphduals-0.1.0/CHANGELOG.md +0 -64
  26. treegraphduals-0.1.0/docs/contributing.rst +0 -4
  27. treegraphduals-0.1.0/docs/index.rst +0 -70
  28. treegraphduals-0.1.0/docs/installation.rst +0 -8
  29. treegraphduals-0.1.0/src/treegraphduals/agents/__init__.py +0 -4
  30. treegraphduals-0.1.0/uv.lock +0 -1383
  31. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/LICENSE +0 -0
  32. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/docs/Makefile +0 -0
  33. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/docs/_static/.gitkeep +0 -0
  34. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/docs/api/core.rst +0 -0
  35. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/docs/api/timeseries.rst +0 -0
  36. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/docs/api/visualizations.rst +0 -0
  37. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/docs/changelog.md +0 -0
  38. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/docs/examples.rst +0 -0
  39. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/docs/quickstart.rst +0 -0
  40. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/docs/testing.rst +0 -0
  41. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/docs/tutorial.rst +0 -0
  42. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/pytest.ini +0 -0
  43. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/__init__.py +0 -0
  44. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/core/__init__.py +0 -0
  45. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/core/base_graph.py +0 -0
  46. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/core/binary_tree.py +0 -0
  47. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/core/dag.py +0 -0
  48. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/core/erdos_renyi.py +0 -0
  49. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/core/forest.py +0 -0
  50. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/core/galton_watson.py +0 -0
  51. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/core/graph.py +0 -0
  52. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/core/multigraph.py +0 -0
  53. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/core/polytree.py +0 -0
  54. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/core/real_tree.py +0 -0
  55. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/core/tree.py +0 -0
  56. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/py.typed +0 -0
  57. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/timeseries/__init__.py +0 -0
  58. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/timeseries/timeseries.py +0 -0
  59. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/visualizations/__init__.py +0 -0
  60. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/visualizations/plot_combined.py +0 -0
  61. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/visualizations/plot_timeseries.py +0 -0
  62. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/src/treegraphduals/visualizations/plot_trees.py +0 -0
  63. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/tests/test_binary_tree.py +0 -0
  64. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/tests/test_timeseries.py +0 -0
  65. {treegraphduals-0.1.0 → treegraphduals-0.2.0}/tests/test_tree.py +0 -0
@@ -16,4 +16,8 @@ dist/
16
16
  .ruff_cache/
17
17
  .env
18
18
  docs/coverage_report.md
19
- docs/test_results.txt
19
+ docs/test_results.txt
20
+ .claude/worktrees/
21
+ DEVNOTES.md
22
+ docs/ai_stats.md
23
+ mutants/
@@ -0,0 +1,63 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
4
+
5
+ ## \[0.2.0\] - 2026-10-11
6
+
7
+ ### Added
8
+
9
+ - Community health files: a Code of Conduct (Contributor Covenant v2.1), issue forms for bug reports, feature requests and math/definition questions, a pull request template, `SECURITY.md` and `SUPPORT.md`. Questions go to GitHub Discussions (#70).
10
+ - Contributing guide: the contribution process, requirements for code, and the test policy (#70, #83).
11
+ - README and docs badges, and weekly Dependabot updates for GitHub Actions and `uv.lock` (#60).
12
+ - A quarterly SPEC 0 workflow that opens a PR raising the Python and dependency floors, and `scripts/sync_python_versions.py`, which keeps every copy of the supported-Python list in sync with `pyproject.toml` (#78).
13
+ - AI-use disclosure: every agent-authored PR records the models, session, AI-generated files, human input and review rounds, and `CLAUDE.md` sets the provenance rules (#82).
14
+ - AI provenance tracking: each PR's human steering and AI/human line authorship is recorded, and the totals, plus a nightly mutation-testing score, are published on the "AI Provenance" docs page (#81, ADR 0006).
15
+ - Docs page: "How This Project Is Built with AI" (#72).
16
+ - Agent workflow tooling: the `/work-issue` skill, a `reviewer` subagent based on Matt Pocock's skills, and a hook that keeps pushes to `main`, PR merges and repository-settings changes with the maintainer, with a test suite for the hook (#71).
17
+ - Architecture decision records in `docs/adr/` (ADRs 0001–0006), and `.github/CODEOWNERS`.
18
+
19
+ ### Changed
20
+
21
+ - **Breaking:** requires Python 3.13 or newer, and raises the minimum versions of NumPy (2.3), SciPy (1.15), Matplotlib (3.11), NetworkX (3.5), igraph (1.0) and SymPy (1.14), following the SPEC 0 support policy (ADRs 0001 and 0002). Python 3.12 users should stay on 0.1.0.
22
+ - CI tests Python 3.13 and 3.14 (required) and 3.15 (non-blocking), plus a non-blocking job on the minimum dependency versions. Free-threaded builds are not tested yet, because igraph has no free-threaded wheels (ADR 0003).
23
+ - Read the Docs builds run the test suite once. Failing tests no longer abort the docs build; the Testing page shows the failures instead (ADR 0004).
24
+
25
+ ### Removed
26
+
27
+ - The empty `treegraphduals.agents` subpackage (planned LangGraph tooling, now out of scope) (#74).
28
+
29
+ ### Security
30
+
31
+ - Every GitHub Action is pinned to a full commit SHA. Workflows that don't push no longer keep checkout credentials, and the release workflow defaults to a read-only token (#83).
32
+ - An OpenSSF Scorecard workflow and README badge, and zizmor checks of the workflow files in pre-commit (#83).
33
+ - GitHub releases carry Sigstore-signed build provenance; the docs explain how to verify it (#83).
34
+ - Dependabot version updates wait out a 7-day cooldown; security updates are not delayed (#83).
35
+ - Vulnerabilities are reported privately (see `SECURITY.md`), and fixes ship in the latest release only (ADR 0005).
36
+
37
+ ## \[0.1.0\] - 2026-09-18
38
+
39
+ ### Added
40
+
41
+ - Initial project setup: `uv` packaging, Ruff lint/format, Pyrefly type checking, pytest + Hypothesis + coverage, Sphinx docs on Read the Docs, `CITATION.cff` with Zenodo DOI, pre-commit hooks.
42
+ - Packaging: Hatchling build backend, so the project can be built and published (`uv build`). Ships `py.typed`, the MIT license file, README as the long description, classifiers, keywords and project URLs.
43
+ - `tests/test_timeseries.py`: regression tests for the extrema cases below, the numerical fallback path, uniform sampling, and level-set tree construction.
44
+
45
+ ### Changed
46
+
47
+ - **Breaking:** moved `core`, `timeseries`, `visualizations` and `agents` under a single `src/treegraphduals/` package. Imports are now `from treegraphduals.core import Tree` rather than `from core import Tree`, so the library no longer claims generic top-level names.
48
+ - `requires-python` raised to `>=3.12`, matching what CI and Read the Docs test.
49
+ - Test and documentation tooling moved out of runtime dependencies into the `dev` and `docs` dependency groups; a plain `pip install treegraphduals` no longer pulls in pytest and Sphinx.
50
+ - Doctests now run by default: `testpaths` covers `src` as well as `tests`, so the `--doctest-modules` flag in CI is no longer a no-op (62 tests, up from 40).
51
+ - `docs/conf.py` reads the version from package metadata instead of hardcoding it, and no longer injects an absolute local path for doctest setup.
52
+ - Replaced a `print` with `warnings.warn` when symbolic differentiation falls back to the numerical extrema search.
53
+ - Cleared the Ruff backlog (56 findings): added missing module and magic-method docstrings, made ASCII-art docstrings raw so the diagrams render literally, replaced implicit `Optional` annotations, narrowed a bare `except`, collapsed nested conditionals, sorted `__all__` lists, and dropped dead locals.
54
+ - README reorganized into what the package computes today versus what is planned. Every original bullet is preserved; the previous single list mixed 25+ unimplemented features (duals, visibility graphs, merge trees, graph metrics) with the 7 that work, and it is the PyPI long description.
55
+
56
+ ### Fixed
57
+
58
+ - Docstring tree diagrams rendered mangled: 15 docstrings were not raw strings, so a trailing `\` in the ASCII art silently swallowed the following newline and joined rows together (`/ \` + `1 2` became `/ 1 2`). The art in the source was correct all along. Diagrams are now raw strings inside RST literal blocks, and the Horton pruning and Horton-Strahler examples are redrawn from what the algorithms actually produce.
59
+ - The Sphinx build is now warning-free (was 84 warnings).
60
+ - Symbolic extrema search (`TimeSeries.from_function(..., preserve_extrema=True)`) no longer silently discards critical points:
61
+ - Real roots that sympy returns in complex form (Cardano's formula writes the three real roots of a cubic using `I`) were dropped, because the reality test was "does `float()` succeed". Reality is now decided by the magnitude of the imaginary part, so e.g. `t**4/4 - 2*t**2 + t/3` reports its three extrema instead of none.
62
+ - Periodic derivatives only ever produced sympy's principal solutions, so `sin(t)` on `[0, 10]` lost its maximum at `5*pi/2`. Critical points are now solved over the interval, which enumerates the periodic solutions.
63
+ - `plot_tree_and_harris_path` no longer leaks one matplotlib figure per call (pyplot kept every discarded `plot_tree` figure alive), and now draws the tree into its left axes instead of a "Tree plot" placeholder.
@@ -1,8 +1,8 @@
1
1
  cff-version: 1.2.0
2
2
  message: "If you use this software in your work, please cite it as below."
3
3
  title: "treegraphduals"
4
- version: "0.1.0"
5
- date-released: "2026-09-19"
4
+ version: "0.2.0"
5
+ date-released: "2026-10-12"
6
6
  authors:
7
7
  - family-names: "Khan"
8
8
  given-names: "Ibraheem"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: treegraphduals
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Trees, their duals, graphs, and time series: level-set trees, Harris paths, Horton-Strahler pruning, and horizontal tunnelability graphs.
5
5
  Project-URL: Homepage, https://github.com/pikhan/treegraphduals
6
6
  Project-URL: Repository, https://github.com/pikhan/treegraphduals
@@ -15,21 +15,33 @@ Keywords: duality,graphs,harris paths,horton-strahler,level-set trees,random sel
15
15
  Classifier: Development Status :: 3 - Alpha
16
16
  Classifier: Intended Audience :: Science/Research
17
17
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
20
  Classifier: Topic :: Scientific/Engineering :: Mathematics
21
21
  Classifier: Typing :: Typed
22
- Requires-Python: >=3.12
23
- Requires-Dist: igraph>=0.11
24
- Requires-Dist: matplotlib>=3.8
25
- Requires-Dist: networkx>=3.2
26
- Requires-Dist: numpy>=1.26
27
- Requires-Dist: scipy>=1.11
28
- Requires-Dist: sympy>=1.12
22
+ Requires-Python: >=3.13
23
+ Requires-Dist: igraph>=1.0
24
+ Requires-Dist: matplotlib>=3.11
25
+ Requires-Dist: networkx>=3.5
26
+ Requires-Dist: numpy>=2.3
27
+ Requires-Dist: scipy>=1.15
28
+ Requires-Dist: sympy>=1.14
29
29
  Description-Content-Type: text/markdown
30
30
 
31
- [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22839827.svg)](https://doi.org/10.5281/zenodo.22839827)
31
+ [![CI](https://github.com/pikhan/treegraphduals/actions/workflows/ci.yml/badge.svg)](https://github.com/pikhan/treegraphduals/actions/workflows/ci.yml)
32
+ [![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pikhan/treegraphduals/badges/coverage.json)](https://github.com/pikhan/treegraphduals/actions/workflows/ci.yml)
33
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
34
+ [![PyPI](https://img.shields.io/pypi/v/treegraphduals)](https://pypi.org/project/treegraphduals/)
35
+ [![Python versions](https://img.shields.io/pypi/pyversions/treegraphduals)](https://pypi.org/project/treegraphduals/)
36
+ [![License](https://img.shields.io/pypi/l/treegraphduals)](https://github.com/pikhan/treegraphduals/blob/main/LICENSE)
32
37
  [![Docs](https://readthedocs.org/projects/treegraphduals/badge/?version=latest)](https://treegraphduals.readthedocs.io/en/latest/)
38
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22839827.svg)](https://doi.org/10.5281/zenodo.22839827)
39
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/pikhan/treegraphduals/badge)](https://scorecard.dev/viewer/?uri=github.com/pikhan/treegraphduals)
40
+ [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/15397/badge)](https://www.bestpractices.dev/projects/15397)
41
+
42
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
43
+ [![Typed: Pyrefly](https://img.shields.io/badge/typed-Pyrefly-blue)](https://pyrefly.org/)
44
+ [![Tested with Hypothesis](https://img.shields.io/badge/tested%20with-Hypothesis-blue)](https://hypothesis.readthedocs.io/)
33
45
 
34
46
  # treegraphduals
35
47
 
@@ -46,7 +58,7 @@ the piece you need is ready.
46
58
  pip install treegraphduals # or: uv add treegraphduals
47
59
  ```
48
60
 
49
- Requires Python 3.12+.
61
+ Requires Python 3.13+.
50
62
 
51
63
  ## Quickstart
52
64
 
@@ -167,6 +179,15 @@ The graph classes are intended to become general enough to allow for:
167
179
 
168
180
  Full API documentation, tutorials, and examples: <https://treegraphduals.readthedocs.io>
169
181
 
182
+ ## Getting help and contributing
183
+
184
+ - Questions: [GitHub Discussions](https://github.com/pikhan/treegraphduals/discussions) (Q&A; definition
185
+ questions go in Math and Definitions).
186
+ - Bugs, feature requests and definition disagreements: [issues](https://github.com/pikhan/treegraphduals/issues/new/choose).
187
+ - Security vulnerabilities: report privately, see [`SECURITY.md`](SECURITY.md).
188
+ - Contributing: see the [contributing guide](https://treegraphduals.readthedocs.io/en/latest/contributing.html).
189
+ Everyone taking part follows the [Code of Conduct](CODE_OF_CONDUCT.md); [`SUPPORT.md`](SUPPORT.md) has the details.
190
+
170
191
  ## Citing
171
192
 
172
193
  If you use this package, please cite it via the DOI above, or see
@@ -1,5 +1,17 @@
1
- [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22839827.svg)](https://doi.org/10.5281/zenodo.22839827)
1
+ [![CI](https://github.com/pikhan/treegraphduals/actions/workflows/ci.yml/badge.svg)](https://github.com/pikhan/treegraphduals/actions/workflows/ci.yml)
2
+ [![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pikhan/treegraphduals/badges/coverage.json)](https://github.com/pikhan/treegraphduals/actions/workflows/ci.yml)
3
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
4
+ [![PyPI](https://img.shields.io/pypi/v/treegraphduals)](https://pypi.org/project/treegraphduals/)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/treegraphduals)](https://pypi.org/project/treegraphduals/)
6
+ [![License](https://img.shields.io/pypi/l/treegraphduals)](https://github.com/pikhan/treegraphduals/blob/main/LICENSE)
2
7
  [![Docs](https://readthedocs.org/projects/treegraphduals/badge/?version=latest)](https://treegraphduals.readthedocs.io/en/latest/)
8
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22839827.svg)](https://doi.org/10.5281/zenodo.22839827)
9
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/pikhan/treegraphduals/badge)](https://scorecard.dev/viewer/?uri=github.com/pikhan/treegraphduals)
10
+ [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/15397/badge)](https://www.bestpractices.dev/projects/15397)
11
+
12
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
13
+ [![Typed: Pyrefly](https://img.shields.io/badge/typed-Pyrefly-blue)](https://pyrefly.org/)
14
+ [![Tested with Hypothesis](https://img.shields.io/badge/tested%20with-Hypothesis-blue)](https://hypothesis.readthedocs.io/)
3
15
 
4
16
  # treegraphduals
5
17
 
@@ -16,7 +28,7 @@ the piece you need is ready.
16
28
  pip install treegraphduals # or: uv add treegraphduals
17
29
  ```
18
30
 
19
- Requires Python 3.12+.
31
+ Requires Python 3.13+.
20
32
 
21
33
  ## Quickstart
22
34
 
@@ -137,6 +149,15 @@ The graph classes are intended to become general enough to allow for:
137
149
 
138
150
  Full API documentation, tutorials, and examples: <https://treegraphduals.readthedocs.io>
139
151
 
152
+ ## Getting help and contributing
153
+
154
+ - Questions: [GitHub Discussions](https://github.com/pikhan/treegraphduals/discussions) (Q&A; definition
155
+ questions go in Math and Definitions).
156
+ - Bugs, feature requests and definition disagreements: [issues](https://github.com/pikhan/treegraphduals/issues/new/choose).
157
+ - Security vulnerabilities: report privately, see [`SECURITY.md`](SECURITY.md).
158
+ - Contributing: see the [contributing guide](https://treegraphduals.readthedocs.io/en/latest/contributing.html).
159
+ Everyone taking part follows the [Code of Conduct](CODE_OF_CONDUCT.md); [`SUPPORT.md`](SUPPORT.md) has the details.
160
+
140
161
  ## Citing
141
162
 
142
163
  If you use this package, please cite it via the DOI above, or see
@@ -0,0 +1,83 @@
1
+ # 0001. Follow SPEC 0 for supported Python and dependency versions
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-10-10
5
+ - **Issues:** #76, #77, #78, #75, #79
6
+
7
+ ## Context
8
+
9
+ treegraphduals is a scientific Python library. Its core dependencies (NumPy,
10
+ SciPy, Matplotlib, NetworkX) follow [SPEC 0](https://scientific-python.org/specs/spec-0000/)
11
+ and drop old versions on a published quarterly schedule. SPEC 0 recommends
12
+ dropping a Python version **3 years** after its release and a core package
13
+ version **2 years** after its release. The stated purpose is to limit
14
+ maintenance burden. Projects may drop earlier when a critical fix isn't
15
+ backported. SPEC 0 doesn't forbid supporting versions longer.
16
+
17
+ Before this ADR, the floors (`requires-python >=3.12`, `numpy>=1.26`,
18
+ `scipy>=1.11`, `matplotlib>=3.8`, `networkx>=3.2`) were all older than SPEC 0
19
+ allows. CI tested none of them: it tested only the latest locked versions, on
20
+ one interpreter. The floors were claims, not tested facts.
21
+
22
+ Installers honor `Requires-Python`. When a release drops a Python version,
23
+ users on that version keep getting the last release that supported it.
24
+ Dropping a version never breaks existing users.
25
+
26
+ JOSS and pyOpenSci reviewers (#64) look for a stated support policy.
27
+
28
+ ## Options considered
29
+
30
+ - **A. Follow SPEC 0.** Drop each Python version 3 years after release and each
31
+ core-package version 2 years after release, quarterly. This is the same
32
+ schedule our dependencies use, and it keeps CI small.
33
+ - **B. SPEC 0, but keep each Python version once it works ("perpetual
34
+ support").** The claim decays. Once NumPy and SciPy stop publishing wheels
35
+ for a version, CI on it tests ever-older dependencies that nobody else tests.
36
+ CPython itself stops security fixes 5 years after release (3.13: October 2029).
37
+ "It worked when we tested it" says nothing about code written later. Users
38
+ on old versions are already served by old releases (see Context).
39
+ - **C. A fixed floor, bumped when someone remembers.** What the project did
40
+ until now. The floors drifted (see Context).
41
+ - **D. NEP 29.** Superseded by SPEC 0.
42
+
43
+ ## Decision
44
+
45
+ 1. **Python.** Support each CPython minor version until its SPEC 0 drop date
46
+ (3 years after its x.y.0 release). The `requires-python` floor is the oldest
47
+ version still in that window. Drop earlier only if a dependency we need
48
+ forces it. Python 3.12 is dropped at the next release (#77), at its SPEC 0
49
+ date (2026 Q4), or a few days before it if that release lands before
50
+ 2026-10-01.
51
+ 2. **Core dependencies** (the SPEC 0 core packages we depend on: NumPy, SciPy,
52
+ Matplotlib, NetworkX). Floors follow SPEC 0's 2-year window.
53
+ 3. **Other runtime dependencies** (igraph, SymPy, and any added later). Floors
54
+ follow the same 2-year window (spec0-action's `update_all: 2`). They are
55
+ raised further when CI's lowest-direct job (#78) shows they must be.
56
+ 4. **Cadence.** Floors are bumped quarterly by an automated PR (#78) that a
57
+ maintainer reviews and merges. The PR's GitHub notification (email) is the
58
+ maintainer's prompt to act; no separate calendar or email reminder is
59
+ required. A release after a quarter boundary includes that quarter's drops.
60
+ 5. **New Python versions.** As soon as uv can install a prerelease, add it and
61
+ its free-threaded build to CI as non-blocking jobs. Make the normal build
62
+ blocking, and add its classifier, once the final release is out, every
63
+ runtime dependency installs from wheels, and the job has been green on
64
+ `main` (#79 for 3.15).
65
+ 6. **Free-threaded builds** are tested in non-blocking jobs only. We don't
66
+ claim free-threading support (no classifier, no thread-safety guarantee)
67
+ until a separate ADR decides to.
68
+ 7. **Development** happens on the newest blocking version. CI's lint and
69
+ type-check job runs on the floor.
70
+ 8. **No upper bounds** on dependencies. `uv.lock` pins exact versions for
71
+ development.
72
+
73
+ ## Consequences
74
+
75
+ - The next release requires Python 3.13+ and raises the dependency floors
76
+ (#77). Python 3.12 users keep getting 0.1.x.
77
+ - Expected Python drops: 3.13 in 2027 Q4, 3.14 in 2028 Q4.
78
+ - Every quarter an automated PR arrives. CI must pass on the new floors,
79
+ including the lowest-direct job.
80
+ - The supported-version list lives in several files (pyproject, CI matrix,
81
+ `.readthedocs.yaml`, the release smoke test, README, CLAUDE.md). #78
82
+ keeps them in sync.
83
+ - Rules out perpetual support (option B) and unscheduled floor bumps (option C).
@@ -0,0 +1,46 @@
1
+ # 0002. Count SPEC 0 dependency windows per minor version
2
+
3
+ - **Status:** Proposed
4
+ - **Date:** 2026-10-11
5
+ - **Issues:** #77, #78
6
+
7
+ ## Context
8
+
9
+ [ADR 0001](0001-spec-0-support-policy.md) decisions 2 and 3 set dependency
10
+ floors by a 2-year window, but don't say what the window is counted over. Two
11
+ readings give different floors:
12
+
13
+ - **Per release:** the floor is the oldest *release* (any patch) from the last
14
+ 2 years. In 2026 Q4 this gives `igraph>=0.11.8` (released 2024-10-28).
15
+ - **Per minor version:** the floor is the oldest *minor version* whose x.y.0
16
+ release came out in the last 2 years. igraph 0.11's first release was
17
+ 2023-10-12, so 0.11 is out and the floor is `igraph>=1.0` (2025-10-23).
18
+
19
+ [SPEC 0](https://scientific-python.org/specs/spec-0000/) uses the
20
+ per-minor-version reading: its drop schedule lists minor versions by the date of
21
+ their x.y.0 release. spec0-action, which #78 uses to automate the quarterly
22
+ bumps, does the same.
23
+
24
+ ## Options considered
25
+
26
+ - **Per release.** Floors can land on a patch release. This is not what SPEC 0
27
+ or spec0-action computes, so the automated bumps (#78) would disagree with
28
+ the floors set by hand.
29
+ - **Per minor version.** Floors move in bigger steps (igraph skips from
30
+ 0.11.x straight to 1.0), but they match SPEC 0 and the tool that maintains
31
+ them.
32
+
33
+ ## Decision
34
+
35
+ Every dependency floor under ADR 0001 decisions 2 and 3, for core and other
36
+ runtime dependencies alike, is the oldest minor version whose x.y.0 release is
37
+ within the 2-year window, written `>=x.y`. Floors are never set to a patch
38
+ release. Python versions are already counted per minor version (ADR 0001
39
+ decision 1).
40
+
41
+ ## Consequences
42
+
43
+ - #77 sets `igraph>=1.0`, not `>=0.11.8`. SymPy is `>=1.14` under either
44
+ reading.
45
+ - #78's spec0-action configuration must use this rule. Its default already does.
46
+ - ADR 0001 is otherwise unchanged.
@@ -0,0 +1,55 @@
1
+ # 0003. Don't test free-threaded Python builds in CI for now
2
+
3
+ - **Status:** Proposed
4
+ - **Date:** 2026-10-11
5
+ - **Issues:** #75, #79
6
+ - **Supersedes:** decision 6, and the free-threaded part of decision 5, of
7
+ [ADR 0001](0001-spec-0-support-policy.md)
8
+
9
+ ## Context
10
+
11
+ [ADR 0001](0001-spec-0-support-policy.md) decision 6 put free-threaded builds
12
+ (3.14t, 3.15t) in non-blocking CI jobs as an early warning. #75 added those
13
+ jobs. On their first runs (PR #88), neither job got as far as running a test:
14
+
15
+ - igraph 1.0.0, a runtime dependency, publishes only limited-API (`abi3`)
16
+ wheels. Free-threaded CPython rejects the limited API
17
+ (`#error "The limited API is not currently supported in the free-threaded
18
+ build"`), so there is no wheel to install and the source build fails.
19
+ - NumPy, SciPy, Matplotlib and Pillow already ship free-threaded wheels. igraph
20
+ is the only blocker.
21
+
22
+ We can't fix this. Until igraph ships free-threaded wheels, these jobs fail at
23
+ `uv sync` on every PR. They test none of our code, and a permanent red ✗ teaches
24
+ everyone to ignore red marks.
25
+
26
+ We don't claim free-threading support, and the ensembles in #49 parallelize just
27
+ as well with a process pool on any Python. Nothing we ship depends on free
28
+ threading.
29
+
30
+ ## Options considered
31
+
32
+ - **A. Keep the jobs, non-blocking (ADR 0001 decision 6).** They give no signal
33
+ until igraph changes, and every PR shows red marks until then. Each new
34
+ free-threaded version would then repeat the wait for dependency wheels.
35
+ - **B. Remove the jobs, and revisit once igraph ships free-threaded wheels.**
36
+ No noise, and no loss of signal, because the jobs test nothing today.
37
+ - **C. Build igraph from source in CI without the limited API.** That would
38
+ mean maintaining a build of a C extension we don't own, for a feature we
39
+ don't claim.
40
+
41
+ ## Decision
42
+
43
+ 1. CI runs no free-threaded (`t`) Python builds. This supersedes ADR 0001
44
+ decision 6.
45
+ 2. We still claim no free-threading support: no classifier, and no
46
+ thread-safety guarantee.
47
+ 3. Revisit once igraph publishes free-threaded wheels (`cp3XXt`) on PyPI. Then a
48
+ new ADR can restore non-blocking free-threaded jobs.
49
+
50
+ ## Consequences
51
+
52
+ - `ci.yml` drops the 3.14t and 3.15t jobs (#75).
53
+ - #79 covers only the normal 3.15 build. 3.15t is no longer in CI.
54
+ - ADR 0001 decision 5's "and its free-threaded build" no longer applies to new
55
+ Python versions.
@@ -0,0 +1,60 @@
1
+ # 0004. Read the Docs runs the test suite once and publishes failures
2
+
3
+ - **Status:** Proposed
4
+ - **Date:** 2026-10-11
5
+ - **Issues:** #31
6
+
7
+ ## Context
8
+
9
+ The Testing page (`docs/testing.rst`) includes `docs/test_results.txt` and
10
+ `docs/coverage_report.md`, and links to the HTML coverage report. These files
11
+ are gitignored. Read the Docs (RTD) generates them in its `pre_build` step.
12
+
13
+ That step ran the suite twice. The first run aborted the build on any failure,
14
+ so the `|| true` on the second run, which wrote `test_results.txt`, never took
15
+ effect. The page could only ever show green, and each docs build took twice
16
+ as long as it needed to. `pytest.ini`'s `addopts` already add coverage, the HTML
17
+ report and doctests to every run, so one run produces everything the page needs.
18
+
19
+ CI already runs the full suite, and its `test` check is required to merge into
20
+ `main`.
21
+
22
+ ## Options considered
23
+
24
+ - **A. Move report generation into CI, and have RTD only build Sphinx.** RTD
25
+ can't download GitHub Actions artifacts without a token, and those artifacts
26
+ expire. RTD builds start on push, often before CI finishes, so the reports
27
+ would lag a commit behind. Too much machinery for the real problem, which is
28
+ the doubled run time.
29
+ - **B. Gate: run once, and a failing test aborts the docs build.** A build that
30
+ aborts leaves RTD serving the last successful build. If broken code reached
31
+ `main`, the docs would keep showing stale green results.
32
+ - **C. Report: run once, and a failing test doesn't abort the build.** The docs
33
+ always match `main`. Normally `main` is green, because CI blocks broken
34
+ merges, so the page is green. If broken code gets in anyway, the page shows it.
35
+
36
+ ## Decision
37
+
38
+ Option C, with tests that fail to load still aborting the build (maintainer,
39
+ on #31 and in the PR #98 session):
40
+
41
+ 1. RTD keeps generating the test and coverage reports. They don't move to CI.
42
+ 2. `pre_build` runs pytest once. Its output goes to `docs/test_results.txt`,
43
+ and the coverage reports (`coverage_report.md`, `coverage.json`, `htmlcov/`)
44
+ come from that same run's data.
45
+ 3. Failing tests (pytest exit code 1) don't abort the docs build. The failures
46
+ appear on the Testing page.
47
+ 4. If the suite doesn't run properly (exit code 2 or higher: tests that fail to
48
+ load, no tests collected, or a pytest error), the docs build aborts. The
49
+ same failure already fails CI and blocks the merge. In that case RTD keeps
50
+ serving the last good build, rather than publishing a page with no results
51
+ and no coverage.
52
+ 5. CI's required `test` check, not the docs build, is the merge gate.
53
+
54
+ ## Consequences
55
+
56
+ - `.readthedocs.yaml` writes the single pytest run to `test_results.txt`, echoes
57
+ it to the build log, and aborts only when pytest's exit code is 2 or higher
58
+ (#31).
59
+ - A red Testing page on `main` means broken code got past CI: treat it as a bug.
60
+ - A green docs build no longer means the tests passed. Check CI for that.
@@ -0,0 +1,59 @@
1
+ # 0005. Security fixes go into the latest release only
2
+
3
+ - **Status:** Proposed
4
+ - **Date:** 2026-10-11
5
+ - **Issues:** #70, #76, #83
6
+
7
+ ## Context
8
+
9
+ `SECURITY.md` (#70) has to say which versions get security fixes. GitHub shows
10
+ the policy under the repository's Security tab, the OpenSSF Scorecard
11
+ Security-Policy check looks for it (#83), and JOSS and pyOpenSci reviewers
12
+ expect one (#64).
13
+
14
+ Vulnerabilities are reported privately through GitHub's private vulnerability
15
+ reporting (#83). A fix is coordinated in a GitHub security advisory and
16
+ published with a release.
17
+
18
+ The project has one maintainer, and releases are cut from `main`. There are no
19
+ maintenance branches.
20
+
21
+ [ADR 0001](0001-spec-0-support-policy.md) has the project follow SPEC 0. Each
22
+ release drops the Python and dependency versions that have left the SPEC 0
23
+ window. Installers honor `Requires-Python`, so users on a dropped Python keep
24
+ getting the last release that supported it. ADR 0001 covers which versions a
25
+ *release* supports. It doesn't say which *releases* get fixes.
26
+
27
+ ## Options considered
28
+
29
+ - **A. Latest release only.** Fixes ship in a new release from `main`, and
30
+ nothing is backported. This is the least work, and it's consistent with
31
+ ADR 0001: old releases are for old environments, not maintained lines. Users on a Python
32
+ that SPEC 0 has dropped don't get the fix.
33
+ - **B. The latest release, plus backports to the previous minor series.** This
34
+ needs maintenance branches, a release process for each one, and CI for
35
+ dependency floors that `main` no longer tests. One maintainer can't keep
36
+ that up.
37
+ - **C. The last release for every Python version still supported by CPython.**
38
+ This is the most generous option. It brings back the old-floor maintenance
39
+ that ADR 0001 ruled out, and the dependencies themselves stop releasing for
40
+ those versions.
41
+
42
+ ## Decision
43
+
44
+ 1. Security fixes are made **only in a new release** cut from `main`. Earlier
45
+ releases get no fixes, and nothing is backported.
46
+ 2. That release follows ADR 0001, so it supports only the Python and
47
+ dependency versions in the SPEC 0 window when it is made.
48
+ 3. The advisory published with the fix names the affected versions and the
49
+ fixed version, so users on older releases can see that they are affected.
50
+ 4. `SECURITY.md` states this policy, and it cites this ADR.
51
+
52
+ ## Consequences
53
+
54
+ - To get a fix, users upgrade to the latest release. A user on a Python that
55
+ SPEC 0 has dropped has to upgrade Python first.
56
+ - No maintenance branches or backport process are needed.
57
+ - Changing this policy, for example to backport to the previous minor series
58
+ once the project has more maintainers, takes a new ADR that supersedes this
59
+ one.
@@ -0,0 +1,126 @@
1
+ # 0006. AI provenance: what is tracked, how, and where it is published
2
+
3
+ - **Status:** Proposed
4
+ - **Date:** 2026-10-11
5
+ - **Issues:** #81, #80, #72
6
+
7
+ ## Context
8
+
9
+ JOSS and pyOpenSci both require a disclosure of AI use (#80), and the
10
+ maintainer wants to see where human effort goes: into prompting and into code.
11
+ Most of the evidence can't be reconstructed later. Commits carry no AI trailers,
12
+ squash merges collapse branch history, and Claude Code deletes session
13
+ transcripts after `cleanupPeriodDays` (30 days by default).
14
+
15
+ #81 left five decisions to the maintainer. They were settled in the issue
16
+ (the maintainer's comment of 2026-10-11, US time) and in the `/work-issue 81` session:
17
+
18
+ - **(a) Tool.** git-ai would work, but the goal is to see where human effort
19
+ went, not attribution alone. Custom scripts only if git-ai can't do it.
20
+ - **(b) Formal approvals.** No bot identity. Commits and PRs stay under the
21
+ maintainer's identity, and the maintainer reviews and merges every PR.
22
+ - **(c) Merge strategy.** Keep squash merges. The PR process doesn't change.
23
+ - **(d) What to publish.** Aggregates and per-PR numbers. No transcripts.
24
+ - **(e) Retention.** The 30-day default is fine.
25
+
26
+ ## Options considered
27
+
28
+ - **git-ai** (open source, Apache-2.0) records line-level AI authorship in git
29
+ notes through agent hooks. It needs a background daemon and user-level hooks
30
+ installed by `curl | bash`. Keeping notes through GitHub's squash merge needs a
31
+ CI workflow that installs git-ai the same way and pushes with a
32
+ `contents: write` token. That cuts against the workflow hardening of #97, and
33
+ git-ai doesn't measure prompting effort at all.
34
+ - **A per-PR count from transcripts and commits.** One stdlib-only script reads
35
+ the transcripts for steering, decides per commit whether an agent made it,
36
+ and blames the PR's added lines. It needs no new tool and no write-token
37
+ workflow beyond the existing pattern, but gives per-PR totals, not per-line
38
+ blame on `main`.
39
+
40
+ The maintainer chose the per-PR count.
41
+
42
+ ## Decision
43
+
44
+ `scripts/ai_provenance.py` records, for each PR, one comment that starts with
45
+ `<!-- ai-provenance:v1 -->` and holds a JSON record. It is computed on the
46
+ maintainer's machine, where the transcripts are. `/work-issue` step 8 runs it,
47
+ and a `SessionEnd` hook in `.claude/settings.json` runs it again in the
48
+ background when a session closes.
49
+
50
+ **Sessions → PRs.** A session counts for a PR if it created the PR (a `pr-link`
51
+ record), worked on its head branch, or is named in the PR body. Within a
52
+ session, a transcript record counts for the PR whose head branch it was on.
53
+ Otherwise it counts for the first PR the session linked at or after it, or the
54
+ last one if it came after all of them.
55
+
56
+ **Human steering**, per PR:
57
+
58
+ - *messages*: user records with `origin.kind == "human"`, excluding interrupts. A
59
+ slash command counts as the `/name args` that was typed.
60
+ - *words* and *characters* of those messages, plus words typed into
61
+ AskUserQuestion answers. Picking an offered option counts as an *answer*, not
62
+ as words.
63
+ - *active time*: for each message, the time since Claude's previous output; for
64
+ each answer, the time since the question. Each is capped at **15 minutes**, so
65
+ breaks don't count. A session's first message counts zero.
66
+ - *wall clock*: the span of the session's records that count for the PR.
67
+ - *messages after approval*: messages after the first reviewer hand-back with
68
+ verdict APPROVE.
69
+
70
+ **Agents**, per PR: models, effort levels and tokens (input including cache
71
+ reads and writes; output), deduplicated per API response, from the main and
72
+ subagent transcripts; agent time as the length of the union of the intervals
73
+ `[t − durationMs, t]` over the `turn_duration` records (within one prompt the
74
+ value is sometimes cumulative and sometimes per turn, and the union is right in
75
+ both cases); reviewer verdicts.
76
+
77
+ **Line authorship.** Each commit in `merge-base..head` is *ai* if an agent's
78
+ Bash output showed git's `[branch sha]` summary for it, or if its **author
79
+ date** falls within one of the agents' Bash calls (±2 s). The author date
80
+ survives rebases such as GitHub's "Update branch". Every other commit is
81
+ *human*. Each non-blank line the PR adds is blamed over that range. A line from
82
+ an *ai* commit is `ai`. A line from a *human* commit is `ai_edited` if that
83
+ commit's hunk replaced lines an *ai* commit had added, and otherwise `human`.
84
+ Lines are split into `src/`, `tests/`, `docs/` and other. The **coverage rule**
85
+ follows: whatever no transcript shows an agent doing counts as human, so
86
+ AI-written code must enter only through Claude Code (already a `CLAUDE.md`
87
+ rule).
88
+
89
+ Re-running keeps what an earlier run recorded: sessions whose transcripts have
90
+ expired, and commits already classified *ai*.
91
+
92
+ **Publishing.** The `Stats` workflow (on push to `main`, nightly, and on
93
+ demand) reads every merged PR's provenance comment, plus GitHub's data: reviewer
94
+ rounds and verdicts from the `Automated review (round N)` comments, formal
95
+ reviews, line comments, commits, time open, and which closed `difficulty: max`
96
+ issues human-written PRs closed. It writes `ai-stats.json` to the orphan `stats`
97
+ branch, the same pattern as the coverage badge. A PR whose final head differs
98
+ from the head its record was computed at is flagged `stale`. Each row also
99
+ counts the PR's commits by class, so human commits pushed to agent PRs are
100
+ published. Only comments by the repository owner (`authorAssociation`
101
+ `OWNER`) are read, and a malformed record is ignored, so other people's
102
+ comments can't forge or break the numbers. The Read the Docs
103
+ build renders the JSON on the *AI provenance* page. Nothing from the transcripts
104
+ is published except these numbers.
105
+
106
+ **Test strength.** mutmut runs nightly in the same workflow on
107
+ `src/treegraphduals/core` and `timeseries` (plotting code is excluded). The
108
+ score, (killed + timeout + segfault) / (those + survived + suspicious +
109
+ no tests), is published with the stats. Deterministic Hypothesis in CI stays
110
+ with #51.
111
+
112
+ ## Consequences
113
+
114
+ - Every agent PR gets a provenance comment. Human PRs without one count as
115
+ human when their AI use section says "None: written by a human".
116
+ - PRs merged before this lands can be backfilled with `collect <pr>` while their
117
+ transcripts still exist (30 days).
118
+ - Known gaps: a human edit that an agent commits along with its own, a human
119
+ amending an agent's commit, or a human commit authored while an agent's Bash
120
+ call (such as a long test run) was running in a session on the same PR, counts
121
+ as AI. Code pasted from other AI chats
122
+ counts as human. Effort in sessions that never touch a PR (planning, triage)
123
+ isn't attributed to any PR.
124
+ - Formal approval records stay impossible (decision b). The review metrics are
125
+ the reviewer agent's rounds and the maintainer's messages after its approval.
126
+ - #72's page builds on the generated statistics and this ADR.