Metadata-Version: 2.4
Name: nocando
Version: 0.2.0
Summary: Pre-flight environment-semantics linter for ML workloads — catches code/hardware mismatches (e.g. scikit-learn on a GPU runtime) before you occupy the hardware.
Author: Phinn Markson
Project-URL: Homepage, https://github.com/phinnphace/nocando
Project-URL: Repository, https://github.com/phinnphace/nocando
Project-URL: Issues, https://github.com/phinnphace/nocando/issues
Keywords: machine-learning,linter,colab,gpu,static-analysis,mlops
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Operating System :: OS Independent
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: pdf
Requires-Dist: pypdf>=4.0; extra == "pdf"
Dynamic: license-file

# nocando

A pre-flight **environment-semantics linter** for ML workloads. It answers one
question *before* you occupy hardware — in either direction:

> Is the code you wrote coherent with the environment you declared?

That covers CPU-only workloads parked on GPUs (waste), unconditional
`.cuda()` calls headed for a CPU runtime (crash at minute 40), CUDA-requiring
libraries on Apple Silicon (import failure), and wrong-generation hardware
(flash-attn needs compute capability >= 8.0; a Colab T4 is 7.5). The unit of
analysis is the *mismatch*. Deliberate slow-on-CPU choices are not flagged —
can't legislate morality.

Syntax linters know your language. Type checkers know your data shapes.
Nothing knows that `from sklearn.svm import SVC` plus "Runtime: T4" is an
incoherent sentence — the constraint lives at the intersection of
library × hardware × platform, and nobody owns intersections. `nocando` owns
this one. Zero LLM calls, zero telemetry, pure static analysis.

Certified by Ted (stoop tabby, QA).

## Why

Wasted accelerator time is deadweight loss: a scikit-learn grid search on a
Colab T4 produces heat, not gradients, for however many hours you sit there.
The knowledge to prevent this already exists — scattered across Stack threads
and subreddits, indexed by *symptom*, findable only after you've failed.
`nocando` inverts the indexing: it audits your declared intent (code + runtime)
at the moment of declaration.

## Usage

```bash
python nocando.py train.py --runtime t4
python nocando.py analysis.ipynb --runtime a100
python nocando.py train.py                 # autodetect via nvidia-smi / MPS
python nocando.py train.py --runtime t4 --json   # machine-readable, for CI
```

In Colab, one cell before your run:

```python
!wget -q https://<your-host>/nocando.py
!python nocando.py /content/drive/MyDrive/train.py --runtime t4
```

## Verdicts

| Verdict | Exit | Meaning |
|---|---|---|
| ✓ CLEARED FOR TAKEOFF | 0 | Accelerator request is coherent with this code |
| △ WILL RUN, BUT | 1 | Runs, but the allocation is wasteful or unconfigured (e.g. torch imported, no `.to(device)`; XGBoost without `device='cuda'`) |
| ✗ NO CAN DO | 2 | Nothing in this code can address the requested accelerator |

Design constraint: the false-positive budget is ~zero (an advisor that
interrupts wrongly gets disabled in a week). So verdicts are conservative:

- **BLOCK** fires only on true incoherence: no GPU-capable library at all,
  a hard CUDA requirement in a non-CUDA environment, unguarded `cuda`
  placement on a CPU runtime, or a compute-capability generation mismatch.
- A script that is one config line away from coherent (XGBoost, LightGBM,
  CatBoost, spaCy) gets **ADVISE**, never BLOCK.
- `numpy`/`scipy` are treated as *ancillary* — present in everything, never
  the story — and can't independently drive a verdict.

## Capability map

Hand-verified deterministic tier covering the common catastrophic cases:
CPU-only libraries (scikit-learn, pandas, statsmodels, NLTK, gensim,
Prophet…), conditionally-GPU libraries (XGBoost, LightGBM, CatBoost, spaCy),
and native accelerator frameworks (PyTorch, TensorFlow, JAX, RAPIDS,
transformers). Extend or override with `--map your_map.json`.

## Intake: the constraint ledger (LEDGER.md)

The audit is only as good as the declared intent, so the package now fronts
with a constraint-ledger protocol (`LEDGER.md`): goal, must-stay, must-not,
and a disposability default stating that anything unlisted — including the
entire current implementation — is negotiable. It prevents the failure mode
where an assistant promotes your current attempt into a requirement.

Participation is optional by design. When no ledger is provided, **inference
mode** applies: the auditor constructs a provisional ledger from evidence,
tags every entry `[inferred]`, and asks the single cheapest high-leverage
ratification question before any expensive work. The declared and inferred
ledgers are the same object at different confidence levels — participation
buys speed, never access. (nocando already does this at the code layer:
`--runtime` undeclared triggers autodetection.)

## Roadmap (tier 2)

The deterministic tier handles "can it." The agent tier handles "should it":

- workload sizing (dataset dims, params → rough FLOPs / VRAM estimate)
- allocation options with time/energy tradeoffs ("your ask is X; options A/B/C")
- syllabus/org aperture: audit a course's notebooks or a team's repo in batch
- retrieval-grounded advisories synthesized from the distributed corpus,
  reserved for cases where being occasionally wrong is survivable
- ledger-first sessions: intake (declared or inferred) -> coherence audit ->
  allocation options, in that order, always

## Tests

`tests/` contains the canonical fixtures:

- `tuesday_night.py` — sklearn SVC + GridSearchCV on a declared T4 → **BLOCK**
- `torch_configured.py` — torch with device placement → **PASS**
- `torch_forgot_device.py` — torch, no device call → **ADVISE**
- `xgb_default.py` — XGBoost with default (CPU) params → **ADVISE**
- `svm_demo.ipynb` — notebook parsing incl. `%magic`/`!shell` stripping → **BLOCK**
- `cuda_on_cpu.py` — unconditional `.to("cuda")` on a CPU runtime → **BLOCK**
- `guarded_cuda.py` — cuda placement behind `is_available()` guard → **PASS**
- `flash_on_t4.py` — flash-attn on a T4 (cc 7.5 < 8.0) → **BLOCK**; same file on A100 → **PASS**
- `bnb_on_mac.py` — bitsandbytes on Apple Silicon (MPS) → **BLOCK**
