stack-guardian 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {stack_guardian-0.1.0/stack_guardian.egg-info → stack_guardian-0.2.0}/PKG-INFO +203 -164
  2. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/README.md +183 -144
  3. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/pyproject.toml +38 -38
  4. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/setup.cfg +4 -4
  5. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian/__init__.py +13 -13
  6. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian/checks.d/10_core.py +238 -173
  7. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian/cli.py +376 -295
  8. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian/collect.py +313 -205
  9. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian/core.py +264 -264
  10. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian/finding.py +490 -490
  11. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian/fixes.py +286 -286
  12. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian/mcp_server.py +358 -339
  13. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian/multinode.py +152 -152
  14. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian/parsers.py +657 -657
  15. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian/serve.py +258 -198
  16. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian/state.py +266 -266
  17. {stack_guardian-0.1.0 → stack_guardian-0.2.0/stack_guardian.egg-info}/PKG-INFO +203 -164
  18. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian.egg-info/SOURCES.txt +10 -1
  19. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/tests/test_doctor_fix.py +137 -137
  20. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/tests/test_examples.py +37 -37
  21. stack_guardian-0.2.0/tests/test_mcp_subcommand.py +31 -0
  22. stack_guardian-0.2.0/tests/test_metrics.py +85 -0
  23. stack_guardian-0.2.0/tests/test_model_expiring.py +47 -0
  24. stack_guardian-0.2.0/tests/test_openai_collector.py +101 -0
  25. stack_guardian-0.2.0/tests/test_readme_1014.py +12 -0
  26. stack_guardian-0.2.0/tests/test_registry_markers.py +21 -0
  27. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/tests/test_review_fixes.py +286 -286
  28. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/tests/test_selftests.py +98 -98
  29. stack_guardian-0.2.0/tests/test_tool_annotations.py +41 -0
  30. stack_guardian-0.2.0/tests/test_ttft.py +58 -0
  31. stack_guardian-0.2.0/tests/test_watch.py +76 -0
  32. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/LICENSE +0 -0
  33. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian/__main__.py +0 -0
  34. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian.egg-info/dependency_links.txt +0 -0
  35. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian.egg-info/entry_points.txt +0 -0
  36. {stack_guardian-0.1.0 → stack_guardian-0.2.0}/stack_guardian.egg-info/top_level.txt +0 -0
@@ -1,164 +1,203 @@
1
- Metadata-Version: 2.4
2
- Name: stack-guardian
3
- Version: 0.1.0
4
- Summary: A health doctor for a home AI stack: Ollama nodes, GPUs and tailnet, with graded confidence.
5
- Author: LuminariSoftwares
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/LuminariSoftwares/stack-guardian
8
- Project-URL: Issues, https://github.com/LuminariSoftwares/stack-guardian/issues
9
- Project-URL: Changelog, https://github.com/LuminariSoftwares/stack-guardian/blob/main/CHANGELOG.md
10
- Keywords: ollama,gpu,vram,tailscale,health-check,local-llm,mcp
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: License :: OSI Approved :: MIT License
13
- Classifier: Operating System :: OS Independent
14
- Classifier: Environment :: Console
15
- Classifier: Topic :: System :: Monitoring
16
- Requires-Python: >=3.11
17
- Description-Content-Type: text/markdown
18
- License-File: LICENSE
19
- Dynamic: license-file
20
-
21
- # stack-guardian
22
-
23
- [![ci](https://github.com/LuminariSoftwares/stack-guardian/actions/workflows/ci.yml/badge.svg)](https://github.com/LuminariSoftwares/stack-guardian/actions/workflows/ci.yml)
24
- [![privacy](https://github.com/LuminariSoftwares/stack-guardian/actions/workflows/privacy.yml/badge.svg)](https://github.com/LuminariSoftwares/stack-guardian/actions/workflows/privacy.yml)
25
-
26
- A health doctor for a home AI stack. It collects a snapshot of your nodes (Ollama endpoints, loaded models, NVIDIA
27
- GPUs, tailnet peers), runs small Python rules against it, remembers the last runs, and reports findings with a
28
- confidence grade: one sample is at most `LIKELY`, the same finding twice in a row is `VERIFIED`. A finding can carry a
29
- typed fix that you run on purpose, never automatically.
30
-
31
- Status: **0.1.0, first release.** Standard library only, Python 3.11+.
32
-
33
- ## Install
34
-
35
- ```
36
- pip install stack-guardian
37
- ```
38
-
39
- This gives you three commands: `stack-guardian` (the CLI), `stack-guardian-serve` and `stack-guardian-mcp`.
40
-
41
- Try it offline first: the example snapshot makes every shipped rule fire once.
42
-
43
- ```
44
- curl -LO https://raw.githubusercontent.com/LuminariSoftwares/stack-guardian/main/examples/snapshot.json
45
- curl -LO https://raw.githubusercontent.com/LuminariSoftwares/stack-guardian/main/examples/ctx.json
46
- stack-guardian doctor --snapshot snapshot.json --ctx ctx.json
47
- ```
48
-
49
- From a clone instead (for writing rules or running the tests):
50
-
51
- ```
52
- git clone https://github.com/LuminariSoftwares/stack-guardian.git
53
- cd stack-guardian
54
- python -m venv .venv
55
- .venv\Scripts\activate # Windows; macOS/Linux: source .venv/bin/activate
56
- pip install -e .
57
- stack-guardian doctor --snapshot examples/snapshot.json --ctx examples/ctx.json
58
- ```
59
-
60
- ## For coding agents
61
-
62
- - **MCP server** (any MCP client): `stack-guardian-mcp` speaks MCP over stdio with three read-only tools,
63
- `stack_health`, `stack_snapshot` and `explain`. In Claude Code:
64
-
65
- ```
66
- claude mcp add stack-guardian -e STACK_GUARDIAN_NODES=/path/to/nodes.json -- stack-guardian-mcp
67
- ```
68
-
69
- Other clients: command `stack-guardian-mcp`, args `["--nodes", "/path/to/nodes.json"]`. `stack_health` grades
70
- against the saved run history without adding to it, and reuses one snapshot for 10 s (`--ttl SECONDS`).
71
- - **DSH plugin** (`npm install dsh-stack-guardian`, source in `dsh/`, Node 20+): adds one line at the start of a DeepSeek Harness session when the doctor reports
72
- WARN or FAIL, and nothing otherwise. It runs `stack-guardian doctor --json --quiet`, so set `STACK_GUARDIAN_NODES`
73
- to your nodes file in the harness environment.
74
-
75
- ## What it does
76
-
77
- ### 1. `doctor` - run the checks
78
-
79
- Describe your nodes in a JSON file (see [examples/nodes.json](examples/nodes.json)) and check them live:
80
-
81
- ```
82
- stack-guardian doctor --nodes examples/nodes.json --ctx examples/ctx.json
83
- ```
84
-
85
- With neither `--snapshot` nor `--nodes`, `doctor` reads the nodes file named by `$STACK_GUARDIAN_NODES`.
86
-
87
- Options: `--checks-dir DIR` (your own rules, default is the shipped `checks.d`), `--ctx FILE` (rule settings such as
88
- the model each node is expected to hold), `--state FILE` (default `$STACK_GUARDIAN_STATE` or
89
- `~/.stack-guardian/state.json`), `--json`, `--quiet` (print nothing when all is OK).
90
-
91
- Exit codes: `0` all OK/INFO, `1` a WARN, `2` a FAIL, `3` usage, `4` the doctor itself crashed.
92
-
93
- Shipped rules: `node.unreachable`, `ollama.unreachable`, `model.not_loaded`, `vram.unattributed`, `gpu.unavailable`,
94
- `tailnet.unavailable`, `tailnet.peer_offline`, `tailnet.relayed`. Each raises one finding per affected node. A rule that
95
- raises becomes a `check.crashed` WARN - a buggy rule never reports a broken stack.
96
-
97
- ### 2. `doctor --fix ID` - run a finding's typed fix
98
-
99
- A rule may attach `fix={"action": ..., "args": {...}}` to a finding. There are exactly three actions and never a raw
100
- shell string:
101
-
102
- | action | what it does | needs |
103
- |---|---|---|
104
- | `unload_model` | POST `keep_alive: 0` to an Ollama `/api/generate` | `node`, `url`, `model` |
105
- | `restart_lane` | runs the argv list you declared for that lane | `lane` declared in `--config` |
106
- | `set_env` | writes `NAME=value` to `<state folder>/env` and sets it for the process | `name`, `value` |
107
-
108
- ```
109
- stack-guardian doctor --checks-dir examples/checks.d --snapshot examples/snapshot.json --fix ollama.stuck --config examples/lanes.json
110
- ```
111
-
112
- That is a **dry run**: it prints the plan and changes nothing. Add `--apply` to perform it. An applied fix first saves
113
- the snapshot under `<state folder>/snapshots/`, then runs, then appends one JSON line to `<state folder>/fixes.jsonl`
114
- (`--audit FILE` to move it). A `set_env` value is never written to the audit, only its length. `ID` is the finding
115
- code, or `code@node` when several nodes raise the same code. With `--fix`, the exit code is the fix's: `0` plan or
116
- success, `1` refused or failed, `3` usage.
117
-
118
- ### 3. Other commands
119
-
120
- - `stack-guardian snapshot --nodes FILE` prints the raw snapshot the rules read.
121
- - `stack-guardian-serve --nodes FILE [--port 8788]` serves a fresh snapshot at `GET /snapshot` (read-only, loopback
122
- unless you pass `--host H --allow-remote`), so one machine can ask another for its health.
123
- no auth: bind to localhost or a tailnet IP only.
124
- - `stack-guardian-mcp --nodes FILE [--ctx FILE] [--state FILE] [--ttl SECONDS]`: see [For coding agents](#for-coding-agents).
125
-
126
- ## Writing a rule
127
-
128
- ```python
129
- from stack_guardian import finding as F
130
-
131
- def ollama_up(snapshot, ctx):
132
- if not snapshot["nodes"]["gpu-box"].get("reachable"):
133
- return F.make_finding("ollama.unreachable", "missing", "FAIL", "gpu-box", "Ollama does not answer")
134
- return None
135
-
136
- CHECKS = [("ollama.up", ollama_up)]
137
- ```
138
-
139
- Drop the file in a folder and point `--checks-dir` at it. A full example with a fix is in
140
- [examples/checks.d/20_ollama_fix.py](examples/checks.d/20_ollama_fix.py). A rule may also return a list of findings,
141
- one per node.
142
-
143
- ## Examples
144
-
145
- | file | what it is |
146
- |---|---|
147
- | `examples/nodes.json` | three nodes: a local one with GPUs, two remote ones over the tailnet |
148
- | `examples/ctx.json` | rule settings: which model gpu-box-2 should hold |
149
- | `examples/snapshot.json` | an offline snapshot on which every shipped rule fires once, for trying `doctor` and `--fix` |
150
- | `examples/lanes.json` | the lanes `restart_lane` is allowed to run |
151
- | `examples/checks.d/20_ollama_fix.py` | a custom rule whose finding carries a fix |
152
-
153
- ## Test
154
-
155
- ```
156
- pip install pytest
157
- python -m pytest -q
158
- ```
159
-
160
- CI runs this on Ubuntu, Windows and macOS with Python 3.11, plus a privacy scan of every tracked file.
161
-
162
- ## License
163
-
164
- MIT
1
+ Metadata-Version: 2.4
2
+ Name: stack-guardian
3
+ Version: 0.2.0
4
+ Summary: A health doctor for a home AI stack: Ollama nodes, GPUs and tailnet, with graded confidence.
5
+ Author: LuminariSoftwares
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/LuminariSoftwares/stack-guardian
8
+ Project-URL: Issues, https://github.com/LuminariSoftwares/stack-guardian/issues
9
+ Project-URL: Changelog, https://github.com/LuminariSoftwares/stack-guardian/blob/main/CHANGELOG.md
10
+ Keywords: ollama,gpu,vram,tailscale,health-check,local-llm,mcp
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Environment :: Console
15
+ Classifier: Topic :: System :: Monitoring
16
+ Requires-Python: >=3.11
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Dynamic: license-file
20
+
21
+ # stack-guardian
22
+
23
+ [![ci](https://github.com/LuminariSoftwares/stack-guardian/actions/workflows/ci.yml/badge.svg)](https://github.com/LuminariSoftwares/stack-guardian/actions/workflows/ci.yml)
24
+ [![privacy](https://github.com/LuminariSoftwares/stack-guardian/actions/workflows/privacy.yml/badge.svg)](https://github.com/LuminariSoftwares/stack-guardian/actions/workflows/privacy.yml)
25
+
26
+ mcp-name: io.github.luminarisoftwares/stack-guardian
27
+
28
+ A health doctor for a home AI stack. It collects a snapshot of your nodes (Ollama endpoints, loaded models, NVIDIA
29
+ GPUs, tailnet peers), runs small Python rules against it, remembers the last runs, and reports findings with a
30
+ confidence grade: one sample is at most `LIKELY`, the same finding twice in a row is `VERIFIED`. A finding can carry a
31
+ typed fix that you run on purpose, never automatically.
32
+
33
+ Status: **0.1.0, first release.** Standard library only, Python 3.11+.
34
+
35
+ ## Install
36
+
37
+ ```
38
+ pip install stack-guardian
39
+ ```
40
+
41
+ This gives you three commands: `stack-guardian` (the CLI), `stack-guardian-serve` and `stack-guardian-mcp`.
42
+
43
+ Try it offline first: the example snapshot makes every shipped rule fire once.
44
+
45
+ ```
46
+ curl -LO https://raw.githubusercontent.com/LuminariSoftwares/stack-guardian/main/examples/snapshot.json
47
+ curl -LO https://raw.githubusercontent.com/LuminariSoftwares/stack-guardian/main/examples/ctx.json
48
+ stack-guardian doctor --snapshot snapshot.json --ctx ctx.json
49
+ ```
50
+
51
+ From a clone instead (for writing rules or running the tests):
52
+
53
+ ```
54
+ git clone https://github.com/LuminariSoftwares/stack-guardian.git
55
+ cd stack-guardian
56
+ python -m venv .venv
57
+ .venv\Scripts\activate # Windows; macOS/Linux: source .venv/bin/activate
58
+ pip install -e .
59
+ stack-guardian doctor --snapshot examples/snapshot.json --ctx examples/ctx.json
60
+ ```
61
+
62
+ ## For coding agents
63
+
64
+ - **MCP server** (any MCP client): `stack-guardian-mcp` speaks MCP over stdio with three read-only tools,
65
+ `stack_health`, `stack_snapshot` and `explain`. In Claude Code:
66
+
67
+ ```
68
+ claude mcp add stack-guardian -e STACK_GUARDIAN_NODES=/path/to/nodes.json -- stack-guardian-mcp
69
+ ```
70
+
71
+ Other clients: command `stack-guardian-mcp`, args `["--nodes", "/path/to/nodes.json"]`. `stack_health` grades
72
+ against the saved run history without adding to it, and reuses one snapshot for 10 s (`--ttl SECONDS`).
73
+ - **DSH plugin** (`npm install dsh-stack-guardian`, source in `dsh/`, Node 20+): adds one line at the start of a DeepSeek Harness session when the doctor reports
74
+ WARN or FAIL, and nothing otherwise. It runs `stack-guardian doctor --json --quiet`, so set `STACK_GUARDIAN_NODES`
75
+ to your nodes file in the harness environment.
76
+ - **Watching and scraping:** `stack-guardian doctor --watch 30 --json` prints one JSON object per finding that changes
77
+ state (new, resolved, severity change), so an agent can tail it; `stack-guardian-serve --nodes FILE` also answers
78
+ `GET /metrics` in Prometheus text format.
79
+
80
+ ## What it does
81
+
82
+ ### 1. `doctor` - run the checks
83
+
84
+ Describe your nodes in a JSON file (see [examples/nodes.json](examples/nodes.json)) and check them live:
85
+
86
+ ```
87
+ stack-guardian doctor --nodes examples/nodes.json --ctx examples/ctx.json
88
+ ```
89
+
90
+ With neither `--snapshot` nor `--nodes`, `doctor` reads the nodes file named by `$STACK_GUARDIAN_NODES`.
91
+
92
+ Options: `--checks-dir DIR` (your own rules, default is the shipped `checks.d`), `--ctx FILE` (rule settings such as
93
+ the model each node is expected to hold), `--state FILE` (default `$STACK_GUARDIAN_STATE` or
94
+ `~/.stack-guardian/state.json`), `--json`, `--quiet` (print nothing when all is OK).
95
+
96
+ Exit codes: `0` all OK/INFO, `1` a WARN, `2` a FAIL, `3` usage, `4` the doctor itself crashed.
97
+
98
+ Shipped rules: `node.unreachable`, `ollama.unreachable`, `model.not_loaded`, `model.expiring`, `model.slow_ttft`,
99
+ `vram.unattributed`, `gpu.unavailable`, `tailnet.unavailable`, `tailnet.peer_offline`, `tailnet.relayed`. Each raises one finding per affected node. A rule that
100
+ raises becomes a `check.crashed` WARN - a buggy rule never reports a broken stack.
101
+
102
+ **LM Studio, vLLM, llama.cpp server.** Give the node `"kind": "openai"` and list its base URLs under `endpoints`.
103
+ It is read from `GET /v1/models` (and `GET /v1/models/<id>` when the server has it) into the same shape as an Ollama
104
+ endpoint, so every rule above applies; a model a server lists as unloaded does not count as loaded.
105
+
106
+ ```
107
+ {"gpu-box": {"kind": "openai", "endpoints": ["http://127.0.0.1:1234"]}}
108
+ ```
109
+
110
+ **`model.expiring`** (WARN): a loaded Ollama model whose `/api/ps` `expires_at` is within 60 s of the snapshot time;
111
+ INFO when `/api/ps` is unavailable. Change the window with `"model_expiring_s": 120` in `--ctx`.
112
+
113
+ **`--watch N`** re-runs the doctor every N seconds and prints only the findings that changed state, each with a
114
+ timestamp. Ctrl+C exits `0` with a one-line summary. With `--json`, each change is one JSON object on stdout.
115
+
116
+ ```
117
+ stack-guardian doctor --nodes examples/nodes.json --watch 30
118
+ ```
119
+
120
+ **`--ttft`** sends a 1-token generation to every loaded model on each Ollama endpoint and records `ttft_ms` on the
121
+ model in the snapshot. It costs a generation, so it never runs unless you pass it, and it needs `--nodes`. The probe
122
+ keeps each model's unload time as it was. `model.slow_ttft` (INFO) fires above 2000 ms; change it with
123
+ `"ttft_slow_ms": 1500` in `--ctx`.
124
+
125
+ ```
126
+ stack-guardian doctor --nodes examples/nodes.json --ttft
127
+ ```
128
+
129
+ ### 2. `doctor --fix ID` - run a finding's typed fix
130
+
131
+ A rule may attach `fix={"action": ..., "args": {...}}` to a finding. There are exactly three actions and never a raw
132
+ shell string:
133
+
134
+ | action | what it does | needs |
135
+ |---|---|---|
136
+ | `unload_model` | POST `keep_alive: 0` to an Ollama `/api/generate` | `node`, `url`, `model` |
137
+ | `restart_lane` | runs the argv list you declared for that lane | `lane` declared in `--config` |
138
+ | `set_env` | writes `NAME=value` to `<state folder>/env` and sets it for the process | `name`, `value` |
139
+
140
+ ```
141
+ stack-guardian doctor --checks-dir examples/checks.d --snapshot examples/snapshot.json --fix ollama.stuck --config examples/lanes.json
142
+ ```
143
+
144
+ That is a **dry run**: it prints the plan and changes nothing. Add `--apply` to perform it. An applied fix first saves
145
+ the snapshot under `<state folder>/snapshots/`, then runs, then appends one JSON line to `<state folder>/fixes.jsonl`
146
+ (`--audit FILE` to move it). A `set_env` value is never written to the audit, only its length. `ID` is the finding
147
+ code, or `code@node` when several nodes raise the same code. With `--fix`, the exit code is the fix's: `0` plan or
148
+ success, `1` refused or failed, `3` usage.
149
+
150
+ ### 3. Other commands
151
+
152
+ - `stack-guardian snapshot --nodes FILE [--ttft]` prints the raw snapshot the rules read.
153
+ - `stack-guardian-serve --nodes FILE [--ctx FILE] [--port 8788]` serves a fresh snapshot at `GET /snapshot` (read-only,
154
+ loopback unless you pass `--host H --allow-remote`), so one machine can ask another for its health.
155
+ no auth: bind to localhost or a tailnet IP only.
156
+ - The same server answers `GET /metrics` in Prometheus text format, running the shipped rules on a fresh snapshot:
157
+ `stack_guardian_finding{code,severity,node}` (1 while raised, 0 once resolved),
158
+ `stack_guardian_worst_severity` (0 OK, 1 INFO, 2 WARN, 3 FAIL) and `stack_guardian_last_run_timestamp`.
159
+
160
+ ```
161
+ curl http://127.0.0.1:8788/metrics
162
+ ```
163
+ - `stack-guardian-mcp --nodes FILE [--ctx FILE] [--state FILE] [--ttl SECONDS]`: see [For coding agents](#for-coding-agents).
164
+
165
+ ## Writing a rule
166
+
167
+ ```python
168
+ from stack_guardian import finding as F
169
+
170
+ def ollama_up(snapshot, ctx):
171
+ if not snapshot["nodes"]["gpu-box"].get("reachable"):
172
+ return F.make_finding("ollama.unreachable", "missing", "FAIL", "gpu-box", "Ollama does not answer")
173
+ return None
174
+
175
+ CHECKS = [("ollama.up", ollama_up)]
176
+ ```
177
+
178
+ Drop the file in a folder and point `--checks-dir` at it. A full example with a fix is in
179
+ [examples/checks.d/20_ollama_fix.py](examples/checks.d/20_ollama_fix.py). A rule may also return a list of findings,
180
+ one per node.
181
+
182
+ ## Examples
183
+
184
+ | file | what it is |
185
+ |---|---|
186
+ | `examples/nodes.json` | three nodes: a local one with GPUs, two remote ones over the tailnet |
187
+ | `examples/ctx.json` | rule settings: which model gpu-box-2 should hold |
188
+ | `examples/snapshot.json` | an offline snapshot on which every shipped rule fires once, for trying `doctor` and `--fix` |
189
+ | `examples/lanes.json` | the lanes `restart_lane` is allowed to run |
190
+ | `examples/checks.d/20_ollama_fix.py` | a custom rule whose finding carries a fix |
191
+
192
+ ## Test
193
+
194
+ ```
195
+ pip install pytest
196
+ python -m pytest -q
197
+ ```
198
+
199
+ CI runs this on Ubuntu, Windows and macOS with Python 3.11, plus a privacy scan of every tracked file.
200
+
201
+ ## License
202
+
203
+ MIT