roadmap-core 0.1.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.
- roadmap_core-0.1.0/LICENSE +21 -0
- roadmap_core-0.1.0/PKG-INFO +181 -0
- roadmap_core-0.1.0/README.md +160 -0
- roadmap_core-0.1.0/pyproject.toml +87 -0
- roadmap_core-0.1.0/roadmap_core/__init__.py +21 -0
- roadmap_core-0.1.0/roadmap_core/cli.py +2061 -0
- roadmap_core-0.1.0/roadmap_core/graph.py +1312 -0
- roadmap_core-0.1.0/roadmap_core/impact.py +150 -0
- roadmap_core-0.1.0/roadmap_core/store.py +199 -0
- roadmap_core-0.1.0/roadmap_core/stores.py +507 -0
- roadmap_core-0.1.0/roadmap_core.egg-info/PKG-INFO +181 -0
- roadmap_core-0.1.0/roadmap_core.egg-info/SOURCES.txt +22 -0
- roadmap_core-0.1.0/roadmap_core.egg-info/dependency_links.txt +1 -0
- roadmap_core-0.1.0/roadmap_core.egg-info/entry_points.txt +2 -0
- roadmap_core-0.1.0/roadmap_core.egg-info/requires.txt +6 -0
- roadmap_core-0.1.0/roadmap_core.egg-info/top_level.txt +1 -0
- roadmap_core-0.1.0/setup.cfg +4 -0
- roadmap_core-0.1.0/templates/roadmap.yml +72 -0
- roadmap_core-0.1.0/tests/test_adoption.py +313 -0
- roadmap_core-0.1.0/tests/test_arcs.py +324 -0
- roadmap_core-0.1.0/tests/test_graph.py +127 -0
- roadmap_core-0.1.0/tests/test_impact.py +141 -0
- roadmap_core-0.1.0/tests/test_store.py +190 -0
- roadmap_core-0.1.0/tests/test_stores.py +454 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 roadmap-core contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: roadmap-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The roadmap work-item and arc graph: status derivation, validation, and markdown rendering. Stdlib-only, so any repo can adopt it without adopting a backend.
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Source, https://github.com/gald33/lucille/tree/main/roadmap-core
|
|
7
|
+
Keywords: roadmap,backlog,dependency-graph,agents,planning
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Provides-Extra: files
|
|
17
|
+
Requires-Dist: pyyaml>=6; extra == "files"
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# roadmap-core
|
|
23
|
+
|
|
24
|
+
The roadmap graph: status derivation, dependency and relation edges, arc state,
|
|
25
|
+
validation, and the markdown renderers behind `roadmap/ROADMAP.md` and `ARCS.md`.
|
|
26
|
+
|
|
27
|
+
**Stdlib-only and dependency-free**, which is the point rather than a nicety.
|
|
28
|
+
Three properties depend on it:
|
|
29
|
+
|
|
30
|
+
1. `scripts/roadmap.py` loads `roadmap_core/graph.py` **by path**, so a coding
|
|
31
|
+
agent in a checkout can read the backlog with no install, no DB, no admin
|
|
32
|
+
token and no network.
|
|
33
|
+
2. The Lucille backend imports the same module, so a status derived by the API
|
|
34
|
+
and a status rendered into the committed markdown cannot disagree — one
|
|
35
|
+
implementation, two callers. That divergence is a failure this repo has been
|
|
36
|
+
bitten by before.
|
|
37
|
+
3. It is the extraction seam. The roadmap is being prepared to run as its own
|
|
38
|
+
product whose default store is a single SQLite file with nothing to
|
|
39
|
+
provision; a package that pulled in a web framework or an ORM could not be
|
|
40
|
+
adopted by another repo without adopting Lucille with it.
|
|
41
|
+
|
|
42
|
+
## Storage
|
|
43
|
+
|
|
44
|
+
`store.py` is the schema — one SQLite file, `CREATE TABLE IF NOT EXISTS` on first
|
|
45
|
+
open, no migration step. `stores.py` is what you talk to:
|
|
46
|
+
|
|
47
|
+
| | `LocalStore` | `ApiStore` |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| needs | a writable path | a `call(method, path, payload)` |
|
|
50
|
+
| provisioning | none | a running host |
|
|
51
|
+
| `claim`/`release` | one `BEGIN IMMEDIATE` transaction | one HTTP request |
|
|
52
|
+
| `impact` | raises `Unsupported` | the host's tickets |
|
|
53
|
+
|
|
54
|
+
Both satisfy the same `Store` protocol, so a caller never branches on which it
|
|
55
|
+
holds. `ApiStore` is constructed with the host's own caller and holds no token,
|
|
56
|
+
no URL and no `urllib` import — auth stays entirely a host concern, and
|
|
57
|
+
`test_stores.py` asserts that rather than trusting it.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from roadmap_core.stores import LocalStore
|
|
61
|
+
|
|
62
|
+
with LocalStore("roadmap/roadmap.db") as store: # created if absent
|
|
63
|
+
store.upsert_item({"key": "a-thing", "title": "A thing"})
|
|
64
|
+
store.claim("a-thing", by="claude/some-branch")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
From the CLI, `--source local` on `push`, `claim`, `release` and `status`, or
|
|
68
|
+
`ROADMAP_SOURCE=local` once. `ROADMAP_STORE` sets the path.
|
|
69
|
+
|
|
70
|
+
## Adopting it in another project
|
|
71
|
+
|
|
72
|
+
Measured end to end by `tests/test_adoption.py`, which runs the CLI as a
|
|
73
|
+
subprocess against a scratch project with nothing on the path but this package
|
|
74
|
+
— no backend, no FastAPI, no SQLAlchemy, no Postgres, no server, no token.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install "roadmap-core[files]" # [files] adds PyYAML, which authoring needs
|
|
78
|
+
mkdir -p roadmap/items
|
|
79
|
+
export ROADMAP_SOURCE=local
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
That is the whole install. `roadmap` is a console script that comes with the
|
|
83
|
+
package — there is nothing to copy. (It used to say `curl -o scripts/roadmap.py
|
|
84
|
+
<this repo>/scripts/roadmap.py`, and this repo is private, so the package was
|
|
85
|
+
installable, importable and useless to anyone outside it.)
|
|
86
|
+
|
|
87
|
+
Then the ordinary loop, which needs nothing else:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
cat > roadmap/items/first-thing.yaml <<'YAML'
|
|
91
|
+
id: first-thing
|
|
92
|
+
title: The first thing to do
|
|
93
|
+
status: ready
|
|
94
|
+
evidence: |
|
|
95
|
+
Why this is worth doing, and how you will know it worked.
|
|
96
|
+
YAML
|
|
97
|
+
|
|
98
|
+
roadmap push # files -> store
|
|
99
|
+
roadmap ready # what is startable
|
|
100
|
+
roadmap claim first-thing
|
|
101
|
+
roadmap release first-thing
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The store is one SQLite file at `roadmap/roadmap.db`. There is nothing to
|
|
105
|
+
provision and no migration to run: it is created on first open.
|
|
106
|
+
|
|
107
|
+
**Two things that are conventions rather than choices**, both found by doing
|
|
108
|
+
this rather than by reading the code:
|
|
109
|
+
|
|
110
|
+
- **Your project root is the nearest ancestor holding `roadmap/items` or
|
|
111
|
+
`.git`**, so the commands work from anywhere inside it. Set
|
|
112
|
+
`ROADMAP_REPO_ROOT` to pin it. Deliberately not bare `roadmap/`: that is a
|
|
113
|
+
directory the tool *creates*, so keying on it let one command run in the
|
|
114
|
+
wrong place mint the marker that made that place look like a project
|
|
115
|
+
forever after.
|
|
116
|
+
- **Authoring is writing a YAML file**, not calling an API. `push` is what
|
|
117
|
+
moves it into the store; there is no `roadmap new`. That is deliberate:
|
|
118
|
+
filing an item belongs in a diff somebody reviews.
|
|
119
|
+
|
|
120
|
+
`ROADMAP_SOURCE=local` selects the SQLite store. Without it the CLI expects the
|
|
121
|
+
API store, which is how Lucille runs it — see `roadmap_core.stores`.
|
|
122
|
+
|
|
123
|
+
### CI
|
|
124
|
+
|
|
125
|
+
Copy `templates/roadmap.yml` to `.github/workflows/roadmap.yml`. That is the
|
|
126
|
+
whole CI story for the floor:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
push # files -> store, rebuilt fresh each run
|
|
130
|
+
validate # schema, dangling dependencies, cycles
|
|
131
|
+
sync --check # is the committed ROADMAP.md still what the graph renders?
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
No schedule, no credentials, no bot identity, no commit back to the default
|
|
135
|
+
branch, no self-hosted runner. `tests/test_adoption.py` reads the commands out
|
|
136
|
+
of that file and runs them, so a template that has drifted from the CLI fails
|
|
137
|
+
rather than reading as tested.
|
|
138
|
+
|
|
139
|
+
The third line is the one that earns the workflow. `ROADMAP.md` is generated but
|
|
140
|
+
committed — that is what lets an agent read the backlog with no install and no
|
|
141
|
+
network — and a generated file nobody regenerates is a file that lies.
|
|
142
|
+
|
|
143
|
+
**Do not commit `roadmap/roadmap.db`.** It is derived: `push` rebuilds it from
|
|
144
|
+
the YAML on first open, and a binary file in git conflicts on every claim. The
|
|
145
|
+
files are the record; the store is the transaction that decides who gets one.
|
|
146
|
+
|
|
147
|
+
### Upgrading to a served store
|
|
148
|
+
|
|
149
|
+
The floor's simplicity comes from one property: the store is inside the
|
|
150
|
+
checkout, so there is no second copy to drift from. Move the store to a server —
|
|
151
|
+
so that claims are visible across machines the moment they are taken, rather
|
|
152
|
+
than when a branch merges — and four things come back, none of which the
|
|
153
|
+
template can supply for you:
|
|
154
|
+
|
|
155
|
+
| What returns | Why |
|
|
156
|
+
|---|---|
|
|
157
|
+
| A **credential** step | the store is now behind auth, and the CLI needs a token per run |
|
|
158
|
+
| A **wait-for-reachable** step | a concurrent deploy can hold the store down, and being early is not being wrong |
|
|
159
|
+
| `pull` and a **bot commit** | the store now knows things no checkout does, and they have to land in the files an agent reads |
|
|
160
|
+
| A **schedule** | finishing an item is usually a code change somewhere else entirely, so no path filter can catch it — only re-asking on a clock can |
|
|
161
|
+
|
|
162
|
+
`ApiStore` is constructed with your own caller, so the auth stays yours (see the
|
|
163
|
+
table under [Storage](#storage)). Lucille's `.github/workflows/roadmap-sync.yml`
|
|
164
|
+
is the worked example of all four, and the reason it is not shipped as a
|
|
165
|
+
template: almost every line of it is a consequence of Lucille's own deployment,
|
|
166
|
+
and handing an adopter that machinery for a problem they do not have reads as
|
|
167
|
+
required rather than as one option.
|
|
168
|
+
|
|
169
|
+
## What is NOT here
|
|
170
|
+
|
|
171
|
+
HTTP, auth, and the CLI. The graph is pure functions over plain dicts keyed by
|
|
172
|
+
`key`, so the same code serves DB rows, API payloads and parsed YAML with no
|
|
173
|
+
adapter. Lucille's own persistence stays in `backend/app/crud/roadmap.py` over
|
|
174
|
+
SQLAlchemy; the two definitions of the same three tables are held together by
|
|
175
|
+
`backend/tests/test_roadmap_store_parity.py`, which asserts both the columns and
|
|
176
|
+
the row dicts the two readers produce.
|
|
177
|
+
|
|
178
|
+
The package's tests live in `tests/` here and import nothing outside the stdlib.
|
|
179
|
+
`.github/workflows/roadmap-core-tests.yml` runs them in a job that fails if
|
|
180
|
+
`app`, `fastapi`, `sqlalchemy` or `yaml` can be imported at all — the isolation
|
|
181
|
+
is asserted, not assumed.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# roadmap-core
|
|
2
|
+
|
|
3
|
+
The roadmap graph: status derivation, dependency and relation edges, arc state,
|
|
4
|
+
validation, and the markdown renderers behind `roadmap/ROADMAP.md` and `ARCS.md`.
|
|
5
|
+
|
|
6
|
+
**Stdlib-only and dependency-free**, which is the point rather than a nicety.
|
|
7
|
+
Three properties depend on it:
|
|
8
|
+
|
|
9
|
+
1. `scripts/roadmap.py` loads `roadmap_core/graph.py` **by path**, so a coding
|
|
10
|
+
agent in a checkout can read the backlog with no install, no DB, no admin
|
|
11
|
+
token and no network.
|
|
12
|
+
2. The Lucille backend imports the same module, so a status derived by the API
|
|
13
|
+
and a status rendered into the committed markdown cannot disagree — one
|
|
14
|
+
implementation, two callers. That divergence is a failure this repo has been
|
|
15
|
+
bitten by before.
|
|
16
|
+
3. It is the extraction seam. The roadmap is being prepared to run as its own
|
|
17
|
+
product whose default store is a single SQLite file with nothing to
|
|
18
|
+
provision; a package that pulled in a web framework or an ORM could not be
|
|
19
|
+
adopted by another repo without adopting Lucille with it.
|
|
20
|
+
|
|
21
|
+
## Storage
|
|
22
|
+
|
|
23
|
+
`store.py` is the schema — one SQLite file, `CREATE TABLE IF NOT EXISTS` on first
|
|
24
|
+
open, no migration step. `stores.py` is what you talk to:
|
|
25
|
+
|
|
26
|
+
| | `LocalStore` | `ApiStore` |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| needs | a writable path | a `call(method, path, payload)` |
|
|
29
|
+
| provisioning | none | a running host |
|
|
30
|
+
| `claim`/`release` | one `BEGIN IMMEDIATE` transaction | one HTTP request |
|
|
31
|
+
| `impact` | raises `Unsupported` | the host's tickets |
|
|
32
|
+
|
|
33
|
+
Both satisfy the same `Store` protocol, so a caller never branches on which it
|
|
34
|
+
holds. `ApiStore` is constructed with the host's own caller and holds no token,
|
|
35
|
+
no URL and no `urllib` import — auth stays entirely a host concern, and
|
|
36
|
+
`test_stores.py` asserts that rather than trusting it.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from roadmap_core.stores import LocalStore
|
|
40
|
+
|
|
41
|
+
with LocalStore("roadmap/roadmap.db") as store: # created if absent
|
|
42
|
+
store.upsert_item({"key": "a-thing", "title": "A thing"})
|
|
43
|
+
store.claim("a-thing", by="claude/some-branch")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
From the CLI, `--source local` on `push`, `claim`, `release` and `status`, or
|
|
47
|
+
`ROADMAP_SOURCE=local` once. `ROADMAP_STORE` sets the path.
|
|
48
|
+
|
|
49
|
+
## Adopting it in another project
|
|
50
|
+
|
|
51
|
+
Measured end to end by `tests/test_adoption.py`, which runs the CLI as a
|
|
52
|
+
subprocess against a scratch project with nothing on the path but this package
|
|
53
|
+
— no backend, no FastAPI, no SQLAlchemy, no Postgres, no server, no token.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install "roadmap-core[files]" # [files] adds PyYAML, which authoring needs
|
|
57
|
+
mkdir -p roadmap/items
|
|
58
|
+
export ROADMAP_SOURCE=local
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
That is the whole install. `roadmap` is a console script that comes with the
|
|
62
|
+
package — there is nothing to copy. (It used to say `curl -o scripts/roadmap.py
|
|
63
|
+
<this repo>/scripts/roadmap.py`, and this repo is private, so the package was
|
|
64
|
+
installable, importable and useless to anyone outside it.)
|
|
65
|
+
|
|
66
|
+
Then the ordinary loop, which needs nothing else:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
cat > roadmap/items/first-thing.yaml <<'YAML'
|
|
70
|
+
id: first-thing
|
|
71
|
+
title: The first thing to do
|
|
72
|
+
status: ready
|
|
73
|
+
evidence: |
|
|
74
|
+
Why this is worth doing, and how you will know it worked.
|
|
75
|
+
YAML
|
|
76
|
+
|
|
77
|
+
roadmap push # files -> store
|
|
78
|
+
roadmap ready # what is startable
|
|
79
|
+
roadmap claim first-thing
|
|
80
|
+
roadmap release first-thing
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The store is one SQLite file at `roadmap/roadmap.db`. There is nothing to
|
|
84
|
+
provision and no migration to run: it is created on first open.
|
|
85
|
+
|
|
86
|
+
**Two things that are conventions rather than choices**, both found by doing
|
|
87
|
+
this rather than by reading the code:
|
|
88
|
+
|
|
89
|
+
- **Your project root is the nearest ancestor holding `roadmap/items` or
|
|
90
|
+
`.git`**, so the commands work from anywhere inside it. Set
|
|
91
|
+
`ROADMAP_REPO_ROOT` to pin it. Deliberately not bare `roadmap/`: that is a
|
|
92
|
+
directory the tool *creates*, so keying on it let one command run in the
|
|
93
|
+
wrong place mint the marker that made that place look like a project
|
|
94
|
+
forever after.
|
|
95
|
+
- **Authoring is writing a YAML file**, not calling an API. `push` is what
|
|
96
|
+
moves it into the store; there is no `roadmap new`. That is deliberate:
|
|
97
|
+
filing an item belongs in a diff somebody reviews.
|
|
98
|
+
|
|
99
|
+
`ROADMAP_SOURCE=local` selects the SQLite store. Without it the CLI expects the
|
|
100
|
+
API store, which is how Lucille runs it — see `roadmap_core.stores`.
|
|
101
|
+
|
|
102
|
+
### CI
|
|
103
|
+
|
|
104
|
+
Copy `templates/roadmap.yml` to `.github/workflows/roadmap.yml`. That is the
|
|
105
|
+
whole CI story for the floor:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
push # files -> store, rebuilt fresh each run
|
|
109
|
+
validate # schema, dangling dependencies, cycles
|
|
110
|
+
sync --check # is the committed ROADMAP.md still what the graph renders?
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
No schedule, no credentials, no bot identity, no commit back to the default
|
|
114
|
+
branch, no self-hosted runner. `tests/test_adoption.py` reads the commands out
|
|
115
|
+
of that file and runs them, so a template that has drifted from the CLI fails
|
|
116
|
+
rather than reading as tested.
|
|
117
|
+
|
|
118
|
+
The third line is the one that earns the workflow. `ROADMAP.md` is generated but
|
|
119
|
+
committed — that is what lets an agent read the backlog with no install and no
|
|
120
|
+
network — and a generated file nobody regenerates is a file that lies.
|
|
121
|
+
|
|
122
|
+
**Do not commit `roadmap/roadmap.db`.** It is derived: `push` rebuilds it from
|
|
123
|
+
the YAML on first open, and a binary file in git conflicts on every claim. The
|
|
124
|
+
files are the record; the store is the transaction that decides who gets one.
|
|
125
|
+
|
|
126
|
+
### Upgrading to a served store
|
|
127
|
+
|
|
128
|
+
The floor's simplicity comes from one property: the store is inside the
|
|
129
|
+
checkout, so there is no second copy to drift from. Move the store to a server —
|
|
130
|
+
so that claims are visible across machines the moment they are taken, rather
|
|
131
|
+
than when a branch merges — and four things come back, none of which the
|
|
132
|
+
template can supply for you:
|
|
133
|
+
|
|
134
|
+
| What returns | Why |
|
|
135
|
+
|---|---|
|
|
136
|
+
| A **credential** step | the store is now behind auth, and the CLI needs a token per run |
|
|
137
|
+
| A **wait-for-reachable** step | a concurrent deploy can hold the store down, and being early is not being wrong |
|
|
138
|
+
| `pull` and a **bot commit** | the store now knows things no checkout does, and they have to land in the files an agent reads |
|
|
139
|
+
| A **schedule** | finishing an item is usually a code change somewhere else entirely, so no path filter can catch it — only re-asking on a clock can |
|
|
140
|
+
|
|
141
|
+
`ApiStore` is constructed with your own caller, so the auth stays yours (see the
|
|
142
|
+
table under [Storage](#storage)). Lucille's `.github/workflows/roadmap-sync.yml`
|
|
143
|
+
is the worked example of all four, and the reason it is not shipped as a
|
|
144
|
+
template: almost every line of it is a consequence of Lucille's own deployment,
|
|
145
|
+
and handing an adopter that machinery for a problem they do not have reads as
|
|
146
|
+
required rather than as one option.
|
|
147
|
+
|
|
148
|
+
## What is NOT here
|
|
149
|
+
|
|
150
|
+
HTTP, auth, and the CLI. The graph is pure functions over plain dicts keyed by
|
|
151
|
+
`key`, so the same code serves DB rows, API payloads and parsed YAML with no
|
|
152
|
+
adapter. Lucille's own persistence stays in `backend/app/crud/roadmap.py` over
|
|
153
|
+
SQLAlchemy; the two definitions of the same three tables are held together by
|
|
154
|
+
`backend/tests/test_roadmap_store_parity.py`, which asserts both the columns and
|
|
155
|
+
the row dicts the two readers produce.
|
|
156
|
+
|
|
157
|
+
The package's tests live in `tests/` here and import nothing outside the stdlib.
|
|
158
|
+
`.github/workflows/roadmap-core-tests.yml` runs them in a job that fails if
|
|
159
|
+
`app`, `fastapi`, `sqlalchemy` or `yaml` can be imported at all — the isolation
|
|
160
|
+
is asserted, not assumed.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "roadmap-core"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "The roadmap work-item and arc graph: status derivation, validation, and markdown rendering. Stdlib-only, so any repo can adopt it without adopting a backend."
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
keywords = ["roadmap", "backlog", "dependency-graph", "agents", "planning"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 4 - Beta",
|
|
11
|
+
"Intended Audience :: Developers",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Topic :: Software Development :: Build Tools",
|
|
15
|
+
]
|
|
16
|
+
# Intentionally dependency-free, and this is load-bearing rather than tidy.
|
|
17
|
+
#
|
|
18
|
+
# Two callers need this logic and neither may drag the other in: the Lucille
|
|
19
|
+
# backend (which has a DB) and `scripts/roadmap.py` (which runs in any checkout
|
|
20
|
+
# with no app dependencies, no DB and no network — that is what lets an agent
|
|
21
|
+
# read the backlog offline). The script loads `roadmap_core/graph.py` BY PATH for
|
|
22
|
+
# exactly that reason, which only keeps working while this stays importable with
|
|
23
|
+
# nothing installed.
|
|
24
|
+
#
|
|
25
|
+
# It is also the extraction seam. The roadmap is being prepared to run as its own
|
|
26
|
+
# product whose default store is a single SQLite file; a package that pulls in a
|
|
27
|
+
# web framework or an ORM could not be adopted by another repo without adopting
|
|
28
|
+
# Lucille with it.
|
|
29
|
+
dependencies = []
|
|
30
|
+
|
|
31
|
+
# pytest only, and nothing else. The tests here are synthetic by rule — no DB, no
|
|
32
|
+
# app, no committed roadmap/items/ — so a repo adopting this package can run its
|
|
33
|
+
# proof of correctness without adopting anything from Lucille. That is checked in
|
|
34
|
+
# CI by installing ONLY this package and running them.
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
# Authoring an item IS writing `roadmap/items/<key>.yaml` — there is no
|
|
37
|
+
# `roadmap.py new`, deliberately, because filing work belongs in a diff
|
|
38
|
+
# somebody reviews. So the file paths need a YAML parser, and an adopter who
|
|
39
|
+
# installs the bare package finds that out from `push` rather than from here:
|
|
40
|
+
#
|
|
41
|
+
# PyYAML required for --source files (pip install pyyaml)
|
|
42
|
+
#
|
|
43
|
+
# Measured on 2026-08-18 by `tests/test_adoption.py` running in the isolation
|
|
44
|
+
# job, after the same test passed locally on a machine that happened to have
|
|
45
|
+
# PyYAML installed. An extra rather than a hard dependency because the top of
|
|
46
|
+
# this file means what it says: the store, the graph and the impact rule stay
|
|
47
|
+
# importable with nothing installed, which is what lets the backend and a bare
|
|
48
|
+
# checkout both use them. `[files]` is what the CLI needs, not what the library
|
|
49
|
+
# needs.
|
|
50
|
+
#
|
|
51
|
+
# Deliberately NOT in `dev`. `roadmap-core-tests.yml` asserts that `yaml` is
|
|
52
|
+
# not importable in that job — the guard that keeps "this package stands alone"
|
|
53
|
+
# from quietly becoming false. Putting PyYAML in `dev` to make one test pass
|
|
54
|
+
# would have disabled that guard to do it, which is the trade it exists to
|
|
55
|
+
# refuse. The adoption test runs in its own job with `[files]` installed.
|
|
56
|
+
files = ["pyyaml>=6"]
|
|
57
|
+
dev = ["pytest>=8"]
|
|
58
|
+
|
|
59
|
+
# The CLI ships WITH the package, and this line is the reason adopting it works
|
|
60
|
+
# at all. `README.md` used to say `curl -o scripts/roadmap.py <this repo>/...`,
|
|
61
|
+
# and this repo is private — so the package was installable, importable, and
|
|
62
|
+
# useless, because the 2000-line program that drives it could not be obtained.
|
|
63
|
+
# A dependency-free library whose tool you cannot get is not an adoptable tool.
|
|
64
|
+
[project.scripts]
|
|
65
|
+
roadmap = "roadmap_core.cli:main"
|
|
66
|
+
|
|
67
|
+
[project.urls]
|
|
68
|
+
Source = "https://github.com/gald33/lucille/tree/main/roadmap-core"
|
|
69
|
+
|
|
70
|
+
[build-system]
|
|
71
|
+
requires = ["setuptools>=68"]
|
|
72
|
+
build-backend = "setuptools.build_meta"
|
|
73
|
+
|
|
74
|
+
[tool.setuptools.packages.find]
|
|
75
|
+
include = ["roadmap_core*"]
|
|
76
|
+
|
|
77
|
+
# The CI template an adopter copies. Shipped in the sdist rather than left in
|
|
78
|
+
# the repo, because the README tells them to copy it and a pip install is all
|
|
79
|
+
# some adopters will ever have of this project.
|
|
80
|
+
[tool.setuptools.data-files]
|
|
81
|
+
"share/roadmap-core/templates" = ["templates/roadmap.yml"]
|
|
82
|
+
|
|
83
|
+
[tool.ruff]
|
|
84
|
+
line-length = 100
|
|
85
|
+
|
|
86
|
+
[tool.ruff.lint]
|
|
87
|
+
select = ["E", "F", "I", "B"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""The roadmap graph, store and CLI, as a standalone package.
|
|
2
|
+
|
|
3
|
+
Import the graph explicitly rather than re-exporting it here::
|
|
4
|
+
|
|
5
|
+
from roadmap_core import graph
|
|
6
|
+
from roadmap_core.graph import derive_status, validate_graph
|
|
7
|
+
|
|
8
|
+
Kept bare on purpose, for a reason that outlived the one it was written for.
|
|
9
|
+
It used to be that ``scripts/roadmap.py`` loaded ``graph.py`` *by path* with
|
|
10
|
+
nothing installed, and a package ``__init__`` importing submodules would have
|
|
11
|
+
broken that. The CLI lives in here now (``roadmap_core.cli``) and imports its
|
|
12
|
+
siblings normally, so that particular hazard is gone.
|
|
13
|
+
|
|
14
|
+
What remains is better: ``graph``, ``store`` and ``stores`` are importable with
|
|
15
|
+
no third-party package present at all, and ``cli`` is importable without
|
|
16
|
+
PyYAML — it imports it inside the two functions that parse item files. A
|
|
17
|
+
``__init__`` that reached for ``cli`` would drag that requirement onto every
|
|
18
|
+
caller of the graph, including the Lucille backend, which needs none of it.
|
|
19
|
+
``roadmap-core-tests.yml`` asserts exactly this by running the suite in an
|
|
20
|
+
environment where ``yaml`` cannot be imported at all.
|
|
21
|
+
"""
|