aetherscan 1.0.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.
- aetherscan-1.0.0/.claude/skills/aetherscan-repo-context/SKILL.md +283 -0
- aetherscan-1.0.0/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- aetherscan-1.0.0/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- aetherscan-1.0.0/.github/workflows/auto-assign-author.yml +51 -0
- aetherscan-1.0.0/.github/workflows/claude-code-review.yml +78 -0
- aetherscan-1.0.0/.github/workflows/claude-contribution-check.yml +136 -0
- aetherscan-1.0.0/.github/workflows/claude-dependency-check.yml +181 -0
- aetherscan-1.0.0/.github/workflows/claude-flaky-test-tracker.yml +151 -0
- aetherscan-1.0.0/.github/workflows/claude-issue-triage.yml +80 -0
- aetherscan-1.0.0/.github/workflows/claude-release-notes.yml +129 -0
- aetherscan-1.0.0/.github/workflows/claude-style-check.yml +217 -0
- aetherscan-1.0.0/.github/workflows/claude-update-docs.yml +379 -0
- aetherscan-1.0.0/.github/workflows/claude.yml +236 -0
- aetherscan-1.0.0/.github/workflows/pre-commit.yml +55 -0
- aetherscan-1.0.0/.github/workflows/release.yml +286 -0
- aetherscan-1.0.0/.github/workflows/sync-pr-labels.yml +170 -0
- aetherscan-1.0.0/.github/workflows/tests.yml +62 -0
- aetherscan-1.0.0/.gitignore +68 -0
- aetherscan-1.0.0/.pre-commit-config.yaml +46 -0
- aetherscan-1.0.0/AI_POLICY.md +31 -0
- aetherscan-1.0.0/CITATION.cff +31 -0
- aetherscan-1.0.0/CLAUDE.md +66 -0
- aetherscan-1.0.0/CODEOWNERS +7 -0
- aetherscan-1.0.0/CODE_OF_CONDUCT.md +83 -0
- aetherscan-1.0.0/CONTRIBUTING.md +622 -0
- aetherscan-1.0.0/KNOWN_ISSUES.md +645 -0
- aetherscan-1.0.0/LICENSE +13 -0
- aetherscan-1.0.0/PKG-INFO +1187 -0
- aetherscan-1.0.0/README.md +1134 -0
- aetherscan-1.0.0/SECURITY.md +204 -0
- aetherscan-1.0.0/aetherscan.def +64 -0
- aetherscan-1.0.0/benchmarks/README.md +549 -0
- aetherscan-1.0.0/benchmarks/_common.py +76 -0
- aetherscan-1.0.0/benchmarks/bench_datagen.py +189 -0
- aetherscan-1.0.0/benchmarks/bench_db_index_shapes.py +435 -0
- aetherscan-1.0.0/benchmarks/bench_gpu.py +389 -0
- aetherscan-1.0.0/benchmarks/bench_injection.py +71 -0
- aetherscan-1.0.0/benchmarks/bench_injection_index.py +314 -0
- aetherscan-1.0.0/benchmarks/bench_input_pipeline.py +564 -0
- aetherscan-1.0.0/benchmarks/bench_latent_gif.py +382 -0
- aetherscan-1.0.0/benchmarks/bench_lognorm_downsample.py +92 -0
- aetherscan-1.0.0/benchmarks/bench_normality.py +93 -0
- aetherscan-1.0.0/benchmarks/bench_pfb_vs_spline.py +92 -0
- aetherscan-1.0.0/benchmarks/bench_rf.py +170 -0
- aetherscan-1.0.0/benchmarks/parse_xplane_occupancy.py +82 -0
- aetherscan-1.0.0/docs/ARCHITECTURE.md +294 -0
- aetherscan-1.0.0/docs/BENCHMARKING.md +281 -0
- aetherscan-1.0.0/docs/CONFIG_AND_CLI.md +457 -0
- aetherscan-1.0.0/docs/DATABASE.md +465 -0
- aetherscan-1.0.0/docs/GITHUB_AUTOMATION.md +150 -0
- aetherscan-1.0.0/docs/GPU_RUNTIME_GUIDE.md +242 -0
- aetherscan-1.0.0/docs/INFERENCE_PIPELINE.md +508 -0
- aetherscan-1.0.0/docs/MODELS.md +266 -0
- aetherscan-1.0.0/docs/PREPROCESSING.md +357 -0
- aetherscan-1.0.0/docs/README.md +19 -0
- aetherscan-1.0.0/docs/RELEASE.md +274 -0
- aetherscan-1.0.0/docs/RUNTIME_SERVICES.md +273 -0
- aetherscan-1.0.0/docs/TESTING.md +286 -0
- aetherscan-1.0.0/docs/TRAINING_PIPELINE.md +888 -0
- aetherscan-1.0.0/docs/assets/aetherscan-banner.png +0 -0
- aetherscan-1.0.0/docs/assets/github-citation-button.png +0 -0
- aetherscan-1.0.0/environment.yml +74 -0
- aetherscan-1.0.0/pyproject.toml +191 -0
- aetherscan-1.0.0/requirements-container.txt +45 -0
- aetherscan-1.0.0/src/aetherscan/__init__.py +45 -0
- aetherscan-1.0.0/src/aetherscan/benchmark.py +179 -0
- aetherscan-1.0.0/src/aetherscan/candidate_figures.py +328 -0
- aetherscan-1.0.0/src/aetherscan/cli.py +3032 -0
- aetherscan-1.0.0/src/aetherscan/config.py +1002 -0
- aetherscan-1.0.0/src/aetherscan/dashboard.py +709 -0
- aetherscan-1.0.0/src/aetherscan/dashboard_cli.py +51 -0
- aetherscan-1.0.0/src/aetherscan/dashboard_launcher.py +194 -0
- aetherscan-1.0.0/src/aetherscan/data_generation.py +1448 -0
- aetherscan-1.0.0/src/aetherscan/db/__init__.py +21 -0
- aetherscan-1.0.0/src/aetherscan/db/db.py +2789 -0
- aetherscan-1.0.0/src/aetherscan/hf_hub.py +547 -0
- aetherscan-1.0.0/src/aetherscan/inference.py +912 -0
- aetherscan-1.0.0/src/aetherscan/inference_viz.py +1494 -0
- aetherscan-1.0.0/src/aetherscan/latent_gif.py +512 -0
- aetherscan-1.0.0/src/aetherscan/latent_variants.py +352 -0
- aetherscan-1.0.0/src/aetherscan/logger/__init__.py +21 -0
- aetherscan-1.0.0/src/aetherscan/logger/logger.py +467 -0
- aetherscan-1.0.0/src/aetherscan/logger/slack_handler.py +760 -0
- aetherscan-1.0.0/src/aetherscan/main.py +1269 -0
- aetherscan-1.0.0/src/aetherscan/manager/__init__.py +21 -0
- aetherscan-1.0.0/src/aetherscan/manager/manager.py +781 -0
- aetherscan-1.0.0/src/aetherscan/models/__init__.py +21 -0
- aetherscan-1.0.0/src/aetherscan/models/random_forest.py +171 -0
- aetherscan-1.0.0/src/aetherscan/models/vae.py +849 -0
- aetherscan-1.0.0/src/aetherscan/monitor/__init__.py +19 -0
- aetherscan-1.0.0/src/aetherscan/monitor/monitor.py +935 -0
- aetherscan-1.0.0/src/aetherscan/pfb.py +161 -0
- aetherscan-1.0.0/src/aetherscan/preprocessing.py +2718 -0
- aetherscan-1.0.0/src/aetherscan/rf_metrics.py +100 -0
- aetherscan-1.0.0/src/aetherscan/round_data.py +932 -0
- aetherscan-1.0.0/src/aetherscan/run_state.py +277 -0
- aetherscan-1.0.0/src/aetherscan/seeding.py +160 -0
- aetherscan-1.0.0/src/aetherscan/shap_parallel.py +238 -0
- aetherscan-1.0.0/src/aetherscan/tag_guards.py +206 -0
- aetherscan-1.0.0/src/aetherscan/train.py +7681 -0
- aetherscan-1.0.0/tests/conftest.py +231 -0
- aetherscan-1.0.0/tests/fixtures/ed_real_slice.npz +0 -0
- aetherscan-1.0.0/tests/integration/conftest.py +63 -0
- aetherscan-1.0.0/tests/integration/test_inference_smoke.py +108 -0
- aetherscan-1.0.0/tests/integration/test_model_behavior.py +143 -0
- aetherscan-1.0.0/tests/integration/test_seeding_determinism.py +145 -0
- aetherscan-1.0.0/tests/integration/test_train_smoke.py +98 -0
- aetherscan-1.0.0/tests/unit/test_benchmark.py +432 -0
- aetherscan-1.0.0/tests/unit/test_candidate_figures.py +158 -0
- aetherscan-1.0.0/tests/unit/test_cli_help_sync.py +64 -0
- aetherscan-1.0.0/tests/unit/test_cli_validation.py +1144 -0
- aetherscan-1.0.0/tests/unit/test_config.py +152 -0
- aetherscan-1.0.0/tests/unit/test_dashboard.py +367 -0
- aetherscan-1.0.0/tests/unit/test_dashboard_cli.py +76 -0
- aetherscan-1.0.0/tests/unit/test_dashboard_launcher.py +192 -0
- aetherscan-1.0.0/tests/unit/test_data_generation.py +688 -0
- aetherscan-1.0.0/tests/unit/test_db.py +1121 -0
- aetherscan-1.0.0/tests/unit/test_hf_hub.py +627 -0
- aetherscan-1.0.0/tests/unit/test_inference.py +401 -0
- aetherscan-1.0.0/tests/unit/test_inference_viz.py +744 -0
- aetherscan-1.0.0/tests/unit/test_latent_gif.py +254 -0
- aetherscan-1.0.0/tests/unit/test_latent_traversal.py +230 -0
- aetherscan-1.0.0/tests/unit/test_latent_variants.py +238 -0
- aetherscan-1.0.0/tests/unit/test_logger.py +248 -0
- aetherscan-1.0.0/tests/unit/test_main.py +636 -0
- aetherscan-1.0.0/tests/unit/test_manager.py +266 -0
- aetherscan-1.0.0/tests/unit/test_models.py +515 -0
- aetherscan-1.0.0/tests/unit/test_monitor.py +363 -0
- aetherscan-1.0.0/tests/unit/test_pfb.py +246 -0
- aetherscan-1.0.0/tests/unit/test_preprocessing.py +1870 -0
- aetherscan-1.0.0/tests/unit/test_rf_metrics.py +105 -0
- aetherscan-1.0.0/tests/unit/test_round_data.py +1179 -0
- aetherscan-1.0.0/tests/unit/test_run_state.py +426 -0
- aetherscan-1.0.0/tests/unit/test_seeding.py +120 -0
- aetherscan-1.0.0/tests/unit/test_shap_parallel.py +152 -0
- aetherscan-1.0.0/tests/unit/test_tag_guards.py +245 -0
- aetherscan-1.0.0/tests/unit/test_train_accumulation.py +191 -0
- aetherscan-1.0.0/tests/unit/test_train_datasets.py +316 -0
- aetherscan-1.0.0/tests/unit/test_train_distribution.py +217 -0
- aetherscan-1.0.0/tests/unit/test_train_utils.py +1097 -0
- aetherscan-1.0.0/utils/benchmark_report.py +545 -0
- aetherscan-1.0.0/utils/fetch_run_outputs.sh +233 -0
- aetherscan-1.0.0/utils/find_optimal_configs.py +242 -0
- aetherscan-1.0.0/utils/get_system_info.sh +105 -0
- aetherscan-1.0.0/utils/hf_tag_release.py +107 -0
- aetherscan-1.0.0/utils/kill_pipeline.sh +282 -0
- aetherscan-1.0.0/utils/print_cli_help.py +76 -0
- aetherscan-1.0.0/utils/run_container.sh +129 -0
- aetherscan-1.0.0/utils/start_tmux_session.sh +76 -0
- aetherscan-1.0.0/utils/verify_train_test_files.py +195 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aetherscan-repo-context
|
|
3
|
+
description: Deep-dive context for working inside the Aetherscan repo — Breakthrough Listen's deep-learning SETI pipeline (Beta-VAE → Random Forest, multi-GPU). Use when developing, debugging, configuring, running, or reviewing changes to this codebase, or when answering questions about its install paths, CLI, config system, conventions, contribution workflow, or security practices.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Working in the Aetherscan Repo
|
|
7
|
+
|
|
8
|
+
Aetherscan is Breakthrough Listen's first end-to-end production-grade deep-learning pipeline for SETI at scale. It detects anomalies in radio spectrograms with technosignature-like characteristics by pairing a **beta-VAE** (dimensionality reduction / feature extraction) with a **Random Forest** ensemble (candidate detection). It is based on [Ma et al. 2023](https://arxiv.org/abs/2301.12670) and runs single-node data-parallel distributed training/inference.
|
|
9
|
+
|
|
10
|
+
> **Scope.** `CLAUDE.md` (repo root) holds the lean, always-on rules; this skill is the on-demand deep-dive — read it when a task needs more than the essentials. The authoritative sources are `README.md`, `CONTRIBUTING.md`, `SECURITY.md`, `KNOWN_ISSUES.md`, and `docs/`; when this file disagrees with them, they win and this file should be updated. **All paths below are relative to the repository root** (an agent's working directory).
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Entry Point & How to Run
|
|
15
|
+
|
|
16
|
+
`src/aetherscan/main.py` is the **primary** designated entry point for the pipeline. Non-development workflows should never call other scripts/modules directly — the one exception is `aetherscan-dashboard`, the console script for manual dashboard runs against a saved DB (see `dashboard_cli.py`). `main.py` dispatches to one of two subcommands via the first positional argument: `train` or `inference`.
|
|
17
|
+
|
|
18
|
+
There are two install paths off the same source tree:
|
|
19
|
+
|
|
20
|
+
| Path | Status | When | Launcher |
|
|
21
|
+
| ------------------------------------------- | ---------------------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------- |
|
|
22
|
+
| **NGC container** (Apptainer/SingularityCE) | Canonical; runs on both clusters; only option on Blackwell | Default | `./utils/run_container.sh python -m aetherscan.main {train\|inference} ...` |
|
|
23
|
+
| **Conda env** | Alternative; **Ampere only** | When containers aren't usable | `PYTHONPATH=src python -m aetherscan.main {train\|inference} ...` |
|
|
24
|
+
|
|
25
|
+
CLI flags are identical between the two paths; only the launcher differs. `PYTHONPATH=src` makes the `aetherscan` package importable from `src/` without a `pip install -e .`; the container sets `PYTHONPATH` automatically.
|
|
26
|
+
|
|
27
|
+
- **Container build:** `singularity build aetherscan-ngc25.02.sif aetherscan.def` (or `apptainer build ...`) — same `aetherscan.def` recipe builds with either runtime. Build on the cluster you intend to run on.
|
|
28
|
+
- **Conda env:** `conda env create -f environment.yml && conda activate aetherscan`
|
|
29
|
+
- **`utils/fetch_run_outputs.sh`** rsyncs one run's outputs from remote cluster node(s) to the local `outputs/` tree, selecting files by the universal `*_<save_tag>.*` suffix and renaming each to `<machine>_<basename>` (collision-free across nodes). `<train|inference> <save_tag> <machine>...`; `--all` adds train checkpoints/archive, `--db` pulls the SQLite DB into `outputs/data/db/`, `--dry-run`. Per-run logs are tag-scoped (`logs/aetherscan_<save_tag>.log`, since PR #221), so the script picks each run's log up by its tag like every other output; the inference branch is provisional pending the inference pipeline.
|
|
30
|
+
- **`utils/kill_pipeline.sh`** stops a running pipeline (main process + all worker children) from a separate shell on the same machine — works for both run modes, finds the process tree itself, and tries a graceful SIGTERM (lets `ResourceManager` close pools/SHM) before escalating to SIGKILL. When no main process is found, sweeps `{round_data_root}/*/producer.pid` for orphaned `RoundDataProducer` trees left by an ungraceful main-process death and reaps them. Assumes a single running instance. `--force` / `--dry-run` / `--timeout N` / `--round-data-root DIR`.
|
|
31
|
+
- **`utils/run_container.sh`** auto-detects apptainer vs singularity (Apptainer wins when both present), sets `--nv` for GPU passthrough, auto-loads `<repo>/.env`, and bind-mounts the repo + `AETHERSCAN_{DATA,MODEL,OUTPUT}_PATH` 1:1 so absolute paths persisted in the DB stay valid across host and container. `AETHERSCAN_EXTRA_BINDS` (comma-separated host paths) appends additional 1:1 binds for data outside the standard dirs (e.g. raw `.h5` files under `/datag` for inference); the runtime's native `SINGULARITY_BIND` / `APPTAINER_BIND` still pass through and are additive.
|
|
32
|
+
- **`utils/start_tmux_session.sh`** (optional) spins up a four-window tmux session — a single-pane `pipeline` working window plus three monitoring windows: `htop` (htop 75% / CPU-MEM ticker 25%), `nvidia-smi`, and `data` (four vertical panes: `/dev/shm`, then `tree` of data / models / outputs). Idempotent.
|
|
33
|
+
|
|
34
|
+
**Common invocations:**
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Train (container, canonical)
|
|
38
|
+
./utils/run_container.sh python -m aetherscan.main train --save-tag train
|
|
39
|
+
|
|
40
|
+
# Train (conda source, Ampere)
|
|
41
|
+
PYTHONPATH=src python -m aetherscan.main train --save-tag train
|
|
42
|
+
|
|
43
|
+
# Inference from a pre-processed .npy
|
|
44
|
+
./utils/run_container.sh python -m aetherscan.main inference \
|
|
45
|
+
--test-files real_filtered_LARGE_test_HIP15638.npy \
|
|
46
|
+
--encoder-path /path/to/vae_encoder.keras \
|
|
47
|
+
--rf-path /path/to/random_forest.joblib \
|
|
48
|
+
--config-path /path/to/config.json
|
|
49
|
+
|
|
50
|
+
# Inference from raw .h5 (triggers energy-detection preprocessing) — bind
|
|
51
|
+
# extra host paths if the .h5 files live outside the standard bind mounts
|
|
52
|
+
AETHERSCAN_EXTRA_BINDS=/datag ./utils/run_container.sh python -m aetherscan.main inference \
|
|
53
|
+
--inference-files complete_cadences_catalog.csv \
|
|
54
|
+
--encoder-path /path/to/vae_encoder.keras \
|
|
55
|
+
--rf-path /path/to/random_forest.joblib \
|
|
56
|
+
--config-path /path/to/config.json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`--inference-files` (raw `.h5` catalog) triggers the energy-detection preprocessing pipeline and takes precedence over `--test-files` (pre-processed `.npy`).
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Configuration & CLI
|
|
64
|
+
|
|
65
|
+
Hierarchical, dataclass-based config with a thread-safe singleton. Resolution order at command time:
|
|
66
|
+
|
|
67
|
+
1. **Defaults** — in `src/aetherscan/config.py`
|
|
68
|
+
2. **Environment variables** — for paths and secrets
|
|
69
|
+
3. **CLI flags** — override both on startup
|
|
70
|
+
|
|
71
|
+
At runtime, the singleton `Config` is read via `get_config()` and may be modified programmatically. See `docs/CONFIG_AND_CLI.md`.
|
|
72
|
+
|
|
73
|
+
**Secrets & paths** come from a `.env` file at the repo root (gitignored). Shell `export` takes precedence over `.env`. The container wrapper forwards `SLACK_*`, `AETHERSCAN_*`, and `HF_TOKEN` via `--env`; the source path loads the **whole** `.env` into `os.environ` at the top of `main.py` via python-dotenv.
|
|
74
|
+
|
|
75
|
+
```ini
|
|
76
|
+
# .env example
|
|
77
|
+
# Defaults to /datax/scratch/zachy/{data|models|outputs}/aetherscan; CLI flags override
|
|
78
|
+
AETHERSCAN_DATA_PATH=/path/to/data
|
|
79
|
+
AETHERSCAN_MODEL_PATH=/path/to/models
|
|
80
|
+
AETHERSCAN_OUTPUT_PATH=/path/to/outputs
|
|
81
|
+
# Optional: comma-separated extra host paths for run_container.sh to bind 1:1
|
|
82
|
+
AETHERSCAN_EXTRA_BINDS=/extra/host/paths
|
|
83
|
+
# Only needed for uploading model weights to the HuggingFace Hub (train --hf-upload);
|
|
84
|
+
# downloads (the inference default) hit a public repo and need no token
|
|
85
|
+
HF_TOKEN=your-huggingface-write-token
|
|
86
|
+
# Slack integration auto-disables if unset
|
|
87
|
+
SLACK_BOT_TOKEN=your-slack-bot-token
|
|
88
|
+
SLACK_CHANNEL=your-slack-channel
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**The CLI Reference in `README.md` is a tight source↔doc contract.** The three code blocks under `## CLI Reference` (Top-Level / Train / Inference Help) are pasted-verbatim argparse output. If `src/aetherscan/cli.py` changes (flags, help strings, subparsers), regenerate them from the repo root with:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
PYTHONPATH=src python utils/print_cli_help.py all
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`print_cli_help.py` imports only `aetherscan.config` and `aetherscan.cli` (pure stdlib, no TensorFlow/conda needed) and pins `COLUMNS=80` for deterministic wrapping. Replace each block verbatim, preserving each subsection's "Regenerate this output with ..." intro paragraph.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Project Structure
|
|
102
|
+
|
|
103
|
+
The tree below annotates the **source** layout. For the complete repository structure — root-level build/config files (`pyproject.toml`, `environment.yml`, `aetherscan.def`, `requirements-container.txt`, `.pre-commit-config.yaml`), governance docs (`CLAUDE.md`, `CONTRIBUTING.md`, `SECURITY.md`, `KNOWN_ISSUES.md`, `AI_POLICY.md`), and the `.claude/` and `.github/` directories — see the Project Structure tree in `CONTRIBUTING.md` (the canonical source).
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
src/aetherscan/
|
|
107
|
+
├── main.py # Entry point, command dispatch, GPU strategy setup (NCCL + fallback)
|
|
108
|
+
├── cli.py # Argument parsing, validation, config override
|
|
109
|
+
├── config.py # Configuration dataclasses & defaults (singleton)
|
|
110
|
+
├── train.py # Training orchestration, curriculum learning, checkpointing
|
|
111
|
+
├── round_data.py # Disk-backed (memmap) round datasets + background producer process
|
|
112
|
+
├── run_state.py # Persisted training-run manifest (stage-aware resume)
|
|
113
|
+
├── inference.py # Inference orchestration, candidate detection
|
|
114
|
+
├── inference_viz.py # End-of-run inference visualization suite
|
|
115
|
+
├── candidate_figures.py # Per-candidate figure renderer (TF-free; forkserver pool; called by inference_viz.py)
|
|
116
|
+
├── preprocessing.py # Loading / downsampling / log-normalization + energy detection
|
|
117
|
+
├── pfb.py # PFB static passband equalization (bandpass flattening)
|
|
118
|
+
├── data_generation.py # Synthetic signal injection — batched memmap workers + background producer
|
|
119
|
+
├── seeding.py # Root-seed stream derivation (reproducible train + inference runs)
|
|
120
|
+
├── benchmark.py # Always-on stage timing to the pipeline_stages table
|
|
121
|
+
├── dashboard.py # Streamlit live-monitoring dashboard (DB-driven)
|
|
122
|
+
├── dashboard_launcher.py # Spawns the headless dashboard subprocess (guarded)
|
|
123
|
+
├── dashboard_cli.py # Console entry point for manual dashboard runs (aetherscan-dashboard)
|
|
124
|
+
├── hf_hub.py # HuggingFace Hub artifact upload/download
|
|
125
|
+
├── tag_guards.py # Fail-early --save-tag dedup guards
|
|
126
|
+
├── rf_metrics.py # Pure RF eval-metric helper (persisted to training_stats by train.py)
|
|
127
|
+
├── shap_parallel.py # RF SHAP process-pool wrapper (TF-free; called by train.py)
|
|
128
|
+
├── latent_variants.py # Latent-representation variant catalogue + selection/calibration (TF-free; shared by train.py + inference.py)
|
|
129
|
+
├── latent_gif.py # Process-parallel latent-GIF frame renderer (TF-free; called by train.py)
|
|
130
|
+
├── models/{vae,random_forest}.py
|
|
131
|
+
├── db/db.py # Thread-safe SQLite, async queue-based writes, schema migration, supersede semantics
|
|
132
|
+
├── logger/ # Multi-handler logging + Slack integration
|
|
133
|
+
├── monitor/monitor.py # Background resource monitoring (CPU, RAM, GPU)
|
|
134
|
+
└── manager/manager.py # Resource lifecycle management (pools, shared memory)
|
|
135
|
+
utils/ # benchmark_report.py, fetch_run_outputs.sh,
|
|
136
|
+
# find_optimal_configs.py, get_system_info.sh,
|
|
137
|
+
# hf_tag_release.py, kill_pipeline.sh, print_cli_help.py,
|
|
138
|
+
# run_container.sh, start_tmux_session.sh,
|
|
139
|
+
# verify_train_test_files.py
|
|
140
|
+
docs/ # Full technical doc suite, one doc per pipeline surface —
|
|
141
|
+
# indexed in docs/README.md; start at docs/ARCHITECTURE.md
|
|
142
|
+
tests/ # Pytest suite: unit/ (CI surface) + gpu/cluster-marked
|
|
143
|
+
# integration/ smokes — see the "Testing" section below
|
|
144
|
+
benchmarks/ # Standalone benchmarks — CPU micro-benchmarks + a GPU
|
|
145
|
+
# benchmark (bench_gpu.py, container-only); not collected
|
|
146
|
+
# by pytest. See benchmarks/README.md + docs/BENCHMARKING.md
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Architecture Patterns (load-bearing)
|
|
152
|
+
|
|
153
|
+
- **Distributed training/inference** — Gradients sync via TF `MirroredStrategy` + NCCL AllReduce, with gradient accumulation for larger effective batches under low VRAM. All TensorFlow model ops **must** occur within `strategy.scope()`.
|
|
154
|
+
- **Cadence-aware composite loss** — beta-VAE reconstruction + β-weighted KL divergence + α-weighted true/false clustering (ON-ON / OFF-OFF proximity, ON-OFF separation for true signals; uniform for false).
|
|
155
|
+
- **Curriculum training** — progressive SNR difficulty with adaptive LR that decays on validation plateaus and resets each round; per-round checkpointing. A persisted run manifest (`run_state_{save_tag}.json`) drives fault-tolerant resume: an explicit stage machine (vae_rounds → vae_plots → rf_train → rf_plots → final_save) skips completed stages, and stale DB rows from failed attempts are marked superseded (never deleted).
|
|
156
|
+
- **Thread-safe singletons** — `Config`, `Database`, `ResourceManager`. Always use the accessors `get_config()`, `get_db()`, `get_manager()`; never instantiate directly.
|
|
157
|
+
- **Shared-memory zero-copy parallelism** — worker pools communicate via shared memory (no serialization). Allocate via `manager.create_shared_memory()`; ResourceManager owns cleanup. Only the **creator** may call `shm.unlink()`, never workers. Training-round datasets are disk-backed memmaps (`round_data.py`): workers write disjoint row ranges in-place, eliminating per-sample IPC; steady-state reads come from page cache.
|
|
158
|
+
- **Data holders** — `TrainDataHolder` / `VizDataHolder` (`train.py`) wrap memmap references (or arrays) with a lock. RF training reuses `TrainDataHolder` via `prepare_distributed_train_dataset`. Call `holder.clear()` after processing completes. (Inference encodes directly from numpy slices since #298 — no holder on that path.)
|
|
159
|
+
- **Background data producer** — `RoundDataProducer` (spawn-started process with its own worker pool) generates round k+1 while round k trains; registered with ResourceManager as a `ManagedProcess`. `CUDA_VISIBLE_DEVICES` is blanked so the producer tree never initializes CUDA; logging crosses the spawn boundary via a `QueueListener` relay.
|
|
160
|
+
- **Worker cleanup** — custom SIGTERM handlers free resources on interruption. **Never log inside SIGTERM handlers** (deadlock risk).
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Code Style & Conventions
|
|
165
|
+
|
|
166
|
+
Enforced by **ruff** (lint + format) via pre-commit; full config in `pyproject.toml` under `[tool.ruff]`.
|
|
167
|
+
|
|
168
|
+
- **Target**: Python 3.10 (lowest common denominator across the conda 3.10 and container 3.12 paths). **Line length 100** (formatter wraps; `E501` is intentionally ignored).
|
|
169
|
+
- **Modern typing** — every module starts with `from __future__ import annotations` (isort's `required-imports`/`I002` auto-inserts it). Use PEP 604 unions (`X | None`, not `Optional[X]`) and PEP 585 generics (`list[int]`, `dict[str, float]`, not `typing.List`/`Dict`). Annotate args **and** return types. The `UP` family auto-fixes legacy idioms.
|
|
170
|
+
- **Docstrings** — short, plain prose. No Sphinx/Google/Numpy section markers. One-liners are fine for self-evident helpers.
|
|
171
|
+
- **Logging** — `logger = logging.getLogger(__name__)`, f-strings for messages. `T20` rejects bare `print()` outside one-off `utils/` scripts (and the self-logging `slack_handler.py`); `G001`–`G003` reject `%`/`str.format()`/`+` pre-formatted log messages. The Slack handler attaches automatically when `SLACK_BOT_TOKEN` is set, so anything at `INFO+` may surface in Slack — keep messages information-dense and **free of secrets**.
|
|
172
|
+
- **Config access** — `get_config()` returns `Config | None`; the canonical idiom guards `if config is None: raise ValueError(...)` (None only happens if `init_config()` hasn't run — a programming error).
|
|
173
|
+
- **Dataclass mutable defaults** — always `field(default_factory=...)`, never a bare `[...]` (shared mutable state; `B`/bugbear flags it).
|
|
174
|
+
- **Retry/error-handling** — pipeline retry loops catch `KeyboardInterrupt` separately and re-raise, log with `logger.error`, then either retry after `time.sleep(retry_delay)` or `sys.exit(1)`. Resume is manifest-driven (no checkpoint hunting): the `TrainingRunState` manifest tells the new pipeline which rounds/stages already completed. Non-critical stages (plots) record failures without forcing a retry; `main.py` exits nonzero if they never recover. Reference: `train_command` / `inference_command`, `run_state.py`.
|
|
175
|
+
- **Naming** — descriptive full words (`num_training_rounds`, not `n`/`bs`). Single letters only in tight loops / math / indexing.
|
|
176
|
+
|
|
177
|
+
| Element | Convention | Example |
|
|
178
|
+
| ------------- | ----------- | ------------------------ |
|
|
179
|
+
| Classes | PascalCase | `DataGenerator` |
|
|
180
|
+
| Functions | snake_case | `run_training_pipeline` |
|
|
181
|
+
| Constants | UPPER_SNAKE | `MAX_RETRIES` |
|
|
182
|
+
| Private | \_prefix | `_init_worker` |
|
|
183
|
+
| Config fields | snake_case | `per_replica_batch_size` |
|
|
184
|
+
|
|
185
|
+
**Grep-friendly inline comment markers** (used consistently): `# TODO:` (actionable work), `# NOTE:` (rationale/question), `# FIX:` (known issue, no time now), `# BUG:` (known bug, often with workaround), `# TEST:` (behavior to verify — now backed by the `tests/` suite). Prefer `# NOTE:` over `# TODO:` when there's no obvious action.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Testing
|
|
190
|
+
|
|
191
|
+
The `tests/` suite splits along a hardware axis:
|
|
192
|
+
|
|
193
|
+
- **`tests/unit/`** — fast, hardware-independent, one `test_<module>.py` per source module. This is the CI surface; everything here must pass on a CPU-only runner.
|
|
194
|
+
- **`tests/integration/`** — `gpu`/`cluster`-marked tests that need real GPUs and cluster-resident data/models; not run in CI. Two end-to-end smokes (`test_train_smoke.py`, `test_inference_smoke.py`) launch `python -m aetherscan.main ...` as a real subprocess (hours of wall time each); the model-behavior gate (`test_model_behavior.py`, issue #139 Gate 2) instead drives generation and scoring in-process against the persisted VAE+RF (minutes, not hours).
|
|
195
|
+
|
|
196
|
+
**Default selection — matches what CI runs** (`.github/workflows/tests.yml`, on Python 3.10, 3.11, and 3.12), no GPUs or cluster data needed. CI adds an explicit `and not integration` as a defense-in-depth leak-guard (see the markers table below), so the exact CI expression is `pytest -m "not gpu and not cluster and not integration" -q`; today the two expressions select the same set because every `integration` test is also `gpu`+`cluster`.
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
pytest -m "not gpu and not cluster" -q
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`pyproject.toml`'s `[tool.pytest.ini_options]` sets `pythonpath = ["src"]`, so **pytest needs no `PYTHONPATH=src` prefix** (unlike running `main.py` from source); it also sets `testpaths = ["tests"]` and `--strict-markers` (a typo'd marker is a collection error, not a silently-ignored one).
|
|
203
|
+
|
|
204
|
+
**Markers** (declared in `pyproject.toml`; `--strict-markers` rejects undeclared ones):
|
|
205
|
+
|
|
206
|
+
| Marker | Meaning | In default selection? |
|
|
207
|
+
| ------------- | ------------------------------------------------ | ----------------------- |
|
|
208
|
+
| `slow` | Slower CPU tests (e.g. builds real TF graphs) | **Yes** — CI runs them |
|
|
209
|
+
| `gpu` | Needs one or more physical GPUs | No |
|
|
210
|
+
| `cluster` | Needs cluster-resident data/models (blpc3/bla0) | No |
|
|
211
|
+
| `integration` | End-to-end subprocess runs; **skips isolation** | No — also `gpu`+`cluster`; CI excludes by marker too as a leak-guard |
|
|
212
|
+
|
|
213
|
+
**Isolation.** The autouse `aetherscan_isolated_env` fixture in `tests/conftest.py` wraps every non-integration test: it points `AETHERSCAN_{DATA,MODEL,OUTPUT}_PATH` at a fresh `tmp_path` tree, deletes `SLACK_BOT_TOKEN`/`SLACK_CHANNEL` (tests must never talk to Slack), resets all five singletons (`Config`, `Database`, `Logger`, `ResourceManager`, `ResourceMonitor`) via their `_reset()` hooks, then on teardown stops any leaked background threads/pools and restores the snapshotted SIGINT/SIGTERM handlers and stdout/stderr. Net effect: tests never touch real data and can't leak state into one another. Integration tests are exempt — they inherit the real environment and run the pipeline as a subprocess.
|
|
214
|
+
|
|
215
|
+
**Discipline.** Run the suite (or the subset you can) before claiming a change works, and **ship unit tests with new logic** — every behavior change should land tests under `tests/unit/` in the matching `test_<module>.py` (create it if the module is new).
|
|
216
|
+
|
|
217
|
+
**Gotcha.** Most unit modules import TensorFlow at collection time, so a bare `pytest` needs the full dependency stack (CI installs `tensorflow-cpu==2.17.*` plus the container requirements). If that stack isn't available locally, run the TF-free subset you can — e.g. `pytest tests/unit/test_config.py -q` — and **say exactly what you ran** rather than claiming the whole suite passed.
|
|
218
|
+
|
|
219
|
+
Deep dive: `docs/TESTING.md` covers the full layout, the synthetic data factories, the coverage-and-deliberate-gaps notes (`logger` / `slack_handler` / `benchmark` stage-timing wiring), CI specifics, how to run the cluster smokes, and the adding-tests checklist.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Contribution Workflow
|
|
224
|
+
|
|
225
|
+
> **All issues are actionable, and all PRs must be tied to an existing issue.** Read `AI_POLICY.md` before doing AI-assisted work — the project has strict AI-usage rules.
|
|
226
|
+
|
|
227
|
+
1. **Discussion first** — check for existing PRs/issues/discussions; otherwise open a [GitHub Discussion](https://github.com/zachtheyek/Aetherscan/discussions) or Slack thread. "Drive-by" issues with no prior discussion may be closed.
|
|
228
|
+
2. **Open an issue** via the template; Claude auto-triages and labels it.
|
|
229
|
+
3. **Feature branch** — `category/description` with prefix: `feature/` (new functionality), `hotfix/` (bug fixes), `misc/` (housekeeping), `claude/` (reserved for the Claude assistant).
|
|
230
|
+
4. **Implement** — focused commits, pass all pre-commit hooks, follow `pyproject.toml` style.
|
|
231
|
+
5. **PR** — rebase (not merge) onto `master`; **all commits need verified GPG signatures**; fill the PR template; link the issue via the Development sidebar or `Closes #N` / `Fixes #N` (enables label sync). PRs not tied to an issue may be closed.
|
|
232
|
+
6. **Review** — needs passing checks, ≥1 maintainer approval, all conversations resolved, branch up to date. Approvals are voided when new commits are pushed. Claude provides an initial review automatically.
|
|
233
|
+
|
|
234
|
+
**Invoking vs. mentioning the assistant.** The assistant workflow (`claude.yml`) triggers whenever the assistant handle — an `@` immediately followed by `claude` — appears in the title/body of a Discussion, issue, or PR (or a comment on one). Write it only when you actually want to summon the assistant (e.g. an auto-filed docs issue asking it to open a PR). To refer to the handle as plain text anywhere else — a PR description, issue body, commit message, review comment — write it as `"@ claude"` (a space after the `@`, double quotes on both sides) so the `contains(…, '@claude')` trigger can't match. Tagging it unintentionally spawns a spurious assistant run and follow-up PR (this is what happened around issue #83).
|
|
235
|
+
|
|
236
|
+
**Responding to the automated review.** Opening (or marking ready) a PR triggers `claude-code-review.yml`, which posts a first-pass review with inline comments (catalogued in `docs/GITHUB_AUTOMATION.md`). Treat it as input, not verdict: wait for the review to land, then work through each comment individually, weighing it against your own understanding of the codebase and the change you actually made — don't assume the reviewer is right. Where a comment exposes a genuine blind spot, fix it in a focused, self-contained commit pushed to the *same* PR; where you're convinced it's wrong, leave the code untouched and be ready to explain concretely why. Then post a single PR comment covering both halves — first the points you addressed (what you changed and the rationale), then the points you think the reviewer got wrong (with your reasoning) — and close that comment by deliberately tagging the assistant handle to kick off a second-pass review. This is precisely the "you actually want to summon it" case from the paragraph above, not a violation of the don't-tag-unintentionally rule. Then repeat the loop — wait, read, validate, address, rebut, comment, re-invoke — until the reviews either come back clean (no further notes / LGTM) or they start drifting out of scope (raising points unrelated to the PR's theme) or turn nonsensical. At that stopping point, post a comment explaining why you're stopping, and do **not** tag the assistant handle again.
|
|
237
|
+
|
|
238
|
+
**Pre-commit hooks** (`pre-commit install` to activate): `ruff` (lint, `--fix`), `ruff-format`, general `pre-commit-hooks` (large files >1 MB, case conflict, merge conflict, YAML/TOML syntax, EOF/trailing-whitespace, private-key detection, `no-commit-to-branch` on master), and `gitleaks` (secret detection). Ruff-format auto-reformats on commit — **re-run `git add` after** it modifies files, then commit again. Bypass only sparingly with `git commit --no-verify`.
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
pre-commit run # staged files
|
|
242
|
+
pre-commit run --all-files # everything
|
|
243
|
+
pre-commit run ruff --all-files
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Security
|
|
249
|
+
|
|
250
|
+
- **Never commit secrets** (tokens, credentials, private data, internal URLs/IPs). Use `.env` (gitignored). `gitleaks` pre-commit hook + GitHub Dependabot back this up but aren't foolproof.
|
|
251
|
+
- **Secrets in use**: `SLACK_BOT_TOKEN` (Slack alerts/notifications); `HF_TOKEN` (HuggingFace Hub upload via `train --hf-upload` — inference downloads hit a public repo and need no token). Use separate dev/prod tokens; store via a secrets manager or restricted-permission encrypted env files.
|
|
252
|
+
- **If a token leaks** — rotate immediately. Slack: revoke in [Slack API](https://api.slack.com/apps) → OAuth & Permissions, reinstall with scopes `channels:read, chat:write, files:write, groups:read, incoming-webhook`, update `SLACK_BOT_TOKEN` everywhere, verify with `PYTHONPATH=src python utils/print_cli_help.py train` (no Slack errors). HuggingFace: invalidate/delete the token at [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens), create a replacement (**write** scope only if you upload), update `HF_TOKEN` everywhere — full steps in `SECURITY.md`.
|
|
253
|
+
- **Incident response**: Contain (revoke creds) → Assess → Notify → Remediate (rotate secrets) → Document → Improve.
|
|
254
|
+
- **Reporting**: non-critical → [GitHub Discussion](https://github.com/zachtheyek/Aetherscan/discussions) with the "security" label; critical → contact [@zachtheyek](https://breakthroughlisten.slack.com/archives/D01SJG0L0TE) on Slack directly (do **not** open a public issue), expect a response in 48–72h.
|
|
255
|
+
- **Data security**: major outputs (weights, code, search results, training/inference data) are publicly disclosed via HuggingFace / GitHub / publications / [BL Open Data Archive](https://breakthroughinitiatives.org/opendatasearch); intermediate products (DB records, plots) stay on access-controlled HPC servers.
|
|
256
|
+
- **Dependency versions**: when bumping a dep, don't chase the latest — target the **newer** of {two minors below the latest stable, the latest stable ≥6 months old}, stable releases only (no alpha/beta/rc/nightly). A known advisory on that target overrides the lag → jump to the minimum patched version. Never cross a documented ceiling (`numpy<2.0`, `setuptools<81`) or the NGC TF 2.17 ABI, and keep `environment.yml` / `requirements-container.txt` / `aetherscan.def` / `pyproject.toml` in lockstep for shared deps. Full policy in `SECURITY.md` → Security Scanning → Version Selection Policy.
|
|
257
|
+
- False positives: add `file:line` to `.gitleaksignore` or inline `# gitleaks:allow` (less preferred).
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Reference Files
|
|
262
|
+
|
|
263
|
+
Paths relative to the repo root:
|
|
264
|
+
|
|
265
|
+
- `CLAUDE.md` — condensed always-on agent rules (this skill is its deep-dive companion)
|
|
266
|
+
- `README.md` — overview, install matrix, usage examples, full CLI reference
|
|
267
|
+
- `CONTRIBUTING.md` — workflow, project structure, code style, pre-commit
|
|
268
|
+
- `SECURITY.md` — security policy, secrets management, token rotation
|
|
269
|
+
- `KNOWN_ISSUES.md` — known bugs and workarounds
|
|
270
|
+
- `AI_POLICY.md` — AI usage policy (read before AI-assisted contributions)
|
|
271
|
+
- `docs/README.md` — index of the technical documentation suite (one doc per surface)
|
|
272
|
+
- `docs/ARCHITECTURE.md` — system map: data model, module map, init order, artifact layout
|
|
273
|
+
- `docs/TRAINING_PIPELINE.md` — rounds, round data + producer, retries, every training plot
|
|
274
|
+
- `docs/INFERENCE_PIPELINE.md` — catalogs, streaming flow, manifest retries, inference figures
|
|
275
|
+
- `docs/PREPROCESSING.md` — energy detection math (PFB/spline, k² derivation), signal injection
|
|
276
|
+
- `docs/MODELS.md` — Beta-VAE architecture/loss math, RF features + threshold semantics
|
|
277
|
+
- `docs/DATABASE.md` — schema, writer thread, flush/supersede protocols, migrations
|
|
278
|
+
- `docs/RUNTIME_SERVICES.md` — logger/Slack, ResourceManager lifecycle, resource monitor
|
|
279
|
+
- `docs/TESTING.md` — suite layout, markers, isolation fixtures, CI, cluster smokes
|
|
280
|
+
- `docs/GITHUB_AUTOMATION.md` — every workflow, dedup guards, assistant-handle rules
|
|
281
|
+
- `docs/RELEASE.md` — version-coupling contract, CD gates, release runbook
|
|
282
|
+
- `docs/GPU_RUNTIME_GUIDE.md` — container build/runtime runbook
|
|
283
|
+
- `docs/CONFIG_AND_CLI.md` — config system deep dive
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Create a report to help us improve
|
|
4
|
+
title: "[BUG]"
|
|
5
|
+
labels: bug
|
|
6
|
+
assignees: ""
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
**Describe the bug**
|
|
10
|
+
A clear and concise description of what the bug is.
|
|
11
|
+
|
|
12
|
+
**To reproduce**
|
|
13
|
+
Steps to reproduce the behavior:
|
|
14
|
+
|
|
15
|
+
1. Go to '...'
|
|
16
|
+
2. Click on '....'
|
|
17
|
+
3. Scroll down to '....'
|
|
18
|
+
4. See error
|
|
19
|
+
|
|
20
|
+
**Expected behavior**
|
|
21
|
+
A clear and concise description of what you expected to happen.
|
|
22
|
+
|
|
23
|
+
**Screenshots**
|
|
24
|
+
If applicable, add screenshots to help explain your problem.
|
|
25
|
+
|
|
26
|
+
**System information**
|
|
27
|
+
Run `utils/get_system_info.sh` and append the outputs as attachments
|
|
28
|
+
|
|
29
|
+
**Additional context**
|
|
30
|
+
Add any other context about the problem here.
|
|
31
|
+
|
|
32
|
+
**Discussion links**
|
|
33
|
+
All issues are required to have at least one prior discussion with a maintainer, either via GitHub discussions or Slack (as per CONTRIBUTING.md). Please link to the associated discussion(s) here for future reference
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest an idea for this project
|
|
4
|
+
title: "[FEATURE]"
|
|
5
|
+
labels: enhancement
|
|
6
|
+
assignees: ""
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
**Is your feature request related to a problem? Please describe.**
|
|
10
|
+
A clear and concise description of what the problem is. E.g. I'm always frustrated when [...]
|
|
11
|
+
|
|
12
|
+
**Describe the solution you'd like**
|
|
13
|
+
A clear and concise description of what you want to happen.
|
|
14
|
+
|
|
15
|
+
**Describe alternatives you've considered**
|
|
16
|
+
A clear and concise description of any alternative solutions or features you've considered.
|
|
17
|
+
|
|
18
|
+
**Additional context**
|
|
19
|
+
Add any other context or screenshots about the feature request here.
|
|
20
|
+
|
|
21
|
+
**Discussion links**
|
|
22
|
+
All issues are required to have at least one prior discussion with a maintainer, either via GitHub discussions or Slack (as per CONTRIBUTING.md). Please link to the associated discussion(s) here for future reference
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Automatically designate the author of an issue/PR as the assignee
|
|
2
|
+
# Note, this does not prevent more assignees from being added in the future
|
|
3
|
+
name: Auto-assign author
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
issues:
|
|
7
|
+
types: [opened]
|
|
8
|
+
pull_request:
|
|
9
|
+
types: [opened]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
issues: write
|
|
13
|
+
pull-requests: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
assign-author:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- name: Assign author to issue/PR
|
|
21
|
+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
|
22
|
+
with:
|
|
23
|
+
script: |
|
|
24
|
+
const sender = context.payload.sender;
|
|
25
|
+
|
|
26
|
+
// GitHub's REST API does not support assigning bot/agent accounts
|
|
27
|
+
// (e.g. GitHub Apps, Copilot/Claude agents) via App installation
|
|
28
|
+
// tokens, so skip the assignment when the sender is not a human user.
|
|
29
|
+
if (sender.type !== "User") {
|
|
30
|
+
core.info(`Skipping assignment: sender ${sender.login} has type "${sender.type}"`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const issueNumber = context.eventName === "issues"
|
|
35
|
+
? context.payload.issue.number
|
|
36
|
+
: context.payload.pull_request.number;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
await github.rest.issues.addAssignees({
|
|
40
|
+
owner: context.repo.owner,
|
|
41
|
+
repo: context.repo.repo,
|
|
42
|
+
issue_number: issueNumber,
|
|
43
|
+
assignees: [sender.login],
|
|
44
|
+
});
|
|
45
|
+
} catch (error) {
|
|
46
|
+
// Defense-in-depth: if GitHub still rejects the assignment (e.g.
|
|
47
|
+
// an agent identity slips past the type check above), log it
|
|
48
|
+
// rather than failing the workflow — the issue/PR was created
|
|
49
|
+
// successfully and assignment is a non-critical convenience.
|
|
50
|
+
core.warning(`Could not assign ${sender.login}: ${error.message}`);
|
|
51
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Auto review every PR (once on open, once when marked as "ready for review")
|
|
2
|
+
name: Claude Code Review
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
pull_request:
|
|
6
|
+
types: [opened, ready_for_review]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
claude-code-review:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
# Reviewing a PR diff with inline comments scales with PR size but shouldn't exceed 20 mins
|
|
12
|
+
# (refine if data shows otherwise)
|
|
13
|
+
timeout-minutes: 20
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
pull-requests: write
|
|
17
|
+
issues: write
|
|
18
|
+
id-token: write
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout repository
|
|
22
|
+
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
|
|
23
|
+
with:
|
|
24
|
+
fetch-depth: 1
|
|
25
|
+
|
|
26
|
+
- name: Run Claude Code Review
|
|
27
|
+
id: claude-review
|
|
28
|
+
uses: anthropics/claude-code-action@5d5c10a4f389689f992ea10bb14dcb6fcc83146d # v1.0.102
|
|
29
|
+
with:
|
|
30
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
31
|
+
allowed_bots: "claude[bot]"
|
|
32
|
+
track_progress: true
|
|
33
|
+
prompt: |
|
|
34
|
+
## Role
|
|
35
|
+
You are an automated code reviewer for the pull request below. Provide feedback on the changes — do not modify them.
|
|
36
|
+
|
|
37
|
+
## Context
|
|
38
|
+
REPO: ${{ github.repository }}
|
|
39
|
+
PR NUMBER: ${{ github.event.pull_request.number }}
|
|
40
|
+
PR TITLE: ${{ github.event.pull_request.title }}
|
|
41
|
+
PR BODY: ${{ github.event.pull_request.body }}
|
|
42
|
+
PR AUTHOR: ${{ github.event.pull_request.user.login }}
|
|
43
|
+
|
|
44
|
+
## Task
|
|
45
|
+
Please review this pull request and provide feedback on:
|
|
46
|
+
- Code quality and best practices
|
|
47
|
+
- Potential bugs or issues
|
|
48
|
+
- Performance considerations
|
|
49
|
+
- Security implications
|
|
50
|
+
- Test coverage
|
|
51
|
+
|
|
52
|
+
## Available tools
|
|
53
|
+
PR interaction:
|
|
54
|
+
- `gh pr diff` to get the PR diff
|
|
55
|
+
- `gh pr view` to get PR metadata
|
|
56
|
+
- `gh pr comment` to post top-level feedback
|
|
57
|
+
- `mcp__github_inline_comment__create_inline_comment` to post inline code comments
|
|
58
|
+
|
|
59
|
+
Issue lookup:
|
|
60
|
+
- `gh issue view` to look up linked issues (primary use) or related issues if necessary
|
|
61
|
+
|
|
62
|
+
Git inspection:
|
|
63
|
+
- `git fetch origin <base-branch>` to fetch the base branch before diffing against it
|
|
64
|
+
- `git diff` to compare code changes between commits (e.g. `git diff origin/<base-branch>...HEAD`)
|
|
65
|
+
- `git log` to inspect commit history
|
|
66
|
+
|
|
67
|
+
Code exploration:
|
|
68
|
+
- `Read`, `Glob`, `Grep` to explore the codebase
|
|
69
|
+
|
|
70
|
+
## Guidelines
|
|
71
|
+
- See CLAUDE.md for context about the repository
|
|
72
|
+
- Only post GitHub comments — don't submit review text as messages
|
|
73
|
+
- Be constructive and helpful in your feedback
|
|
74
|
+
- Your only responsibilities are to review the current PR. Do not do anything that is not within this job scope.
|
|
75
|
+
|
|
76
|
+
claude_args: |
|
|
77
|
+
--allowedTools "Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr comment:*),mcp__github_inline_comment__create_inline_comment,Bash(gh issue view:*),Bash(git fetch:*),Bash(git diff:*),Bash(git log:*),Read,Glob,Grep"
|
|
78
|
+
--model opus
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Enforce contribution rules: PRs need linked issues, issues need prior discussions
|
|
2
|
+
# Handles both existence & correctness
|
|
3
|
+
name: Claude Contribution Check
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
issues:
|
|
7
|
+
types: [opened]
|
|
8
|
+
pull_request:
|
|
9
|
+
types: [opened]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
pull-requests: write
|
|
14
|
+
issues: write
|
|
15
|
+
id-token: write
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
contribution-check:
|
|
19
|
+
# Never run on bot- or automation-authored issues/PRs. Our own workflows (e.g.
|
|
20
|
+
# claude-update-docs) file issues as the `claude` bot; without this guard the job would spin
|
|
21
|
+
# up a runner and claude-code-action would abort with a "non-human actor" error — a red
|
|
22
|
+
# failure on every bot issue. Skip the whole job instead. (allowed_bots is already unset, so
|
|
23
|
+
# no comment is ever posted; this additionally avoids the noisy failed run.) See issue #83.
|
|
24
|
+
if: >-
|
|
25
|
+
github.event.issue.user.type != 'Bot' &&
|
|
26
|
+
github.event.pull_request.user.type != 'Bot' &&
|
|
27
|
+
!endsWith(github.actor, '[bot]') &&
|
|
28
|
+
github.actor != 'claude'
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
# Contribution check shouldn't take more than 5 mins (refine if data shows otherwise)
|
|
31
|
+
timeout-minutes: 5
|
|
32
|
+
|
|
33
|
+
steps:
|
|
34
|
+
- name: Checkout repository
|
|
35
|
+
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
|
|
36
|
+
with:
|
|
37
|
+
fetch-depth: 1
|
|
38
|
+
|
|
39
|
+
- name: Run Claude Contribution Check
|
|
40
|
+
uses: anthropics/claude-code-action@5d5c10a4f389689f992ea10bb14dcb6fcc83146d # v1.0.102
|
|
41
|
+
with:
|
|
42
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
43
|
+
prompt: |
|
|
44
|
+
## Role
|
|
45
|
+
You are a contribution compliance checker. Your job is to verify that contributions follow the project's workflow rules.
|
|
46
|
+
|
|
47
|
+
## Context
|
|
48
|
+
REPO: ${{ github.repository }}
|
|
49
|
+
EVENT: ${{ github.event_name }}
|
|
50
|
+
ACTION: ${{ github.event.action }}
|
|
51
|
+
|
|
52
|
+
## Task
|
|
53
|
+
### If this is a pull_request event:
|
|
54
|
+
|
|
55
|
+
PR NUMBER: ${{ github.event.pull_request.number }}
|
|
56
|
+
PR TITLE: ${{ github.event.pull_request.title }}
|
|
57
|
+
PR BODY: ${{ github.event.pull_request.body }}
|
|
58
|
+
PR AUTHOR: ${{ github.event.pull_request.user.login }}
|
|
59
|
+
|
|
60
|
+
Check if this PR has a formally linked issue using the GitHub GraphQL API:
|
|
61
|
+
```bash
|
|
62
|
+
gh api graphql -f query='
|
|
63
|
+
query {
|
|
64
|
+
repository(owner: "${{ github.repository_owner }}", name: "${{ github.event.repository.name }}") {
|
|
65
|
+
pullRequest(number: ${{ github.event.pull_request.number }}) {
|
|
66
|
+
closingIssuesReferences(first: 5) {
|
|
67
|
+
nodes { number title }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
If NO linked issues are found:
|
|
76
|
+
1. Add the label `needs-issue` to the PR: `gh pr edit ${{ github.event.pull_request.number }} --add-label "needs-issue"`
|
|
77
|
+
2. Post a polite warning comment on the PR explaining that all PRs must be linked to an existing issue (via `Closes #N` or `Fixes #N` in the PR body, or via the "Development" sidebar). Reference the CONTRIBUTING.md guidelines.
|
|
78
|
+
|
|
79
|
+
If linked issues ARE found, verify correctness:
|
|
80
|
+
1. View each linked issue (`gh issue view <number>`) and check that its title/description plausibly relates to the PR's title, description, and stated changes.
|
|
81
|
+
2. If a linked issue appears unrelated to the PR (e.g. the PR is about fixing a typo but the linked issue is about a performance optimization), post a friendly comment noting the potential mismatch and asking the author to verify the correct issue is linked.
|
|
82
|
+
3. If the PR body mentions issue numbers (e.g. "Related to #10") that are NOT formally linked via closing keywords, post a friendly reminder suggesting the author use `Closes #N` or `Fixes #N` to create a formal link.
|
|
83
|
+
4. If all linked issues are relevant and correctly linked — do nothing, the PR is compliant.
|
|
84
|
+
|
|
85
|
+
### If this is an issues event:
|
|
86
|
+
|
|
87
|
+
ISSUE NUMBER: ${{ github.event.issue.number }}
|
|
88
|
+
ISSUE TITLE: ${{ github.event.issue.title }}
|
|
89
|
+
ISSUE BODY: ${{ github.event.issue.body }}
|
|
90
|
+
ISSUE AUTHOR: ${{ github.event.issue.user.login }}
|
|
91
|
+
|
|
92
|
+
Search for related GitHub Discussions that may have preceded this issue:
|
|
93
|
+
```bash
|
|
94
|
+
gh search issues --type discussion --repo ${{ github.repository }} "<relevant keywords from issue title>"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Note that the repository's issue templates provide a designated "Discussion links" section for authors to link to relevant discussions in the issue body. These may be a good starting point, though you shouldn't assume they're always accurate due to the potential for human error.
|
|
98
|
+
|
|
99
|
+
If NO related discussions are found:
|
|
100
|
+
1. Add the label `needs-discussion` to the issue: `gh issue edit ${{ github.event.issue.number }} --add-label "needs-discussion"`
|
|
101
|
+
2. Post a friendly reminder comment explaining that issues should be preceded by a GitHub Discussion or Slack thread (per CONTRIBUTING.md). This is a gentle nudge, not a hard block.
|
|
102
|
+
3. If the original issue body didn't include a Slack thread link under "Discussion links", add a note towards the end of your reminder stating that if the discussion happened on Slack rather than GitHub Discussions, this reminder may be safely ignored, but the issue author should link to the relevant Slack threads in the original issue body.
|
|
103
|
+
4. If the original issue body DID include a Slack thread link under "Discussion links", do not assign the `needs-discussion` label, or post the reminder comment. Assume the issue is compliant.
|
|
104
|
+
|
|
105
|
+
If related discussions ARE found, but the original issue body has incorrect/incomplete links:
|
|
106
|
+
1. Post a friendly reminder comment suggesting the issue author modify the original issue body with the correct links, then link to the correct links that you found.
|
|
107
|
+
|
|
108
|
+
If all related discussions found match the issues linked in the original issue body — do nothing, the issue is compliant.
|
|
109
|
+
|
|
110
|
+
## Available tools
|
|
111
|
+
GitHub API:
|
|
112
|
+
- `gh api graphql` to query a PR's formally linked issues via `closingIssuesReferences` (the only reliable way to detect `Closes #N` / `Fixes #N` style links)
|
|
113
|
+
|
|
114
|
+
PR interaction (pull_request events):
|
|
115
|
+
- `gh pr edit --add-label` to apply the `needs-issue` label when a PR has no linked issue
|
|
116
|
+
- `gh pr comment` to post the missing-issue warning or a relevance-mismatch reminder
|
|
117
|
+
|
|
118
|
+
Issue interaction (issues events, plus `gh issue view` on PR events):
|
|
119
|
+
- `gh issue view` to inspect each linked issue referenced by a PR and check its relevance to the PR's stated changes
|
|
120
|
+
- `gh issue edit --add-label` to apply the `needs-discussion` label when a new issue lacks a prior discussion
|
|
121
|
+
- `gh issue comment` to post the missing-discussion reminder or a links-correction nudge
|
|
122
|
+
|
|
123
|
+
Search:
|
|
124
|
+
- `gh search issues --type discussion` to find prior GitHub Discussions related to a newly opened issue
|
|
125
|
+
|
|
126
|
+
Code exploration:
|
|
127
|
+
- `Read`, `Glob`, `Grep` to consult CONTRIBUTING.md, issue templates, or other repo context when assessing compliance
|
|
128
|
+
|
|
129
|
+
## Guidelines
|
|
130
|
+
- Be concise in your comments (2-3 sentences max)
|
|
131
|
+
- Do NOT close or reject any PRs or issues — only warn and label
|
|
132
|
+
- Your only responsibilities are to enforce contribution compliance. Do not do anything that is not within this job scope.
|
|
133
|
+
|
|
134
|
+
claude_args: |
|
|
135
|
+
--allowedTools "Bash(gh api:*),Bash(gh pr edit:*),Bash(gh pr comment:*),Bash(gh issue view:*),Bash(gh issue edit:*),Bash(gh issue comment:*),Bash(gh search:*),Read,Glob,Grep"
|
|
136
|
+
--model opus
|