lost-sessions 0.0.0.dev0__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 (28) hide show
  1. lost_sessions-0.2.0/PKG-INFO +92 -0
  2. lost_sessions-0.2.0/README.md +66 -0
  3. lost_sessions-0.2.0/pyproject.toml +236 -0
  4. lost_sessions-0.2.0/pyproject.toml.orig +300 -0
  5. lost_sessions-0.2.0/src/lost_sessions/__init__.py +51 -0
  6. lost_sessions-0.2.0/src/lost_sessions/__main__.py +5 -0
  7. lost_sessions-0.2.0/src/lost_sessions/agents.py +147 -0
  8. lost_sessions-0.2.0/src/lost_sessions/cli.py +595 -0
  9. lost_sessions-0.2.0/src/lost_sessions/core.py +526 -0
  10. lost_sessions-0.2.0/src/lost_sessions/default_plugins/__init__.py +1 -0
  11. lost_sessions-0.2.0/src/lost_sessions/default_plugins/claude.py +137 -0
  12. lost_sessions-0.2.0/src/lost_sessions/default_plugins/codex.py +157 -0
  13. lost_sessions-0.2.0/src/lost_sessions/default_plugins/herdr.py +362 -0
  14. lost_sessions-0.2.0/src/lost_sessions/default_plugins/muse.py +146 -0
  15. lost_sessions-0.2.0/src/lost_sessions/default_plugins/opencode.py +190 -0
  16. lost_sessions-0.2.0/src/lost_sessions/default_plugins/rex.py +278 -0
  17. lost_sessions-0.2.0/src/lost_sessions/history.py +268 -0
  18. lost_sessions-0.2.0/src/lost_sessions/hookspecs.py +27 -0
  19. lost_sessions-0.2.0/src/lost_sessions/installer.py +179 -0
  20. lost_sessions-0.2.0/src/lost_sessions/inventory.py +117 -0
  21. lost_sessions-0.2.0/src/lost_sessions/managers.py +232 -0
  22. lost_sessions-0.2.0/src/lost_sessions/menu.py +375 -0
  23. lost_sessions-0.2.0/src/lost_sessions/models.py +81 -0
  24. lost_sessions-0.2.0/src/lost_sessions/plugins.py +150 -0
  25. lost_sessions-0.0.0.dev0/PKG-INFO +0 -10
  26. lost_sessions-0.0.0.dev0/README.md +0 -3
  27. lost_sessions-0.0.0.dev0/pyproject.toml +0 -10
  28. /lost_sessions-0.0.0.dev0/src/lost_sessions/__init__.py → /lost_sessions-0.2.0/src/lost_sessions/py.typed +0 -0
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: lost_sessions
3
+ Version: 0.2.0
4
+ Summary: Recover agent sessions that a reboot dropped, with pluggable session managers
5
+ Author: Steve Morin
6
+ Author-email: Steve Morin <steve.morin@gmail.com>
7
+ License-Expression: MIT
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Requires-Dist: click>=8.1.0
16
+ Requires-Dist: pluggy>=1.5.0
17
+ Requires-Dist: pydantic>=2.0.0
18
+ Requires-Dist: textual>=8,<9
19
+ Requires-Python: >=3.13
20
+ Project-URL: Homepage, https://github.com/smorinlabs/lost-sessions
21
+ Project-URL: Repository, https://github.com/smorinlabs/lost-sessions
22
+ Project-URL: Documentation, https://lost-sessions.readthedocs.io/en/latest/
23
+ Project-URL: Changelog, https://github.com/smorinlabs/lost-sessions/blob/main/CHANGELOG.md
24
+ Project-URL: Issues, https://github.com/smorinlabs/lost-sessions/issues
25
+ Description-Content-Type: text/markdown
26
+
27
+ # lost-sessions
28
+
29
+ Recover the agent sessions a reboot dropped. `lost-sessions` finds Claude Code,
30
+ Codex, Muse and OpenCode sessions that started before the last boot and that no
31
+ session manager will restore, then prints their resume commands or reopens
32
+ them in Herdr or Rex.
33
+
34
+ Agents and session managers are plugins, the way
35
+ [`simonw/llm`](https://llm.datasette.io/en/stable/plugins/) does plugins. The
36
+ six built-ins use the same interface a third-party `lost-sessions-<name>`
37
+ package uses.
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ uv tool install "lost-sessions @ git+https://github.com/smorinlabs/lost-sessions"
43
+ ```
44
+
45
+ This installs the latest `main`. Python 3.13 or later. macOS and Linux. Tagged
46
+ releases, and a PyPI package, follow once publishing is set up.
47
+
48
+ ## Use
49
+
50
+ | Command | What it does |
51
+ |---|---|
52
+ | `lost-sessions recover` | Interactive menu: choose Print, Rex or Herdr for each dropped session, then run them all |
53
+ | `lost-sessions list` | Numbered list of the dropped sessions in the window |
54
+ | `lost-sessions open REF… --print\|--rex\|--herdr [--focus [REF]]` | Print or open sessions without the menu; `REF@DEST` picks a destination |
55
+ | `lost-sessions workspaces` | The Herdr workspaces and Rex sessions you can open sessions in |
56
+ | `lost-sessions history [--json]` | Every action taken, from `${XDG_STATE_HOME:-~/.local/state}/lost-sessions/history.jsonl` |
57
+ | `lost-sessions inventory [--json]` | Read-only inventory of every agent session on this machine |
58
+ | `lost-sessions plugins [--all]` | Installed plugins, as JSON |
59
+ | `lost-sessions install PKG…` / `uninstall PKG…` | Add or remove plugins in the `uv tool` install |
60
+
61
+ The window is every session that started before the last boot and was active
62
+ since its start: by default 00:00 on the day before the boot. `--since` sets
63
+ that start, as a date (`--since 2026-11-09`) or a duration back from now
64
+ (`--since 3d`); pick an earlier start to widen the window. Running sessions
65
+ and sessions a manager restores are hidden unless `--all`.
66
+
67
+ ## Library
68
+
69
+ ```python
70
+ from lost_sessions.inventory import collect, select
71
+
72
+ inventory = collect() # an Inventory: sessions, census, sources, failed
73
+ for session in select(inventory, live=True):
74
+ print(session.tool, session.id, session.resume)
75
+ ```
76
+
77
+ ## Plugins
78
+
79
+ See [Writing plugins](docs/source/reference/plugins.md) for the hooks, the
80
+ `AgentTool` and `SessionManager` contracts, and how to test and install a
81
+ plugin. The design is in
82
+ [design 0006](docs/design/0006-lost-sessions-plugin-design.md).
83
+
84
+ ## Develop
85
+
86
+ ```bash
87
+ make bootstrap # bare machines only: just + uv
88
+ just setup # dev environment and git hooks
89
+ just check # tests, lint, types, import contracts
90
+ ```
91
+
92
+ See [AGENTS.md](AGENTS.md) for the full workflow.
@@ -0,0 +1,66 @@
1
+ # lost-sessions
2
+
3
+ Recover the agent sessions a reboot dropped. `lost-sessions` finds Claude Code,
4
+ Codex, Muse and OpenCode sessions that started before the last boot and that no
5
+ session manager will restore, then prints their resume commands or reopens
6
+ them in Herdr or Rex.
7
+
8
+ Agents and session managers are plugins, the way
9
+ [`simonw/llm`](https://llm.datasette.io/en/stable/plugins/) does plugins. The
10
+ six built-ins use the same interface a third-party `lost-sessions-<name>`
11
+ package uses.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ uv tool install "lost-sessions @ git+https://github.com/smorinlabs/lost-sessions"
17
+ ```
18
+
19
+ This installs the latest `main`. Python 3.13 or later. macOS and Linux. Tagged
20
+ releases, and a PyPI package, follow once publishing is set up.
21
+
22
+ ## Use
23
+
24
+ | Command | What it does |
25
+ |---|---|
26
+ | `lost-sessions recover` | Interactive menu: choose Print, Rex or Herdr for each dropped session, then run them all |
27
+ | `lost-sessions list` | Numbered list of the dropped sessions in the window |
28
+ | `lost-sessions open REF… --print\|--rex\|--herdr [--focus [REF]]` | Print or open sessions without the menu; `REF@DEST` picks a destination |
29
+ | `lost-sessions workspaces` | The Herdr workspaces and Rex sessions you can open sessions in |
30
+ | `lost-sessions history [--json]` | Every action taken, from `${XDG_STATE_HOME:-~/.local/state}/lost-sessions/history.jsonl` |
31
+ | `lost-sessions inventory [--json]` | Read-only inventory of every agent session on this machine |
32
+ | `lost-sessions plugins [--all]` | Installed plugins, as JSON |
33
+ | `lost-sessions install PKG…` / `uninstall PKG…` | Add or remove plugins in the `uv tool` install |
34
+
35
+ The window is every session that started before the last boot and was active
36
+ since its start: by default 00:00 on the day before the boot. `--since` sets
37
+ that start, as a date (`--since 2026-11-09`) or a duration back from now
38
+ (`--since 3d`); pick an earlier start to widen the window. Running sessions
39
+ and sessions a manager restores are hidden unless `--all`.
40
+
41
+ ## Library
42
+
43
+ ```python
44
+ from lost_sessions.inventory import collect, select
45
+
46
+ inventory = collect() # an Inventory: sessions, census, sources, failed
47
+ for session in select(inventory, live=True):
48
+ print(session.tool, session.id, session.resume)
49
+ ```
50
+
51
+ ## Plugins
52
+
53
+ See [Writing plugins](docs/source/reference/plugins.md) for the hooks, the
54
+ `AgentTool` and `SessionManager` contracts, and how to test and install a
55
+ plugin. The design is in
56
+ [design 0006](docs/design/0006-lost-sessions-plugin-design.md).
57
+
58
+ ## Develop
59
+
60
+ ```bash
61
+ make bootstrap # bare machines only: just + uv
62
+ just setup # dev environment and git hooks
63
+ just check # tests, lint, types, import contracts
64
+ ```
65
+
66
+ See [AGENTS.md](AGENTS.md) for the full workflow.
@@ -0,0 +1,236 @@
1
+ [project]
2
+ name = "lost_sessions"
3
+ version = "0.2.0"
4
+ description = "Recover agent sessions that a reboot dropped, with pluggable session managers"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ license = "MIT"
8
+ classifiers = [
9
+ "Development Status :: 4 - Beta",
10
+ "Intended Audience :: Developers",
11
+ "Operating System :: OS Independent",
12
+ "Programming Language :: Python :: 3",
13
+ "Programming Language :: Python :: 3.13",
14
+ "Programming Language :: Python :: 3 :: Only",
15
+ "Topic :: Software Development :: Libraries :: Python Modules",
16
+ ]
17
+ dependencies = [
18
+ "click>=8.1.0",
19
+ "pluggy>=1.5.0",
20
+ "pydantic>=2.0.0",
21
+ "textual>=8,<9",
22
+ ]
23
+
24
+ [[project.authors]]
25
+ name = "Steve Morin"
26
+ email = "steve.morin@gmail.com"
27
+
28
+ [project.scripts]
29
+ lost-sessions = "lost_sessions.cli:main"
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/smorinlabs/lost-sessions"
33
+ Repository = "https://github.com/smorinlabs/lost-sessions"
34
+ Documentation = "https://lost-sessions.readthedocs.io/en/latest/"
35
+ Changelog = "https://github.com/smorinlabs/lost-sessions/blob/main/CHANGELOG.md"
36
+ Issues = "https://github.com/smorinlabs/lost-sessions/issues"
37
+
38
+ [dependency-groups]
39
+ dev = [
40
+ "pytest>=7.0.0",
41
+ "pytest-cov>=4.1.0",
42
+ "ty>=0.0.1a16",
43
+ "ruff>=0.1.0",
44
+ "types-Pygments",
45
+ "cogapp",
46
+ "bandit>=1.8.0",
47
+ "codespell>=2.4.0",
48
+ "editorconfig-checker>=3.0.0",
49
+ "yamllint>=1.35.0",
50
+ "pip-audit>=2.9.0",
51
+ "twine>=6.1.0",
52
+ "syrupy>=4.8.0",
53
+ "import-linter>=2.0",
54
+ "pytest-xdist>=3.6",
55
+ "pytest-timeout>=2.3",
56
+ "pytest-randomly>=3.15",
57
+ "hypothesis>=6.100",
58
+ ]
59
+ docs = [
60
+ "sphinx>=7.0.0",
61
+ "sphinx-rtd-theme",
62
+ "furo",
63
+ "myst-parser",
64
+ "sphinx-autodoc-typehints",
65
+ "cogapp",
66
+ "sphinx-copybutton",
67
+ "sphinx-autobuild",
68
+ "sphinxext-opengraph",
69
+ ]
70
+
71
+ [build-system]
72
+ requires = ["uv_build>=0.5,<1.0"]
73
+ build-backend = "uv_build"
74
+
75
+ [tool.uv.build-backend]
76
+ module-name = "lost_sessions"
77
+ module-root = "src"
78
+
79
+ [tool.ruff]
80
+ target-version = "py313"
81
+ line-length = 88
82
+ fix = true
83
+ exclude = [
84
+ ".git",
85
+ ".venv",
86
+ "__pycache__",
87
+ "build",
88
+ "dist",
89
+ "docs/source/conf.py",
90
+ "src/lost_sessions/_version.py",
91
+ "prototypes",
92
+ ]
93
+
94
+ [tool.ruff.lint]
95
+ select = [
96
+ "E",
97
+ "F",
98
+ "I",
99
+ "B",
100
+ "C4",
101
+ "UP",
102
+ "N",
103
+ "RUF",
104
+ "W",
105
+ "YTT",
106
+ "S",
107
+ "ANN",
108
+ ]
109
+ ignore = [
110
+ "E501",
111
+ "W293",
112
+ ]
113
+ unfixable = []
114
+
115
+ [tool.ruff.lint.pycodestyle]
116
+ max-line-length = 88
117
+
118
+ [tool.ruff.lint.per-file-ignores]
119
+ "__init__.py" = ["F401"]
120
+ "tests/*" = [
121
+ "S101",
122
+ "S105",
123
+ "S106",
124
+ "S603",
125
+ "S607",
126
+ "ANN",
127
+ "F811",
128
+ ]
129
+
130
+ [tool.ruff.format]
131
+ quote-style = "double"
132
+ indent-style = "space"
133
+ line-ending = "auto"
134
+
135
+ [tool.pyright]
136
+ include = [
137
+ "src/lost_sessions",
138
+ "tests",
139
+ ]
140
+ exclude = [
141
+ "**/node_modules",
142
+ "**/__pycache__",
143
+ ]
144
+ typeCheckingMode = "strict"
145
+ pythonVersion = "3.13"
146
+ useLibraryCodeForTypes = true
147
+ reportMissingImports = true
148
+ reportMissingTypeStubs = true
149
+ reportUnknownParameterType = true
150
+ reportUnknownArgumentType = true
151
+ reportUnknownLambdaType = true
152
+ reportUnknownVariableType = true
153
+ reportUnknownMemberType = true
154
+ reportMissingTypeArgument = true
155
+ reportInvalidTypeVarUse = true
156
+ reportUnnecessaryIsInstance = true
157
+ reportUnnecessaryCast = true
158
+ reportUnnecessaryComparison = true
159
+ reportDuplicateImport = true
160
+ reportPrivateUsage = "warning"
161
+ reportUnusedImport = true
162
+ reportUnusedVariable = true
163
+ reportUnusedFunction = true
164
+ reportUnusedClass = true
165
+
166
+ [tool.pyright.defineConstant]
167
+ DEBUG = true
168
+
169
+ [tool.ty.environment]
170
+ python-version = "3.13"
171
+
172
+ [tool.ty.rules]
173
+ ambiguous-protocol-member = "error"
174
+ blanket-ignore-comment = "error"
175
+ invalid-ignore-comment = "error"
176
+ ignore-comment-unknown-rule = "error"
177
+ ineffective-final = "error"
178
+ invalid-enum-member-annotation = "error"
179
+ invalid-named-tuple-override = "error"
180
+
181
+ [tool.ty.terminal]
182
+ error-on-warning = false
183
+
184
+ [tool.bandit]
185
+ exclude_dirs = [
186
+ "tests",
187
+ ".venv",
188
+ "build",
189
+ "dist",
190
+ ]
191
+ skips = []
192
+
193
+ [tool.pytest.ini_options]
194
+ testpaths = ["tests"]
195
+ python_files = ["test_*.py"]
196
+ markers = [
197
+ "live: requires external services",
198
+ "slow: takes >1s",
199
+ ]
200
+ addopts = "-m 'not live and not slow'"
201
+ strict_markers = true
202
+ timeout = 60
203
+ timeout_method = "thread"
204
+
205
+ [tool.coverage.run]
206
+ branch = true
207
+
208
+ [tool.codespell]
209
+ skip = ".git,.venv,build,dist,node_modules,uv.lock,bun.lock,*.lock,docs/_build,*.svg,*.cast,.ruff_cache,.pytest_cache,.mypy_cache,.ty_cache,*.log"
210
+ ignore-words-list = "ans,porjects,unparseable,wit"
211
+ check-filenames = true
212
+ check-hidden = false
213
+
214
+ [tool.importlinter]
215
+ root_package = "lost_sessions"
216
+ include_external_packages = true
217
+
218
+ [[tool.importlinter.contracts]]
219
+ name = "lost-sessions is a CLI: no web framework imports"
220
+ type = "forbidden"
221
+ source_modules = ["lost_sessions"]
222
+ forbidden_modules = [
223
+ "fastapi",
224
+ "starlette",
225
+ "uvicorn",
226
+ ]
227
+
228
+ [[tool.importlinter.contracts]]
229
+ name = "core reaches built-in plugins only through the plugin manager"
230
+ type = "forbidden"
231
+ source_modules = [
232
+ "lost_sessions.cli",
233
+ "lost_sessions.hookspecs",
234
+ "lost_sessions.plugins",
235
+ ]
236
+ forbidden_modules = ["lost_sessions.default_plugins"]
@@ -0,0 +1,300 @@
1
+ # Copyright (c) 2026, Steve Morin
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ # this software and associated documentation files (the "Software"), to deal in
5
+ # the Software without restriction, including without limitation the rights to
6
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ # the Software, and to permit persons to whom the Software is furnished to do so,
8
+ # subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
20
+ [project]
21
+ name = "lost_sessions"
22
+ # ITM-070 — static version (ADR-06 cutover). release-please bumps this.
23
+ version = "0.2.0"
24
+ description = "Recover agent sessions that a reboot dropped, with pluggable session managers"
25
+ readme = "README.md"
26
+ requires-python = ">=3.13"
27
+ license = "MIT" # PEP 639 license expression (ITM-070, round-19).
28
+ authors = [{ name = "Steve Morin", email = "steve.morin@gmail.com" }]
29
+ classifiers = [
30
+ # ITM-070 — round-19. py3.13+ only (per ITM-033 floor); MIT classifier
31
+ # intentionally omitted (PEP 639 license expression above is the single
32
+ # source of truth — duplication would drift).
33
+ "Development Status :: 4 - Beta",
34
+ "Intended Audience :: Developers",
35
+ "Operating System :: OS Independent",
36
+ "Programming Language :: Python :: 3",
37
+ "Programming Language :: Python :: 3.13",
38
+ "Programming Language :: Python :: 3 :: Only",
39
+ "Topic :: Software Development :: Libraries :: Python Modules",
40
+ ]
41
+ dependencies = [
42
+ "click>=8.1.0",
43
+ "pluggy>=1.5.0", # plugin manager, as in simonw/llm (design Section 4)
44
+ "pydantic>=2.0.0", # every data object is a Pydantic model (design Q-G2)
45
+ "textual>=8,<9", # the `recover` menu; imported lazily
46
+ ]
47
+
48
+ # ITM-063 — PEP 735 dependency-groups (round-13 decision). Replaces
49
+ # [project.optional-dependencies] dev+docs. Install via `uv sync --group <name>`.
50
+ [dependency-groups]
51
+ dev = [
52
+ "pytest>=7.0.0",
53
+ "pytest-cov>=4.1.0",
54
+ "ty>=0.0.1a16", # per ADR-03 (pre-1.0; bump as needed)
55
+ "ruff>=0.1.0",
56
+ "types-Pygments",
57
+ "cogapp",
58
+ # WL-001 — hook/CI tools pinned via uv.lock (run with `uv run`, not floating
59
+ # `uvx`), so local hooks and CI use the same version and upgrades arrive as
60
+ # reviewable dependabot PRs instead of surprise breakage.
61
+ "bandit>=1.8.0",
62
+ "codespell>=2.4.0",
63
+ "editorconfig-checker>=3.0.0",
64
+ "yamllint>=1.35.0",
65
+ "pip-audit>=2.9.0", # WL-014 — `just audit` + scheduled dep-audit workflow
66
+ "twine>=6.1.0", # WL-004 — dist metadata check in the build-smoke CI job
67
+ "syrupy>=4.8.0", # WL-023 — CLI --help snapshot tests
68
+ "import-linter>=2.0", # HEX-30 — architectural layer/boundary rules
69
+ # Testing-strategy shortlist (ADR-0019).
70
+ "pytest-xdist>=3.6", # WL-008 — parallel test execution (`-n auto`)
71
+ "pytest-timeout>=2.3", # WL-009 — per-test timeout guard (hung test fails loud)
72
+ "pytest-randomly>=3.15", # WL-010 — randomized order surfaces inter-test coupling
73
+ "hypothesis>=6.100", # WL-013 — property-based tests at parse/serialize seams
74
+ ]
75
+
76
+ docs = [
77
+ "sphinx>=7.0.0",
78
+ "sphinx-rtd-theme",
79
+ "furo",
80
+ "myst-parser",
81
+ "sphinx-autodoc-typehints",
82
+ "cogapp",
83
+ "sphinx-copybutton",
84
+ "sphinx-autobuild",
85
+ "sphinxext-opengraph", # https://github.com/wpilibsuite/sphinxext-opengraph
86
+ ]
87
+
88
+
89
+ [project.scripts]
90
+ lost-sessions = "lost_sessions.cli:main"
91
+
92
+ # ITM-072 — package metadata URLs (round 19).
93
+ [project.urls]
94
+ Homepage = "https://github.com/smorinlabs/lost-sessions"
95
+ Repository = "https://github.com/smorinlabs/lost-sessions"
96
+ Documentation = "https://lost-sessions.readthedocs.io/en/latest/"
97
+ Changelog = "https://github.com/smorinlabs/lost-sessions/blob/main/CHANGELOG.md"
98
+ Issues = "https://github.com/smorinlabs/lost-sessions/issues"
99
+
100
+ # ITM-073 — uv_build (ADR-06 cutover; replaces Hatchling + hatch-vcs).
101
+ [build-system]
102
+ requires = ["uv_build>=0.5,<1.0"]
103
+ build-backend = "uv_build"
104
+
105
+ # ITM-074 — uv_build module configuration.
106
+ # Template uses a flat layout (package at repo root, not src/) — must set
107
+ # module-root = "" so uv_build doesn't expect src/lost_sessions/.
108
+ [tool.uv.build-backend]
109
+ module-name = "lost_sessions"
110
+ # src/ layout (Decision 1) — package now lives at src/lost_sessions/.
111
+ module-root = "src"
112
+
113
+ [tool.ruff]
114
+ # ITM-018 reconciled config (round-2). Merge: template's stricter rule
115
+ # selection + source's ignore rationale. target-version reflects ITM-033
116
+ # Python-floor bump.
117
+ target-version = "py313"
118
+ line-length = 88
119
+
120
+ lint.select = [
121
+ "E", # pycodestyle errors
122
+ "F", # pyflakes
123
+ "I", # isort
124
+ "B", # flake8-bugbear
125
+ "C4", # flake8-comprehensions
126
+ "UP", # pyupgrade
127
+ "N", # pep8-naming
128
+ "RUF", # Ruff-specific rules
129
+ "W", # pycodestyle warnings
130
+ "YTT", # flake8-2020
131
+ "S", # flake8-bandit
132
+ "ANN", # flake8-annotations — enforce annotation *presence* (P03; the gap ty
133
+ # deliberately does NOT check). Scoped to the package via
134
+ # per-file-ignores below; tests/ are exempt.
135
+ ]
136
+
137
+ # Source's ignore rationale (round-2): line-length handled by formatter;
138
+ # template-applicable subset adopted (B904/B007 omitted — source-specific
139
+ # patterns that don't apply here).
140
+ lint.ignore = [
141
+ "E501", # line too long (formatter owns this)
142
+ "W293", # whitespace on blank line (formatter owns trim_trailing)
143
+ ]
144
+ lint.unfixable = [] # Rules that should not be fixed automatically
145
+
146
+ # Allow autofix behavior for specific rules
147
+ fix = true
148
+
149
+ # Exclude files/folders
150
+ exclude = [
151
+ ".git",
152
+ ".venv",
153
+ "__pycache__",
154
+ "build",
155
+ "dist",
156
+ "docs/source/conf.py",
157
+ "src/lost_sessions/_version.py",
158
+ "prototypes", # P0004 template-press engine prototypes (own lint config in template-press)
159
+ ]
160
+ # Per-file-ignores
161
+ [tool.ruff.lint.pycodestyle]
162
+ max-line-length = 88
163
+ [tool.ruff.lint.per-file-ignores]
164
+ "__init__.py" = ["F401"] # Ignore unused imports in __init__.py files
165
+ "tests/*" = [
166
+ "S101",
167
+ "S105",
168
+ "S106",
169
+ "S603",
170
+ "S607",
171
+ "ANN",
172
+ "F811",
173
+ ] # asserts + annotations optional; tests drive the CLI and stand-ins via subprocess;
174
+ # F811: importing a pytest fixture and naming it as a parameter is the normal pattern
175
+ # S101: assert statements removed under -O — security risk if assert is the only gate.
176
+ # S105/S106: hardcoded password strings/variables in code.
177
+
178
+ # ITM-018 — explicit format config (round-2; mirrors source). Values match Ruff
179
+ # defaults but being explicit prevents silent drift if defaults change.
180
+ [tool.ruff.format]
181
+ quote-style = "double"
182
+ indent-style = "space"
183
+ line-ending = "auto"
184
+
185
+ # Pyright/Pylance (IDE) strict type checking — formerly pyrightconfig.json.
186
+ # CI typechecking runs ty (ADR-03); this section drives the VS Code Pyright
187
+ # extension and ad-hoc `pyright` runs.
188
+ [tool.pyright]
189
+ include = ["src/lost_sessions", "tests"]
190
+ exclude = ["**/node_modules", "**/__pycache__"]
191
+ defineConstant = { DEBUG = true }
192
+ typeCheckingMode = "strict"
193
+ pythonVersion = "3.13"
194
+ useLibraryCodeForTypes = true
195
+ reportMissingImports = true
196
+ reportMissingTypeStubs = true
197
+ reportUnknownParameterType = true
198
+ reportUnknownArgumentType = true
199
+ reportUnknownLambdaType = true
200
+ reportUnknownVariableType = true
201
+ reportUnknownMemberType = true
202
+ reportMissingTypeArgument = true
203
+ reportInvalidTypeVarUse = true
204
+ reportUnnecessaryIsInstance = true
205
+ reportUnnecessaryCast = true
206
+ reportUnnecessaryComparison = true
207
+ reportDuplicateImport = true
208
+ reportPrivateUsage = "warning"
209
+ reportUnusedImport = true
210
+ reportUnusedVariable = true
211
+ reportUnusedFunction = true
212
+ reportUnusedClass = true
213
+
214
+ # ty (Astral) is the CI type-check authority (ADR-03). ty is rule-based, not
215
+ # strict-mode-based: its correctness core already defaults to `error`, so this
216
+ # block only promotes the handful of quality rules that ship below `error`.
217
+ # Each was verified against the pinned ty (see uv.lock, 0.0.61): running
218
+ # `ty check --error <rule> src/lost_sessions/` passes for each.
219
+ # `error-on-warning` is set explicitly to false below (not left implicit): a
220
+ # dependency `deprecated` warning must not fail every fork's CI, and pinning the
221
+ # value keeps CI deterministic across ty upgrades (its default is version-
222
+ # sensitive pre-1.0). `strict-literal-narrowing` is left off as an optional
223
+ # stricter knob. See projects/P03-type-precision-uplevel.md.
224
+ [tool.ty.environment]
225
+ python-version = "3.13" # matches requires-python floor; explicit for determinism
226
+
227
+ [tool.ty.rules]
228
+ ambiguous-protocol-member = "error" # core/ports.py exposes Protocol ports
229
+ blanket-ignore-comment = "error" # every `# ty: ignore` must name its rule
230
+ invalid-ignore-comment = "error"
231
+ ignore-comment-unknown-rule = "error"
232
+ ineffective-final = "error"
233
+ invalid-enum-member-annotation = "error"
234
+ invalid-named-tuple-override = "error"
235
+
236
+ [tool.ty.terminal]
237
+ error-on-warning = false # warnings (e.g. `deprecated`) inform; they don't gate CI
238
+
239
+ # ITM-027 — bandit config (round 3). Tests legitimately use `assert`
240
+ # (pytest convention) — exclude_dirs covers tests/.
241
+ [tool.bandit]
242
+ exclude_dirs = ["tests", ".venv", "build", "dist"]
243
+ skips = [] # No global skips; add with rationale only.
244
+
245
+ [tool.pytest.ini_options]
246
+ testpaths = ["tests"]
247
+ python_files = ["test_*.py"]
248
+ # Marker taxonomy (ITM-046, round-9 decision).
249
+ # `live` = requires external services; `slow` = takes >1s.
250
+ # Default `addopts` excludes both — run full suite with `pytest -m ""`.
251
+ markers = ["live: requires external services", "slow: takes >1s"]
252
+ addopts = "-m 'not live and not slow'"
253
+ strict_markers = true
254
+ # WL-009 — a hung test fails loudly instead of burning a CI slot. `thread`
255
+ # method (not the POSIX-only signal default) so the cap also holds on the
256
+ # Windows matrix leg. A genuinely slow test overrides via
257
+ # `@pytest.mark.timeout(N)`.
258
+ timeout = 60
259
+ timeout_method = "thread"
260
+ # WL-010 (pytest-randomly) reorders tests each run with a printed, replayable
261
+ # seed (`-p randomly --randomly-seed=…`) to surface hidden inter-test coupling;
262
+ # it needs no config. Parallelism (WL-008, `-n auto`) is opt-in per invocation
263
+ # (`just test -n auto`, and the CI full-suite + canary runs) rather than in
264
+ # default addopts, so the fast local inner loop keeps clean `-x`/pdb behaviour.
265
+
266
+ # Branch coverage (ADR-0019): count both-arms of every branch, not just line
267
+ # execution, so the Codecov gate (WL-005) catches untested conditionals.
268
+ [tool.coverage.run]
269
+ branch = true
270
+
271
+ # ITM-015 — codespell. US English baseline (default builtin="clear,rare"); empty
272
+ # ignore list — add terms only when intentional. Decided round 9.
273
+ [tool.codespell]
274
+ skip = ".git,.venv,build,dist,node_modules,uv.lock,bun.lock,*.lock,docs/_build,*.svg,*.cast,.ruff_cache,.pytest_cache,.mypy_cache,.ty_cache,*.log"
275
+ # *.log: raw run output, not prose.
276
+ ignore-words-list = "ans,porjects,unparseable,wit"
277
+ # ans: variable name used in prompt-handling code; codespell wants to "fix"
278
+ # it to "and". Whitelist to keep the short variable name.
279
+ # porjects: the deliberate typo example in the superseded ADR 0007.
280
+ # wit: WebAssembly Interface Types, in docs/research/0001.
281
+ check-filenames = true
282
+ check-hidden = false
283
+
284
+ # import-linter: architectural import rules for this package. Run via
285
+ # `uv run lint-imports`; wired into `just check` and CI (import-boundaries).
286
+ [tool.importlinter]
287
+ root_package = "lost_sessions"
288
+ include_external_packages = true
289
+
290
+ [[tool.importlinter.contracts]]
291
+ name = "lost-sessions is a CLI: no web framework imports"
292
+ type = "forbidden"
293
+ source_modules = ["lost_sessions"]
294
+ forbidden_modules = ["fastapi", "starlette", "uvicorn"]
295
+
296
+ [[tool.importlinter.contracts]]
297
+ name = "core reaches built-in plugins only through the plugin manager"
298
+ type = "forbidden"
299
+ source_modules = ["lost_sessions.cli", "lost_sessions.hookspecs", "lost_sessions.plugins"]
300
+ forbidden_modules = ["lost_sessions.default_plugins"]