Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions skills/audit_docs/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
name: audit-docs
description: Audit docs/api/*.rst files across repos for broken module paths, missing classes, and stale autosummary entries.
user-invocable: true
---

Scan Sphinx API documentation across PyAutoFit, PyAutoGalaxy, and PyAutoLens for broken `.. currentmodule::` paths and stale `.. autosummary::` class references. Optionally auto-fix broken entries.

A **PyAutoHeart** check — documentation correctness is part of the validation surface Heart owns. The audit is read-only; auto-fix is opt-in and confined to the docs files it flags.

## Usage

```
/audit-docs # audit all three repos (default)
/audit-docs PyAutoLens # audit one repo
/audit-docs PyAutoGalaxy PyAutoFit # audit specific repos
/audit-docs --fix # audit all repos and auto-fix broken references
/audit-docs --fix PyAutoLens # fix one repo
```

## Repo Mapping

| Argument | Docs Directory |
|----------|---------------|
| `PyAutoFit` | `./PyAutoFit/docs/api/` |
| `PyAutoGalaxy` | `./PyAutoGalaxy/docs/api/` |
| `PyAutoLens` | `./PyAutoLens/docs/api/` |

All paths are relative to the workspace root.

## Steps

### 1. Determine which repos to audit

- **Default: audit ALL three repos.** Cross-package references (e.g. PyAutoLens docs referencing autoarray modules) mean a change in one library can break another library's docs.
- Only audit a subset if the user explicitly passes repo names as arguments.

### 2. Parse RST files

For each repo, glob `docs/api/*.rst` and extract:

- Every `.. currentmodule:: <module_path>` directive (record file, line number, module path)
- Every entry in `.. autosummary::` blocks (record file, line number, class/function name, and which `currentmodule` it falls under)

Build a list of `(file, line, module_path, class_name)` tuples.

### 3. Validate module paths

For each unique `currentmodule` path, test whether it is importable:

```bash
NUMBA_CACHE_DIR=/tmp/numba_cache MPLCONFIGDIR=/tmp/matplotlib python -c "import <module_path>"
```

Batch multiple imports into a single Python invocation for speed:

```python
import importlib, sys
modules = ["autoarray.inversion.mesh.image_mesh", "autofit", ...]
for m in modules:
try:
importlib.import_module(m)
print(f"OK {m}")
except ImportError as e:
print(f"FAIL {m}: {e}")
```

Record which modules pass and which fail.

### 4. Validate class/function names

For each autosummary entry whose parent module is importable, verify the name exists:

```python
import importlib
mod = importlib.import_module("<module_path>")
if hasattr(mod, "<ClassName>"):
print(f"OK <module_path>.<ClassName>")
else:
print(f"MISSING <module_path>.<ClassName>")
```

Again, batch into a single Python invocation.

### 5. Discover undocumented exports

For each importable `currentmodule`, list what the module actually exports:

```python
mod = importlib.import_module("<module_path>")
exports = [name for name in dir(mod) if not name.startswith("_") and isinstance(getattr(mod, name), type)]
```

Compare against the documented autosummary entries. Flag any class that exists in the module but is NOT listed in the docs. These are **suggestions**, not errors — some classes are intentionally internal.

### 6. Attempt to suggest fixes for broken modules

When a module path fails to import, try common renames to find the correct path. For each broken module `a.b.c.d`:

1. Try importing `a.b.c` — if that works and has attribute `d`, the correct currentmodule is `a.b.c` (and `d` should be in the autosummary list)
2. Search for the leaf name `d` elsewhere in the package:
```python
# Find modules containing the leaf name
import pkgutil, importlib
root = importlib.import_module(module_path.split(".")[0])
for importer, modname, ispkg in pkgutil.walk_packages(root.__path__, root.__name__ + "."):
try:
mod = importlib.import_module(modname)
if hasattr(mod, leaf_name):
print(f"SUGGESTION: {modname}")
except: pass
```
3. Present suggestions in the report.

### 7. Report results

Display a per-file summary table:

```
PyAutoLens/docs/api/pixelization.rst
OK line 16: currentmodule autolens
OK line 23: autolens.Pixelization
X BROKEN MODULE line 28: autoarray.inversion.pixelization.image_mesh
-> Suggested fix: autoarray.inversion.mesh.image_mesh
X MISSING CLASS line 51: Voronoi (not found in autoarray.inversion.mesh.mesh)
o NOT DOCUMENTED autoarray.inversion.mesh.mesh.KNearestNeighbor

Summary:
Modules checked: 12 (10 OK, 2 broken)
Classes checked: 45 (43 OK, 2 missing)
Undocumented exports: 8 (suggestions only)
```

### 8. Auto-fix (if --fix flag)

When `--fix` is specified:

1. **Broken module paths with a single suggestion**: Replace the `currentmodule` directive with the suggested path. Show the diff.
2. **Missing class references**: Remove the entry from the autosummary block. Show what was removed.
3. **Undocumented exports**: Do NOT auto-add these. Just list them as suggestions for the user to review.

After fixing, re-run the validation (steps 3-4) to confirm all references now resolve.

### 9. Post results to GitHub issue (optional)

If `PyAutoMind/active.md` contains an active issue URL, offer to post the audit summary as a comment. Use the format:

```bash
gh issue comment <number> --repo <owner/repo> --body "$(cat <<'AUDIT_EOF'
## Docs API Audit — <YYYY-MM-DD>

| Repo | Modules OK | Modules Broken | Classes OK | Classes Missing | Undocumented |
|------|-----------|---------------|-----------|----------------|-------------|
| PyAutoFit | X | Y | X | Y | Z |
| PyAutoGalaxy | X | Y | X | Y | Z |
| PyAutoLens | X | Y | X | Y | Z |

<details if any failures>
AUDIT_EOF
)"
```

## Notes

- Always set `NUMBA_CACHE_DIR=/tmp/numba_cache MPLCONFIGDIR=/tmp/matplotlib` before Python import checks to avoid cache permission errors.
- Top-level namespace packages (e.g. `autolens`, `autogalaxy`, `autofit`) re-export many classes from lower-level packages. A `currentmodule:: autolens` with `Pixelization` in the autosummary is valid if `autolens.Pixelization` resolves, even though the class is defined in `autoarray`.
- Some RST files legitimately reference modules from other packages (e.g. PyAutoLens docs referencing `autoarray.inversion.regularization`). This is expected and should not be flagged as an error — only flag it if the import actually fails.
- The `Voronoi` mesh class was removed from autoarray but may still appear in older docs. This is a known pattern — always remove dead references rather than leaving them.
- Duplicate entries in autosummary blocks (e.g. `Voronoi` listed twice) should be flagged and deduplicated.
132 changes: 132 additions & 0 deletions skills/cli_noise_clean/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
name: cli-noise-clean
description: Audit CLI output from tests and workspace scripts for warnings, stray prints, and library noise, then report fixes.
user-invocable: true
---

Audit CLI output across PyAuto repos for noise — warnings, stray print statements, verbose logging, and third-party library messages — then report what needs fixing.

A **PyAutoHeart** check — output-noise classification is part of the validation surface Heart owns. It reports; it does not apply the fixes itself.

## Usage

```
/cli_noise_clean # full audit: pytest + workspace scripts
/cli_noise_clean pytest # pytest collection + short run only
/cli_noise_clean scripts # workspace scripts only
```

## Environment Variables

All workspace script runs use these to keep execution fast:

```bash
PYAUTO_TEST_MODE=2
PYAUTO_WORKSPACE_SMALL_DATASETS=1
PYAUTO_DISABLE_JAX=0 # JAX ON so we catch JAX-specific noise
```

## Steps

### 1. Determine audit scope

- **Default (no argument):** run both pytest and workspace script audits
- **`pytest`:** skip workspace scripts
- **`scripts`:** skip pytest

### 2. Pytest noise audit

For each library repo (PyAutoConf, PyAutoFit, PyAutoArray, PyAutoGalaxy, PyAutoLens):

```bash
cd <repo_path>
python -m pytest test_<pkg>/ -x -q --co 2>&1 | grep -v "^test_\|^<\|^$\|^=\|collected"
```

This captures noise emitted during **test collection** (import-time warnings, JAX init, SQLAlchemy mapper config). Then run a small subset of actual tests to catch runtime warnings:

```bash
python -m pytest test_<pkg>/ -x -q --tb=no -W all 2>&1 | head -100
```

Collect all warning/noise lines and deduplicate.

### 3. Workspace script noise audit

Run representative scripts from each workspace. These exercise real code paths (modeling, simulation, plotting) that unit tests may skip.

**Scripts to run:**

| Workspace | Scripts |
|-----------|---------|
| `autolens_workspace` | `imaging/simulators/start_here.py`, `imaging/modeling/start_here.py` |
| `autogalaxy_workspace` | `imaging/simulators/start_here.py`, `imaging/modeling/start_here.py` |
| `autofit_workspace` | `howtofit/chapter_1/tutorial_1_models.py` |

For each script:

```bash
cd <workspace_path>
PYAUTO_TEST_MODE=2 PYAUTO_WORKSPACE_SMALL_DATASETS=1 \
NUMBA_CACHE_DIR=/tmp/numba_cache MPLCONFIGDIR=/tmp/matplotlib \
python <script> 2>&1 | grep -viE "^(result|model|log_likelihood|figure|saved)" | head -80
```

Capture stderr separately to isolate library warnings from expected stdout:

```bash
PYAUTO_TEST_MODE=2 PYAUTO_WORKSPACE_SMALL_DATASETS=1 \
NUMBA_CACHE_DIR=/tmp/numba_cache MPLCONFIGDIR=/tmp/matplotlib \
python <script> 2>/tmp/cli_noise_stderr.txt 1>/tmp/cli_noise_stdout.txt
cat /tmp/cli_noise_stderr.txt
```

### 4. Classify noise

Group findings into categories:

| Category | Example | Fix |
|----------|---------|-----|
| **Third-party warnings** | JAX CUDA plugin, numpy deprecation | `filterwarnings` in `pyproject.toml` |
| **ORM/DB warnings** | SQLAlchemy relationship overlaps | Fix relationship definitions |
| **Stray print()** | VRAM profiling, aggregator output | Convert to `logger.debug()` or gate behind verbosity |
| **Verbose logging** | INFO-level autoarray messages | Set log level to WARNING in conftest.py |
| **Docstring warnings** | Badly formatted numpydoc | Fix the docstring |
| **JAX compilation** | Tracing/compilation messages during modeling | Filter or configure JAX logging level |

### 5. Report

Output a summary table:

```
CLI Noise Audit — <date>

Noise sources found: N

| # | Category | Source | Message (truncated) | Repo | Fix |
|---|----------------|---------------------------------|----------------------------------|------------|------------------------|
| 1 | Third-party | jax_plugins.xla_cuda12 | cuda_plugin_extension not found | all | pyproject filterwarning |
| 2 | ORM warning | autofit/database/model/fit.py | relationship will copy column | PyAutoFit | add overlaps param |
| ... |

New since last audit: M
Previously seen: K
```

If this is a follow-up audit (previous results exist in `/tmp/cli_noise_baseline.txt`), diff against the baseline and highlight new entries.

### 6. Save baseline

Save the current findings to `/tmp/cli_noise_baseline.txt` so future runs can detect regressions:

```bash
# Format: category|source|message_hash|repo
echo "<findings>" > /tmp/cli_noise_baseline.txt
```

## Notes

- This skill is **read-only** — it reports noise but does not fix it. The operator decides what to fix.
- JAX is intentionally left enabled (`PYAUTO_DISABLE_JAX=0`) because JAX-specific noise is a primary target.
- Modeling scripts with `PYAUTO_TEST_MODE=2` exit after the first likelihood evaluation, so they run in seconds but still exercise the full import + setup + first-iteration path.
- If a script hangs or takes more than 60 seconds, kill it and note the timeout in the report.
Loading