modulearn 0.2.0__tar.gz → 0.3.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.
- modulearn-0.3.0/CHANGELOG.md +24 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/PKG-INFO +8 -4
- {modulearn-0.2.0 → modulearn-0.3.0}/PUBLISHING.md +11 -5
- {modulearn-0.2.0 → modulearn-0.3.0}/README.md +6 -3
- modulearn-0.3.0/docs/changelog-tags-guide.html +218 -0
- modulearn-0.3.0/docs/ci-badge-guide.html +174 -0
- modulearn-0.3.0/docs/init-scaffold-guide.html +361 -0
- modulearn-0.3.0/docs/readme-install-guide.html +192 -0
- modulearn-0.3.0/docs/registry-api-guide.html +196 -0
- modulearn-0.3.0/docs/regression-guide.html +359 -0
- modulearn-0.3.0/docs/reload-guide.html +202 -0
- modulearn-0.3.0/docs/run-errors-guide.html +232 -0
- modulearn-0.3.0/docs/single-source-version-guide.html +198 -0
- modulearn-0.3.0/docs/type-rules-guide.html +439 -0
- modulearn-0.3.0/examples/regression.py +77 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/__init__.py +1 -1
- modulearn-0.3.0/modulearn/cli.py +229 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/demo.py +1 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/pyproject.toml +5 -1
- modulearn-0.3.0/tests/test_cli_init.py +23 -0
- modulearn-0.3.0/tests/test_run_errors_cli.py +22 -0
- modulearn-0.3.0/tests/test_type_rules.py +134 -0
- modulearn-0.2.0/CHANGELOG.md +0 -6
- modulearn-0.2.0/modulearn/cli.py +0 -88
- {modulearn-0.2.0 → modulearn-0.3.0}/.github/workflows/ci.yml +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/.gitignore +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/LICENSE +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/docs/implementation-guide.html +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/docs/roadmap.html +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/examples/quickstart.py +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/__main__.py +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/compiler.py +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/registry.py +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/server.py +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/static/graph-engine.js +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/static/graph.html +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/static/graph.js +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/tests/test_robustness.py +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/tests/test_run_errors.py +0 -0
- {modulearn-0.2.0 → modulearn-0.3.0}/tests/test_smoke.py +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to ModuLearn are documented here.
|
|
4
|
+
This project follows [Keep a Changelog](https://keepachangelog.com) and
|
|
5
|
+
[Semantic Versioning](https://semver.org).
|
|
6
|
+
|
|
7
|
+
## [0.3.0] - 2026-07-27
|
|
8
|
+
- `modulearn init [dir]` scaffolds a runnable starter project. (app.py, README, .gitignore)
|
|
9
|
+
- `modulearn run --reload` restarts the server on file changes for live development.
|
|
10
|
+
- Sharper `modulearn run` errors: names the failing line on import errors, suggests a real app attribute
|
|
11
|
+
when the main one is missing, and rejects non-app objects.
|
|
12
|
+
|
|
13
|
+
## [0.2.0] — 2026-07-23
|
|
14
|
+
- Surface on_train crashes on the canvas (error + full traceback)
|
|
15
|
+
- Loss node now shapes the training curve
|
|
16
|
+
- CompiledGraph.check_shape() validates loaded array shape
|
|
17
|
+
- Enforce hyperparameter min/max at compile
|
|
18
|
+
- GitHub Actions CI across Python 3.10–3.13
|
|
19
|
+
|
|
20
|
+
## [0.1.1] — 2026-07-22
|
|
21
|
+
- Add `python -m modulearn` entry point
|
|
22
|
+
|
|
23
|
+
## [0.1.0] — 2026-07-22
|
|
24
|
+
- Initial release: node-graph editor, Registry, compiler, and `modulearn` CLI
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modulearn
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A visual, Unreal-Blueprints-style node-graph editor for building and launching ML training runs.
|
|
5
5
|
Project-URL: Homepage, https://github.com/IsaiahKoamalu/modulearn
|
|
6
6
|
Project-URL: Source, https://github.com/IsaiahKoamalu/modulearn
|
|
@@ -16,6 +16,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
16
16
|
Requires-Python: >=3.10
|
|
17
17
|
Requires-Dist: fastapi>=0.110
|
|
18
18
|
Requires-Dist: pydantic>=2.0
|
|
19
|
+
Requires-Dist: scikit-learn>=1.7.2
|
|
19
20
|
Requires-Dist: uvicorn>=0.29
|
|
20
21
|
Provides-Extra: dev
|
|
21
22
|
Requires-Dist: build; extra == 'dev'
|
|
@@ -25,14 +26,14 @@ Description-Content-Type: text/markdown
|
|
|
25
26
|
|
|
26
27
|
# ModuLearn
|
|
27
28
|
|
|
29
|
+
[](https://github.com/IsaiahKoamalu/modulearn/actions/workflows/ci.yml)
|
|
30
|
+
|
|
28
31
|
A visual, [Unreal-Blueprints](https://dev.epicgames.com/documentation/en-us/unreal-engine/blueprints-visual-scripting-in-unreal-engine)-style
|
|
29
32
|
node-graph editor for building and launching machine-learning training runs.
|
|
30
33
|
Drop **Dataset → Model → Train** nodes on a canvas, wire **hyperparameters**,
|
|
31
34
|
**loss** and **feature transforms** into them, hit ▶ Train, and watch the learning
|
|
32
35
|
curve live on the graph. No forms, no drift between the UI and the code it runs.
|
|
33
36
|
|
|
34
|
-
> **ModuLearn** is a working name — you assemble training runs from modular nodes and
|
|
35
|
-
> learn from the live curve. Rename the package/repo freely; nothing depends on the name.
|
|
36
37
|
|
|
37
38
|
<!-- A screenshot or GIF of the editor goes well here. -->
|
|
38
39
|
|
|
@@ -47,7 +48,7 @@ backend can't accept and it simply won't connect.
|
|
|
47
48
|
## Install
|
|
48
49
|
|
|
49
50
|
```bash
|
|
50
|
-
pip install
|
|
51
|
+
pip install modulearn # fastapi + uvicorn + pydantic, from PyPI
|
|
51
52
|
modulearn demo # serve the bundled editor at http://localhost:8000
|
|
52
53
|
```
|
|
53
54
|
|
|
@@ -65,6 +66,9 @@ Swap `on_train` for your real training loop and nothing else changes. (The
|
|
|
65
66
|
equivalent source file lives at [`examples/quickstart.py`](examples/quickstart.py)
|
|
66
67
|
if you'd rather run it directly with `python`.)
|
|
67
68
|
|
|
69
|
+
> **Working on ModuLearn itself?** Clone the repo and install editable with
|
|
70
|
+
> dev extras: `pip install -e ".[dev]"` (adds pytest, build, twine).
|
|
71
|
+
|
|
68
72
|
## The whole integration is two things
|
|
69
73
|
|
|
70
74
|
```python
|
|
@@ -96,8 +96,14 @@ modulearn demo
|
|
|
96
96
|
|
|
97
97
|
## Cutting later releases
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
The version is single-sourced from `modulearn/__init__.py` — `pyproject.toml`
|
|
100
|
+
reads it via `[tool.hatch.version]`, so you bump exactly one line.
|
|
101
|
+
|
|
102
|
+
1. Bump `__version__` in `modulearn/__init__.py` (the only version to edit).
|
|
103
|
+
2. In `CHANGELOG.md`, move the `Unreleased` notes under a new
|
|
104
|
+
`## [<ver>] — <YYYY-MM-DD>` heading.
|
|
105
|
+
3. `rm -rf dist && python -m build`
|
|
106
|
+
4. `twine check dist/*`
|
|
107
|
+
5. `git commit -am "modulearn <ver>" && git tag -a v<ver> -m "modulearn <ver>" && git push --tags`
|
|
108
|
+
6. `twine upload dist/*`
|
|
109
|
+
7. Verify: `pip index versions modulearn` shows `<ver>` and tag `v<ver>` exists.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# ModuLearn
|
|
2
2
|
|
|
3
|
+
[](https://github.com/IsaiahKoamalu/modulearn/actions/workflows/ci.yml)
|
|
4
|
+
|
|
3
5
|
A visual, [Unreal-Blueprints](https://dev.epicgames.com/documentation/en-us/unreal-engine/blueprints-visual-scripting-in-unreal-engine)-style
|
|
4
6
|
node-graph editor for building and launching machine-learning training runs.
|
|
5
7
|
Drop **Dataset → Model → Train** nodes on a canvas, wire **hyperparameters**,
|
|
6
8
|
**loss** and **feature transforms** into them, hit ▶ Train, and watch the learning
|
|
7
9
|
curve live on the graph. No forms, no drift between the UI and the code it runs.
|
|
8
10
|
|
|
9
|
-
> **ModuLearn** is a working name — you assemble training runs from modular nodes and
|
|
10
|
-
> learn from the live curve. Rename the package/repo freely; nothing depends on the name.
|
|
11
11
|
|
|
12
12
|
<!-- A screenshot or GIF of the editor goes well here. -->
|
|
13
13
|
|
|
@@ -22,7 +22,7 @@ backend can't accept and it simply won't connect.
|
|
|
22
22
|
## Install
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
pip install
|
|
25
|
+
pip install modulearn # fastapi + uvicorn + pydantic, from PyPI
|
|
26
26
|
modulearn demo # serve the bundled editor at http://localhost:8000
|
|
27
27
|
```
|
|
28
28
|
|
|
@@ -40,6 +40,9 @@ Swap `on_train` for your real training loop and nothing else changes. (The
|
|
|
40
40
|
equivalent source file lives at [`examples/quickstart.py`](examples/quickstart.py)
|
|
41
41
|
if you'd rather run it directly with `python`.)
|
|
42
42
|
|
|
43
|
+
> **Working on ModuLearn itself?** Clone the repo and install editable with
|
|
44
|
+
> dev extras: `pip install -e ".[dev]"` (adds pytest, build, twine).
|
|
45
|
+
|
|
43
46
|
## The whole integration is two things
|
|
44
47
|
|
|
45
48
|
```python
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>ModuLearn — Implementing: CHANGELOG + tags that match PyPI</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root{
|
|
9
|
+
--bg:#14181f; --panel:#1b212b; --panel2:#20262f; --edge:#2a323f;
|
|
10
|
+
--ink:#e6edf3; --muted:#8b98a9; --faint:#5f6b7a; --accent:#a8542f;
|
|
11
|
+
--c:#c98bdb; --err:#f85149; --ok:#3fb950;
|
|
12
|
+
--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
|
13
|
+
--sans:ui-sans-serif,system-ui,-apple-system,sans-serif;
|
|
14
|
+
}
|
|
15
|
+
*{box-sizing:border-box} html,body{margin:0}
|
|
16
|
+
body{background:var(--bg); color:var(--ink); font:14.5px/1.6 var(--sans);
|
|
17
|
+
background-image:linear-gradient(rgba(255,255,255,.022) 1px,transparent 1px),
|
|
18
|
+
linear-gradient(90deg,rgba(255,255,255,.022) 1px,transparent 1px);
|
|
19
|
+
background-size:50px 50px; -webkit-font-smoothing:antialiased;}
|
|
20
|
+
.wrap{max-width:860px; margin:0 auto; padding:0 22px}
|
|
21
|
+
code{font:12.5px/1.5 var(--mono); background:#0f131a; border:1px solid var(--edge);
|
|
22
|
+
border-radius:4px; padding:1px 5px; color:#c3ccd8}
|
|
23
|
+
pre{font:12.5px/1.6 var(--mono); background:#0f131a; border:1px solid var(--edge);
|
|
24
|
+
border-radius:8px; padding:13px 15px; overflow-x:auto; color:#c3ccd8; margin:12px 0}
|
|
25
|
+
pre code{background:none; border:none; padding:0}
|
|
26
|
+
a{color:var(--c)}
|
|
27
|
+
header{padding:60px 0 26px}
|
|
28
|
+
.kicker{font:600 11px/1 var(--mono); letter-spacing:.26em; text-transform:uppercase;
|
|
29
|
+
color:var(--muted); display:flex; align-items:center; gap:10px}
|
|
30
|
+
.kicker .dot{width:9px; height:9px; border-radius:50%; background:var(--c);
|
|
31
|
+
box-shadow:0 0 0 4px color-mix(in srgb,var(--c) 18%,transparent)}
|
|
32
|
+
h1{font:600 clamp(30px,5vw,46px)/1.06 var(--sans); letter-spacing:-.02em; margin:16px 0 0}
|
|
33
|
+
h1 code{font-size:.7em; background:#0f131a; padding:2px 9px; vertical-align:3px}
|
|
34
|
+
.lede{margin:16px 0 0; color:#c3ccd8; max-width:62ch} .lede b{color:var(--ink)}
|
|
35
|
+
.goal{margin:22px 0 0; padding:13px 15px; border-left:3px solid var(--c);
|
|
36
|
+
background:#0f131a; border-radius:0 7px 7px 0; font-size:13.5px} .goal b{color:var(--c)}
|
|
37
|
+
.progress{margin:26px 0 0; display:flex; align-items:center; gap:14px}
|
|
38
|
+
.track{flex:1; height:8px; border-radius:5px; background:#0f131a; border:1px solid var(--edge); overflow:hidden}
|
|
39
|
+
.fill{height:100%; width:0; background:var(--c); transition:width .45s cubic-bezier(.2,.7,.2,1)}
|
|
40
|
+
.cnt{font:600 12px/1 var(--mono); color:var(--muted); white-space:nowrap} .cnt b{color:var(--ink); font-size:15px}
|
|
41
|
+
h2{font:600 13px/1 var(--mono); letter-spacing:.13em; text-transform:uppercase; color:var(--muted);
|
|
42
|
+
margin:52px 0 0; padding-bottom:11px; border-bottom:1px solid var(--edge); display:flex; align-items:center; gap:9px}
|
|
43
|
+
h2::before{content:""; width:9px; height:9px; border-radius:50%; background:var(--c)}
|
|
44
|
+
ol.steps{list-style:none; counter-reset:s; padding:0; margin:20px 0 0}
|
|
45
|
+
ol.steps > li{counter-increment:s; position:relative; padding:0 0 22px 52px; border-left:1px dashed var(--edge); margin-left:15px}
|
|
46
|
+
ol.steps > li:last-child{border-left-color:transparent}
|
|
47
|
+
ol.steps > li::before{content:counter(s); position:absolute; left:-15px; top:0; width:30px; height:30px;
|
|
48
|
+
border-radius:50%; background:var(--panel2); border:1px solid var(--edge); color:var(--c);
|
|
49
|
+
font:600 12px/28px var(--mono); text-align:center}
|
|
50
|
+
ol.steps > li.done::before{content:"✓"; background:var(--c); color:#0f131a; border-color:var(--c)}
|
|
51
|
+
.stitle{font:600 15px/1.35 var(--sans); display:flex; align-items:flex-start; gap:10px; margin-top:3px}
|
|
52
|
+
.stitle .chk{flex:none; width:19px; height:19px; margin-top:1px; border-radius:5px; border:1.5px solid var(--edge);
|
|
53
|
+
background:#0f131a; cursor:pointer; position:relative; transition:.12s}
|
|
54
|
+
.stitle .chk:hover{border-color:var(--c)}
|
|
55
|
+
li.done .chk{background:var(--c); border-color:var(--c)}
|
|
56
|
+
li.done .chk::after{content:"✓"; position:absolute; inset:0; display:grid; place-items:center; font-size:11px; color:#0f131a}
|
|
57
|
+
li.done .stitle span{color:var(--muted); text-decoration:line-through; text-decoration-color:var(--faint)}
|
|
58
|
+
.sbody{margin:9px 0 0; color:#c3ccd8; font-size:13.5px} .sbody p{margin:9px 0 0}
|
|
59
|
+
.where{font:600 11px/1 var(--mono); color:var(--faint); margin:2px 0 0; letter-spacing:.04em}
|
|
60
|
+
.note{margin:14px 0 0; padding:11px 13px; border-radius:7px; font-size:13px;
|
|
61
|
+
background:color-mix(in srgb,var(--c) 8%,transparent); border:1px solid color-mix(in srgb,var(--c) 32%,transparent); color:#ecdff2}
|
|
62
|
+
.note b{color:var(--c)}
|
|
63
|
+
.ok{margin:16px 0 0; padding:11px 13px; border-radius:7px; font-size:13px;
|
|
64
|
+
background:rgba(63,185,80,.07); border:1px solid rgba(63,185,80,.3); color:#a6e0ac} .ok b{color:var(--ok)}
|
|
65
|
+
.nav{display:flex; gap:10px; flex-wrap:wrap; margin:22px 0 0}
|
|
66
|
+
.nav a{font:600 12px/1 var(--mono); color:var(--muted); text-decoration:none; border:1px solid var(--edge);
|
|
67
|
+
border-radius:20px; padding:7px 12px} .nav a:hover{border-color:var(--c); color:var(--ink)}
|
|
68
|
+
footer{margin:60px 0; padding-top:20px; border-top:1px solid var(--edge); color:var(--faint); font:12px/1.6 var(--mono)}
|
|
69
|
+
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
|
|
70
|
+
@media (max-width:640px){ ol.steps > li{padding-left:38px} }
|
|
71
|
+
</style>
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
<div class="wrap">
|
|
75
|
+
<header>
|
|
76
|
+
<div class="kicker"><span class="dot"></span>Roadmap · Project hygiene · P2 · S</div>
|
|
77
|
+
<h1>CHANGELOG + tags that match PyPI</h1>
|
|
78
|
+
<p class="lede">A build-it-yourself guide. <code>CHANGELOG.md</code> has a single loose
|
|
79
|
+
<code>0.2.0</code> entry and no format; the repo carries <code>v0.1.1</code> and
|
|
80
|
+
<code>v0.2.0</code> tags. This turns that into a <b>ritual</b>: a well-formed changelog and a
|
|
81
|
+
git tag for every release, so GitHub and PyPI always tell the same story.</p>
|
|
82
|
+
<div class="goal"><b>Done when:</b> <code>CHANGELOG.md</code> follows a consistent format with an
|
|
83
|
+
<code>Unreleased</code> section, every published PyPI version has a matching annotated git tag,
|
|
84
|
+
and a documented release checklist keeps them in lockstep going forward.</div>
|
|
85
|
+
<div class="progress"><div class="track"><div class="fill" id="fill"></div></div>
|
|
86
|
+
<div class="cnt"><b id="dn">0</b> / <span id="tn">0</span> steps</div></div>
|
|
87
|
+
<div class="nav">
|
|
88
|
+
<a href="roadmap.html">← roadmap</a>
|
|
89
|
+
<a href="single-source-version-guide.html">← single-source version</a>
|
|
90
|
+
<a href="readme-install-guide.html">← README install</a>
|
|
91
|
+
<a href="ci-badge-guide.html">CI badge →</a>
|
|
92
|
+
</div>
|
|
93
|
+
</header>
|
|
94
|
+
|
|
95
|
+
<h2>The principle</h2>
|
|
96
|
+
<p style="margin-top:16px">A user who hits a bug needs to answer two questions from the repo alone:
|
|
97
|
+
<em>"what changed, and in which version?"</em> That works only if three things line up — the
|
|
98
|
+
<b>changelog</b> heading, the <b>git tag</b>, and the <b>PyPI release</b>. Right now they mostly do,
|
|
99
|
+
but by luck, not by process. Adopt the <a href="https://keepachangelog.com">Keep a Changelog</a>
|
|
100
|
+
convention and a fixed release order so it stays that way.</p>
|
|
101
|
+
<div class="note"><b>Do this after <a href="single-source-version-guide.html">single-sourcing the
|
|
102
|
+
version</a>.</b> With one version file, the release ritual is: bump <code>__version__</code> →
|
|
103
|
+
move the <code>Unreleased</code> notes under a dated heading → tag. One number, three derived
|
|
104
|
+
artifacts.</div>
|
|
105
|
+
|
|
106
|
+
<h2>Steps</h2>
|
|
107
|
+
<ol class="steps" id="steps">
|
|
108
|
+
<li data-k="ct1">
|
|
109
|
+
<div class="stitle"><span class="chk"></span><span>Reformat CHANGELOG.md to a consistent standard</span></div>
|
|
110
|
+
<div class="where">CHANGELOG.md</div>
|
|
111
|
+
<div class="sbody">
|
|
112
|
+
<p>Add a header, an <code>Unreleased</code> section at the top, and keep the existing
|
|
113
|
+
<code>0.2.0</code> notes under a dated, versioned heading. Backfill the <code>0.1.x</code>
|
|
114
|
+
releases that already shipped to PyPI so the history is complete.</p>
|
|
115
|
+
<pre><code># Changelog
|
|
116
|
+
|
|
117
|
+
All notable changes to ModuLearn are documented here.
|
|
118
|
+
This project follows [Keep a Changelog](https://keepachangelog.com) and
|
|
119
|
+
[Semantic Versioning](https://semver.org).
|
|
120
|
+
|
|
121
|
+
## [Unreleased]
|
|
122
|
+
|
|
123
|
+
## [0.2.0] — 2026-07-23
|
|
124
|
+
- Surface on_train crashes on the canvas (error + full traceback)
|
|
125
|
+
- Loss node now shapes the training curve
|
|
126
|
+
- CompiledGraph.check_shape() validates loaded array shape
|
|
127
|
+
- Enforce hyperparameter min/max at compile
|
|
128
|
+
- GitHub Actions CI across Python 3.10–3.13
|
|
129
|
+
|
|
130
|
+
## [0.1.1] — add python -m entry point
|
|
131
|
+
|
|
132
|
+
## [0.1.0] — initial release</code></pre>
|
|
133
|
+
<div class="note"><b>Backfill from what you know.</b> The git log has the real dates
|
|
134
|
+
(<code>git log --tags --simplify-by-decoration --pretty="%ci %d"</code>); fill the
|
|
135
|
+
<code>0.1.x</code> dates from there rather than guessing.</div>
|
|
136
|
+
</div>
|
|
137
|
+
</li>
|
|
138
|
+
<li data-k="ct2">
|
|
139
|
+
<div class="stitle"><span class="chk"></span><span>Audit that tags match PyPI</span></div>
|
|
140
|
+
<div class="where">terminal — one-time reconciliation</div>
|
|
141
|
+
<div class="sbody">
|
|
142
|
+
<p>List local tags and the versions PyPI actually serves, and confirm every published version
|
|
143
|
+
has a tag. (<code>v0.1.1</code> and <code>v0.2.0</code> already exist — this catches any gap,
|
|
144
|
+
e.g. a <code>0.1.0</code> release with no tag.)</p>
|
|
145
|
+
<pre><code>git tag --list 'v*' # local tags
|
|
146
|
+
pip index versions modulearn # what PyPI serves
|
|
147
|
+
# for any published version with no tag, tag its release commit:
|
|
148
|
+
git tag -a v0.1.0 <commit> -m "modulearn 0.1.0"
|
|
149
|
+
git push --tags</code></pre>
|
|
150
|
+
</div>
|
|
151
|
+
</li>
|
|
152
|
+
<li data-k="ct3">
|
|
153
|
+
<div class="stitle"><span class="chk"></span><span>Write the release checklist into PUBLISHING.md</span></div>
|
|
154
|
+
<div class="where">PUBLISHING.md</div>
|
|
155
|
+
<div class="sbody">
|
|
156
|
+
<p>Codify the order so future releases can't drift. The changelog and tag are steps, not
|
|
157
|
+
afterthoughts.</p>
|
|
158
|
+
<pre><code>## Cutting a release
|
|
159
|
+
1. Bump `__version__` in modulearn/__init__.py
|
|
160
|
+
2. Move Unreleased notes under `## [X.Y.Z] — <date>` in CHANGELOG.md
|
|
161
|
+
3. Commit: "modulearn X.Y.Z"
|
|
162
|
+
4. Tag: git tag -a vX.Y.Z -m "modulearn X.Y.Z" && git push --tags
|
|
163
|
+
5. Build + upload: python -m build && twine upload dist/*
|
|
164
|
+
6. Verify: pip index versions modulearn shows X.Y.Z, tag vX.Y.Z exists</code></pre>
|
|
165
|
+
</div>
|
|
166
|
+
</li>
|
|
167
|
+
<li data-k="ct4">
|
|
168
|
+
<div class="stitle"><span class="chk"></span><span>Verify the story is consistent</span></div>
|
|
169
|
+
<div class="sbody">
|
|
170
|
+
<p>Three artifacts, one version — check they agree for the latest release.</p>
|
|
171
|
+
<pre><code>grep -m1 '\[0' CHANGELOG.md # top versioned heading, e.g. [0.2.0]
|
|
172
|
+
git tag --list 'v0.2.0' # matching tag exists
|
|
173
|
+
pip index versions modulearn # PyPI serves 0.2.0
|
|
174
|
+
python -c "import modulearn; print(modulearn.__version__)" # 0.2.0</code></pre>
|
|
175
|
+
<div class="note"><b>Tag the annotated way.</b> <code>git tag -a</code> (not a lightweight
|
|
176
|
+
tag) records who/when/why and is what GitHub's Releases page reads. Lightweight tags look fine
|
|
177
|
+
locally but carry no metadata.</div>
|
|
178
|
+
</div>
|
|
179
|
+
</li>
|
|
180
|
+
<li data-k="ct5">
|
|
181
|
+
<div class="stitle"><span class="chk"></span><span>Commit</span></div>
|
|
182
|
+
<div class="sbody">
|
|
183
|
+
<pre><code>git add CHANGELOG.md PUBLISHING.md
|
|
184
|
+
git commit -m "Keep-a-Changelog format + release ritual; reconcile tags with PyPI"
|
|
185
|
+
git push
|
|
186
|
+
gh run list --branch main --limit 1 # expect: success</code></pre>
|
|
187
|
+
</div>
|
|
188
|
+
</li>
|
|
189
|
+
<li data-k="ct6">
|
|
190
|
+
<div class="stitle"><span class="chk"></span><span>Mark it shipped on the roadmap</span></div>
|
|
191
|
+
<div class="sbody"><p>Add <code>done:true,</code> to the
|
|
192
|
+
<code>{area:"hygiene", … title:"CHANGELOG + tags that match PyPI"}</code> item in
|
|
193
|
+
<code>docs/roadmap.html</code>.</p></div>
|
|
194
|
+
</li>
|
|
195
|
+
</ol>
|
|
196
|
+
|
|
197
|
+
<h2>Worth knowing</h2>
|
|
198
|
+
<div class="note" style="margin-top:18px"><b>A tag is a promise you can't edit.</b> Once
|
|
199
|
+
<code>vX.Y.Z</code> is pushed and the wheel is on PyPI, that version is immutable — PyPI refuses a
|
|
200
|
+
re-upload of the same version. That's exactly why the checklist puts the changelog and tag
|
|
201
|
+
<em>before</em> <code>twine upload</code>: get the record right while it's still cheap to fix.</div>
|
|
202
|
+
|
|
203
|
+
<footer>ModuLearn · docs/changelog-tags-guide.html · companion to
|
|
204
|
+
<a href="roadmap.html">roadmap.html</a> · step state is local to this browser.</footer>
|
|
205
|
+
</div>
|
|
206
|
+
<script>
|
|
207
|
+
const KEY="modulearn.changelog-tags.steps";
|
|
208
|
+
const done=new Set(JSON.parse(localStorage.getItem(KEY)||"[]"));
|
|
209
|
+
const lis=[...document.querySelectorAll("#steps > li")];
|
|
210
|
+
function paint(){lis.forEach(li=>li.classList.toggle("done",done.has(li.dataset.k)));
|
|
211
|
+
document.getElementById("fill").style.width=(100*done.size/lis.length)+"%";
|
|
212
|
+
document.getElementById("dn").textContent=done.size; document.getElementById("tn").textContent=lis.length;}
|
|
213
|
+
lis.forEach(li=>li.querySelector(".chk").addEventListener("click",()=>{const k=li.dataset.k;
|
|
214
|
+
done.has(k)?done.delete(k):done.add(k); localStorage.setItem(KEY,JSON.stringify([...done])); paint();}));
|
|
215
|
+
paint();
|
|
216
|
+
</script>
|
|
217
|
+
</body>
|
|
218
|
+
</html>
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>ModuLearn — Implementing: CI status badge</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root{
|
|
9
|
+
--bg:#14181f; --panel:#1b212b; --panel2:#20262f; --edge:#2a323f;
|
|
10
|
+
--ink:#e6edf3; --muted:#8b98a9; --faint:#5f6b7a; --accent:#a8542f;
|
|
11
|
+
--c:#c98bdb; --err:#f85149; --ok:#3fb950;
|
|
12
|
+
--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
|
13
|
+
--sans:ui-sans-serif,system-ui,-apple-system,sans-serif;
|
|
14
|
+
}
|
|
15
|
+
*{box-sizing:border-box} html,body{margin:0}
|
|
16
|
+
body{background:var(--bg); color:var(--ink); font:14.5px/1.6 var(--sans);
|
|
17
|
+
background-image:linear-gradient(rgba(255,255,255,.022) 1px,transparent 1px),
|
|
18
|
+
linear-gradient(90deg,rgba(255,255,255,.022) 1px,transparent 1px);
|
|
19
|
+
background-size:50px 50px; -webkit-font-smoothing:antialiased;}
|
|
20
|
+
.wrap{max-width:860px; margin:0 auto; padding:0 22px}
|
|
21
|
+
code{font:12.5px/1.5 var(--mono); background:#0f131a; border:1px solid var(--edge);
|
|
22
|
+
border-radius:4px; padding:1px 5px; color:#c3ccd8}
|
|
23
|
+
pre{font:12.5px/1.6 var(--mono); background:#0f131a; border:1px solid var(--edge);
|
|
24
|
+
border-radius:8px; padding:13px 15px; overflow-x:auto; color:#c3ccd8; margin:12px 0}
|
|
25
|
+
pre code{background:none; border:none; padding:0}
|
|
26
|
+
a{color:var(--c)}
|
|
27
|
+
header{padding:60px 0 26px}
|
|
28
|
+
.kicker{font:600 11px/1 var(--mono); letter-spacing:.26em; text-transform:uppercase;
|
|
29
|
+
color:var(--muted); display:flex; align-items:center; gap:10px}
|
|
30
|
+
.kicker .dot{width:9px; height:9px; border-radius:50%; background:var(--c);
|
|
31
|
+
box-shadow:0 0 0 4px color-mix(in srgb,var(--c) 18%,transparent)}
|
|
32
|
+
h1{font:600 clamp(30px,5vw,46px)/1.06 var(--sans); letter-spacing:-.02em; margin:16px 0 0}
|
|
33
|
+
h1 code{font-size:.7em; background:#0f131a; padding:2px 9px; vertical-align:3px}
|
|
34
|
+
.lede{margin:16px 0 0; color:#c3ccd8; max-width:62ch} .lede b{color:var(--ink)}
|
|
35
|
+
.goal{margin:22px 0 0; padding:13px 15px; border-left:3px solid var(--c);
|
|
36
|
+
background:#0f131a; border-radius:0 7px 7px 0; font-size:13.5px} .goal b{color:var(--c)}
|
|
37
|
+
.progress{margin:26px 0 0; display:flex; align-items:center; gap:14px}
|
|
38
|
+
.track{flex:1; height:8px; border-radius:5px; background:#0f131a; border:1px solid var(--edge); overflow:hidden}
|
|
39
|
+
.fill{height:100%; width:0; background:var(--c); transition:width .45s cubic-bezier(.2,.7,.2,1)}
|
|
40
|
+
.cnt{font:600 12px/1 var(--mono); color:var(--muted); white-space:nowrap} .cnt b{color:var(--ink); font-size:15px}
|
|
41
|
+
h2{font:600 13px/1 var(--mono); letter-spacing:.13em; text-transform:uppercase; color:var(--muted);
|
|
42
|
+
margin:52px 0 0; padding-bottom:11px; border-bottom:1px solid var(--edge); display:flex; align-items:center; gap:9px}
|
|
43
|
+
h2::before{content:""; width:9px; height:9px; border-radius:50%; background:var(--c)}
|
|
44
|
+
ol.steps{list-style:none; counter-reset:s; padding:0; margin:20px 0 0}
|
|
45
|
+
ol.steps > li{counter-increment:s; position:relative; padding:0 0 22px 52px; border-left:1px dashed var(--edge); margin-left:15px}
|
|
46
|
+
ol.steps > li:last-child{border-left-color:transparent}
|
|
47
|
+
ol.steps > li::before{content:counter(s); position:absolute; left:-15px; top:0; width:30px; height:30px;
|
|
48
|
+
border-radius:50%; background:var(--panel2); border:1px solid var(--edge); color:var(--c);
|
|
49
|
+
font:600 12px/28px var(--mono); text-align:center}
|
|
50
|
+
ol.steps > li.done::before{content:"✓"; background:var(--c); color:#0f131a; border-color:var(--c)}
|
|
51
|
+
.stitle{font:600 15px/1.35 var(--sans); display:flex; align-items:flex-start; gap:10px; margin-top:3px}
|
|
52
|
+
.stitle .chk{flex:none; width:19px; height:19px; margin-top:1px; border-radius:5px; border:1.5px solid var(--edge);
|
|
53
|
+
background:#0f131a; cursor:pointer; position:relative; transition:.12s}
|
|
54
|
+
.stitle .chk:hover{border-color:var(--c)}
|
|
55
|
+
li.done .chk{background:var(--c); border-color:var(--c)}
|
|
56
|
+
li.done .chk::after{content:"✓"; position:absolute; inset:0; display:grid; place-items:center; font-size:11px; color:#0f131a}
|
|
57
|
+
li.done .stitle span{color:var(--muted); text-decoration:line-through; text-decoration-color:var(--faint)}
|
|
58
|
+
.sbody{margin:9px 0 0; color:#c3ccd8; font-size:13.5px} .sbody p{margin:9px 0 0}
|
|
59
|
+
.where{font:600 11px/1 var(--mono); color:var(--faint); margin:2px 0 0; letter-spacing:.04em}
|
|
60
|
+
.note{margin:14px 0 0; padding:11px 13px; border-radius:7px; font-size:13px;
|
|
61
|
+
background:color-mix(in srgb,var(--c) 8%,transparent); border:1px solid color-mix(in srgb,var(--c) 32%,transparent); color:#ecdff2}
|
|
62
|
+
.note b{color:var(--c)}
|
|
63
|
+
.ok{margin:16px 0 0; padding:11px 13px; border-radius:7px; font-size:13px;
|
|
64
|
+
background:rgba(63,185,80,.07); border:1px solid rgba(63,185,80,.3); color:#a6e0ac} .ok b{color:var(--ok)}
|
|
65
|
+
.nav{display:flex; gap:10px; flex-wrap:wrap; margin:22px 0 0}
|
|
66
|
+
.nav a{font:600 12px/1 var(--mono); color:var(--muted); text-decoration:none; border:1px solid var(--edge);
|
|
67
|
+
border-radius:20px; padding:7px 12px} .nav a:hover{border-color:var(--c); color:var(--ink)}
|
|
68
|
+
footer{margin:60px 0; padding-top:20px; border-top:1px solid var(--edge); color:var(--faint); font:12px/1.6 var(--mono)}
|
|
69
|
+
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
|
|
70
|
+
@media (max-width:640px){ ol.steps > li{padding-left:38px} }
|
|
71
|
+
</style>
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
<div class="wrap">
|
|
75
|
+
<header>
|
|
76
|
+
<div class="kicker"><span class="dot"></span>Roadmap · Project hygiene · P3 · S</div>
|
|
77
|
+
<h1>CI status badge</h1>
|
|
78
|
+
<p class="lede">A build-it-yourself guide. The CI workflow (<code>.github/workflows/ci.yml</code>,
|
|
79
|
+
testing Python 3.10–3.13) already runs on every push and PR. This surfaces its result as a badge
|
|
80
|
+
at the top of the README — a cheap, at-a-glance signal of health for anyone evaluating the
|
|
81
|
+
package.</p>
|
|
82
|
+
<div class="goal"><b>Done when:</b> a live CI badge sits at the top of the README, links to the
|
|
83
|
+
workflow's runs, and shows <em>passing</em> green on <code>main</code>.</div>
|
|
84
|
+
<div class="progress"><div class="track"><div class="fill" id="fill"></div></div>
|
|
85
|
+
<div class="cnt"><b id="dn">0</b> / <span id="tn">0</span> steps</div></div>
|
|
86
|
+
<div class="nav">
|
|
87
|
+
<a href="roadmap.html">← roadmap</a>
|
|
88
|
+
<a href="changelog-tags-guide.html">← changelog + tags</a>
|
|
89
|
+
<a href="readme-install-guide.html">← README install</a>
|
|
90
|
+
<a href="single-source-version-guide.html">← single-source version</a>
|
|
91
|
+
</div>
|
|
92
|
+
</header>
|
|
93
|
+
|
|
94
|
+
<h2>Prerequisite</h2>
|
|
95
|
+
<p style="margin-top:16px">This one <b>depends on CI landing first</b> — and it has. GitHub
|
|
96
|
+
auto-serves a badge SVG for any workflow at a predictable URL built from the repo and the workflow
|
|
97
|
+
<em>file name</em>:</p>
|
|
98
|
+
<ol style="margin:12px 0 0; padding-left:22px; color:#c3ccd8">
|
|
99
|
+
<li><b>Owner/repo:</b> <code>IsaiahKoamalu/modulearn</code></li>
|
|
100
|
+
<li><b>Workflow file:</b> <code>ci.yml</code> (the <code>name:</code> inside is <code>CI</code> —
|
|
101
|
+
that's the badge's label).</li>
|
|
102
|
+
</ol>
|
|
103
|
+
<div class="note"><b>The badge tracks the default branch.</b> By default the SVG reflects the latest
|
|
104
|
+
run on <code>main</code>. That's what you want at the top of a README — "is <code>main</code>
|
|
105
|
+
green right now?"</div>
|
|
106
|
+
|
|
107
|
+
<h2>Steps</h2>
|
|
108
|
+
<ol class="steps" id="steps">
|
|
109
|
+
<li data-k="cb1">
|
|
110
|
+
<div class="stitle"><span class="chk"></span><span>Add the badge to the top of the README</span></div>
|
|
111
|
+
<div class="where">README.md — immediately under the <code># ModuLearn</code> title</div>
|
|
112
|
+
<div class="sbody">
|
|
113
|
+
<p>The image links to the workflow's run history so a click goes straight to the logs.</p>
|
|
114
|
+
<pre><code># ModuLearn
|
|
115
|
+
|
|
116
|
+
[](https://github.com/IsaiahKoamalu/modulearn/actions/workflows/ci.yml)</code></pre>
|
|
117
|
+
<div class="note"><b>Pin to a branch if you like.</b> Append
|
|
118
|
+
<code>?branch=main</code> to the <code>badge.svg</code> URL to make the "reflects main" behavior
|
|
119
|
+
explicit and immune to anyone running the workflow from a fork branch.</div>
|
|
120
|
+
</div>
|
|
121
|
+
</li>
|
|
122
|
+
<li data-k="cb2">
|
|
123
|
+
<div class="stitle"><span class="chk"></span><span>Verify the badge renders and links correctly</span></div>
|
|
124
|
+
<div class="sbody">
|
|
125
|
+
<p>The SVG is public — you can fetch it before pushing to confirm the URL is right, then eyeball
|
|
126
|
+
it on GitHub after.</p>
|
|
127
|
+
<pre><code>curl -sI "https://github.com/IsaiahKoamalu/modulearn/actions/workflows/ci.yml/badge.svg" \
|
|
128
|
+
| grep -i "content-type" # expect: image/svg+xml
|
|
129
|
+
# after pushing: open the repo on GitHub — badge shows "CI passing" (green)
|
|
130
|
+
# click it — lands on the workflow's runs list</code></pre>
|
|
131
|
+
<div class="note"><b>Grey "no status"?</b> That means the workflow file name in the URL doesn't
|
|
132
|
+
match a real workflow, or it hasn't run on the default branch yet. Confirm the path is exactly
|
|
133
|
+
<code>ci.yml</code> and that a run exists via <code>gh run list --workflow ci.yml</code>.</div>
|
|
134
|
+
</div>
|
|
135
|
+
</li>
|
|
136
|
+
<li data-k="cb3">
|
|
137
|
+
<div class="stitle"><span class="chk"></span><span>Commit</span></div>
|
|
138
|
+
<div class="sbody">
|
|
139
|
+
<pre><code>git add README.md
|
|
140
|
+
git commit -m "README: add CI status badge"
|
|
141
|
+
git push
|
|
142
|
+
gh run list --branch main --limit 1 # expect: success (and the badge follows it)</code></pre>
|
|
143
|
+
</div>
|
|
144
|
+
</li>
|
|
145
|
+
<li data-k="cb4">
|
|
146
|
+
<div class="stitle"><span class="chk"></span><span>Mark it shipped on the roadmap</span></div>
|
|
147
|
+
<div class="sbody"><p>Add <code>done:true,</code> to the
|
|
148
|
+
<code>{area:"hygiene", … title:"CI status badge"}</code> item in
|
|
149
|
+
<code>docs/roadmap.html</code>.</p></div>
|
|
150
|
+
</li>
|
|
151
|
+
</ol>
|
|
152
|
+
|
|
153
|
+
<h2>Worth knowing</h2>
|
|
154
|
+
<div class="note" style="margin-top:18px"><b>A badge is only as honest as the suite behind it.</b> A
|
|
155
|
+
green badge over a thin test suite is false comfort. It pairs best with the <a href="roadmap.html">Testing</a>
|
|
156
|
+
work — the more the CI matrix actually covers (the compiler's type rules, server endpoints, the
|
|
157
|
+
round-trip), the more that green square is worth to a reader deciding whether to trust the package.</div>
|
|
158
|
+
|
|
159
|
+
<footer>ModuLearn · docs/ci-badge-guide.html · companion to
|
|
160
|
+
<a href="roadmap.html">roadmap.html</a> · step state is local to this browser.</footer>
|
|
161
|
+
</div>
|
|
162
|
+
<script>
|
|
163
|
+
const KEY="modulearn.ci-badge.steps";
|
|
164
|
+
const done=new Set(JSON.parse(localStorage.getItem(KEY)||"[]"));
|
|
165
|
+
const lis=[...document.querySelectorAll("#steps > li")];
|
|
166
|
+
function paint(){lis.forEach(li=>li.classList.toggle("done",done.has(li.dataset.k)));
|
|
167
|
+
document.getElementById("fill").style.width=(100*done.size/lis.length)+"%";
|
|
168
|
+
document.getElementById("dn").textContent=done.size; document.getElementById("tn").textContent=lis.length;}
|
|
169
|
+
lis.forEach(li=>li.querySelector(".chk").addEventListener("click",()=>{const k=li.dataset.k;
|
|
170
|
+
done.has(k)?done.delete(k):done.add(k); localStorage.setItem(KEY,JSON.stringify([...done])); paint();}));
|
|
171
|
+
paint();
|
|
172
|
+
</script>
|
|
173
|
+
</body>
|
|
174
|
+
</html>
|