ennbo 0.0.7__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 (66) hide show
  1. ennbo-0.0.7/.cursorrules +54 -0
  2. ennbo-0.0.7/.cursorrules~ +51 -0
  3. ennbo-0.0.7/.pre-commit-config.yaml +34 -0
  4. ennbo-0.0.7/LICENSE +21 -0
  5. ennbo-0.0.7/PKG-INFO +75 -0
  6. ennbo-0.0.7/README.md +30 -0
  7. ennbo-0.0.7/admin/find_forgotten_py.sh +43 -0
  8. ennbo-0.0.7/assets/image-2b7c2993-a63b-4d7c-bddd-245c066ad6db.png +0 -0
  9. ennbo-0.0.7/assets/image-2bea4cb6-098f-4c0c-9dc2-b04e03808d96.png +0 -0
  10. ennbo-0.0.7/assets/image-40487dc1-d2d3-4603-81d6-7a5dbb4b500e.png +0 -0
  11. ennbo-0.0.7/assets/image-5fd9563a-6797-4835-87c4-72d9e7e92ada.png +0 -0
  12. ennbo-0.0.7/assets/image-a5905007-8b6b-4ce5-8af2-ab9dfc66506d.png +0 -0
  13. ennbo-0.0.7/assets/image-c634c557-87d0-47c1-b9e7-779872808905.png +0 -0
  14. ennbo-0.0.7/assets/image-fbf2c91f-2c75-47de-a7c9-bc2d6dea4263.png +0 -0
  15. ennbo-0.0.7/data/MNIST/raw/t10k-images-idx3-ubyte +0 -0
  16. ennbo-0.0.7/data/MNIST/raw/t10k-images-idx3-ubyte.gz +0 -0
  17. ennbo-0.0.7/data/MNIST/raw/t10k-labels-idx1-ubyte +0 -0
  18. ennbo-0.0.7/data/MNIST/raw/t10k-labels-idx1-ubyte.gz +0 -0
  19. ennbo-0.0.7/data/MNIST/raw/train-images-idx3-ubyte +0 -0
  20. ennbo-0.0.7/data/MNIST/raw/train-images-idx3-ubyte.gz +0 -0
  21. ennbo-0.0.7/data/MNIST/raw/train-labels-idx1-ubyte +0 -0
  22. ennbo-0.0.7/data/MNIST/raw/train-labels-idx1-ubyte.gz +0 -0
  23. ennbo-0.0.7/examples/data/MNIST/raw/t10k-images-idx3-ubyte +0 -0
  24. ennbo-0.0.7/examples/data/MNIST/raw/t10k-images-idx3-ubyte.gz +0 -0
  25. ennbo-0.0.7/examples/data/MNIST/raw/t10k-labels-idx1-ubyte +0 -0
  26. ennbo-0.0.7/examples/data/MNIST/raw/t10k-labels-idx1-ubyte.gz +0 -0
  27. ennbo-0.0.7/examples/data/MNIST/raw/train-images-idx3-ubyte +0 -0
  28. ennbo-0.0.7/examples/data/MNIST/raw/train-images-idx3-ubyte.gz +0 -0
  29. ennbo-0.0.7/examples/data/MNIST/raw/train-labels-idx1-ubyte +0 -0
  30. ennbo-0.0.7/examples/data/MNIST/raw/train-labels-idx1-ubyte.gz +0 -0
  31. ennbo-0.0.7/examples/demo_enn.ipynb +197 -0
  32. ennbo-0.0.7/examples/demo_turbo_enn.ipynb +333 -0
  33. ennbo-0.0.7/pyproject.toml +39 -0
  34. ennbo-0.0.7/requirements.md +16 -0
  35. ennbo-0.0.7/requirements.txt~ +6 -0
  36. ennbo-0.0.7/src/enn/__init__.py +28 -0
  37. ennbo-0.0.7/src/enn/enn.py +222 -0
  38. ennbo-0.0.7/src/enn/enn_fit.py +132 -0
  39. ennbo-0.0.7/src/enn/enn_normal.py +27 -0
  40. ennbo-0.0.7/src/enn/enn_params.py +10 -0
  41. ennbo-0.0.7/src/enn/enn_util.py +92 -0
  42. ennbo-0.0.7/src/turbo/__init__.py +11 -0
  43. ennbo-0.0.7/src/turbo/base_turbo_impl.py +98 -0
  44. ennbo-0.0.7/src/turbo/lhd_only_impl.py +42 -0
  45. ennbo-0.0.7/src/turbo/proposal.py +171 -0
  46. ennbo-0.0.7/src/turbo/turbo_config.py +27 -0
  47. ennbo-0.0.7/src/turbo/turbo_enn_impl.py +185 -0
  48. ennbo-0.0.7/src/turbo/turbo_gp.py +29 -0
  49. ennbo-0.0.7/src/turbo/turbo_gp_base.py +27 -0
  50. ennbo-0.0.7/src/turbo/turbo_gp_noisy.py +36 -0
  51. ennbo-0.0.7/src/turbo/turbo_mode.py +10 -0
  52. ennbo-0.0.7/src/turbo/turbo_mode_impl.py +67 -0
  53. ennbo-0.0.7/src/turbo/turbo_one_impl.py +163 -0
  54. ennbo-0.0.7/src/turbo/turbo_optimizer.py +343 -0
  55. ennbo-0.0.7/src/turbo/turbo_trust_region.py +121 -0
  56. ennbo-0.0.7/src/turbo/turbo_utils.py +298 -0
  57. ennbo-0.0.7/src/turbo/turbo_zero_impl.py +24 -0
  58. ennbo-0.0.7/style.md +89 -0
  59. ennbo-0.0.7/tests/conftest.py +56 -0
  60. ennbo-0.0.7/tests/examples/__init__.py +0 -0
  61. ennbo-0.0.7/tests/examples/mnist/__init__.py +0 -0
  62. ennbo-0.0.7/tests/examples/mnist/test_mnist.py +149 -0
  63. ennbo-0.0.7/tests/test_enn_core.py +428 -0
  64. ennbo-0.0.7/tests/test_enn_fit.py +40 -0
  65. ennbo-0.0.7/tests/test_enn_util.py +106 -0
  66. ennbo-0.0.7/tests/test_turbo.py +1202 -0
@@ -0,0 +1,54 @@
1
+ # FIRST RULE OF CURSOR - MANDATORY: READ THIS FIRST
2
+ MANDATORY FIRST ACTION: When the user sends their first request in a conversation, you MUST immediately read style.md using the read_file tool BEFORE performing any other action (no searches, no code reading, no tool calls of any kind). This is the absolute first step - do not answer the question, do not search, do not read other files. Read style.md first, then proceed with the user's request.
3
+
4
+
5
+ --
6
+
7
+ ### Cursor Rule: Claims vs Hypotheses
8
+
9
+ - Label uncertain reasoning as Hypothesis; only use Claim with explicit evidence.
10
+ - Claims must cite evidence (code refs, logs, metrics). Otherwise, downgrade to Hypothesis.
11
+ - For each Hypothesis, include:
12
+ - Hypothesis: concise, falsifiable statement.
13
+ - Predictions: measurable outcomes if true.
14
+ - Test: minimal experiment (setup, variables, metrics, pass/fail).
15
+ - Confounders: likely alternatives and controls.
16
+ - Language:
17
+ - Hypothesis: “suggests”, “may”, “indicates”.
18
+ - Claim (with evidence): “shows”, “demonstrates”, “causes”.
19
+ - Trigger this rule when explaining performance differences, algorithm behavior, or proposing changes.
20
+ - Whenever you see an error happen more two or more times in a row, stop, state the problem clearly, then hypothesize the cause. Then test to determine the cause. Once the cause is determined, fix the problem.
21
+
22
+ --
23
+
24
+ When running python code, you'll need to set PYTHONPATH
25
+
26
+ --
27
+
28
+ When your write code, always make sure `pytest -sv tests` and `ruff check` pass.
29
+ Iterate until they do.
30
+
31
+ --
32
+
33
+ # Debugging process rule
34
+
35
+ When debugging: run the broken code first to observe the actual failure, write a test that reproduces that exact failure, then fix it. Don't guess—observe empirically. Replicate the exact code path that fails in the test, not a simplified version. The broken code is the source of truth; observe it directly before fixing.
36
+ --
37
+
38
+
39
+ Acronyms we can use to make communication more efficient:
40
+ DRY: Don't Repeat Yourself. The user would like you to look for opportunities to factor out common code.
41
+ DCC: Don't Change Code. Repond to the user's query, but don't change *any* code.
42
+ DCT: Don't Change Tests. Repond to the user's query, but don't change any tests. You may change non-test code, though.
43
+ OCT: *Only* Change Tests. Repond to the user's query, but don't change any non-test code. You may change test code, though.
44
+ RR: Restate my request clearly. Lmk if you have any questions. DCC
45
+
46
+ --
47
+
48
+ # Cursor Roles
49
+ The Tester: The Tester writes unit tests but does not implement the code being tested. Tests for unimplemented code should be failing tests.
50
+ The Implementor: The Implementor writes code but does not write or change tests.
51
+
52
+ --
53
+
54
+ After every 5th request, `cat .cursorrules`
@@ -0,0 +1,51 @@
1
+ # FIRST RULE OF CURSOR - MANDATORY: READ THIS FIRST
2
+ MANDATORY FIRST ACTION: When the user sends their first request in a conversation, you MUST immediately read style.md using the read_file tool BEFORE performing any other action (no searches, no code reading, no tool calls of any kind). This is the absolute first step - do not answer the question, do not search, do not read other files. Read style.md first, then proceed with the user's request.
3
+
4
+
5
+ --
6
+
7
+ ### Cursor Rule: Claims vs Hypotheses
8
+
9
+ - Label uncertain reasoning as Hypothesis; only use Claim with explicit evidence.
10
+ - Claims must cite evidence (code refs, logs, metrics). Otherwise, downgrade to Hypothesis.
11
+ - For each Hypothesis, include:
12
+ - Hypothesis: concise, falsifiable statement.
13
+ - Predictions: measurable outcomes if true.
14
+ - Test: minimal experiment (setup, variables, metrics, pass/fail).
15
+ - Confounders: likely alternatives and controls.
16
+ - Language:
17
+ - Hypothesis: “suggests”, “may”, “indicates”.
18
+ - Claim (with evidence): “shows”, “demonstrates”, “causes”.
19
+ - Trigger this rule when explaining performance differences, algorithm behavior, or proposing changes.
20
+ - Whenever you see an error happen more two or more times in a row, stop, state the problem clearly, then hypothesize the cause. Then test to determine the cause. Once the cause is determined, fix the problem.
21
+
22
+ --
23
+
24
+ When running python code, you'll need to set PYTHONPATH
25
+
26
+ --
27
+
28
+ When your write code, always make sure `pytest -sv tests` and `ruff check` pass.
29
+ Iterate until they do.
30
+
31
+ --
32
+
33
+ # Debugging process rule
34
+
35
+ When debugging: run the broken code first to observe the actual failure, write a test that reproduces that exact failure, then fix it. Don't guess—observe empirically. Replicate the exact code path that fails in the test, not a simplified version. The broken code is the source of truth; observe it directly before fixing.
36
+ --
37
+
38
+
39
+ Acronyms we can use to make communication more efficient:
40
+ DRY: Don't Repeat Yourself. The user would like you to look for opportunities to factor out common code.
41
+ DCC: Don't Change Code. Repond to the user's query, but don't change *any* code.
42
+ DCT: Don't Change Tests. Repond to the user's query, but don't change any tests. You may change non-test code, though.
43
+ OCT: *Only* Change Tests. Repond to the user's query, but don't change any non-test code. You may change test code, though.
44
+ RR: Restate my request clearly. Lmk if you have any questions. DCC
45
+
46
+ --
47
+
48
+ # Cursor Roles
49
+ The Tester: The Tester writes unit tests but does not implement the code being tested. Tests for unimplemented code should be failing tests.
50
+ The Implementor: The Implementor writes code but does not write or change tests.
51
+
@@ -0,0 +1,34 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.6.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-json
9
+ - id: check-added-large-files
10
+ args: ["--maxkb=500"]
11
+
12
+ # Forbid committing binary files and run custom checks
13
+ - repo: local
14
+ hooks:
15
+ - id: forbid-binary
16
+ name: Forbid committing binary files
17
+ entry: bash -c 'echo \"Binary files are not allowed in this repository.\"; exit 1'
18
+ language: system
19
+ types: [binary]
20
+
21
+ - id: find-forgotten-py
22
+ name: Find forgotten Python files via git status
23
+ entry: admin/find_forgotten_py.sh
24
+ language: system
25
+ pass_filenames: false
26
+ always_run: true
27
+
28
+ # Linting, formatting, and import sorting via Ruff
29
+ - repo: https://github.com/astral-sh/ruff-pre-commit
30
+ rev: v0.7.0
31
+ hooks:
32
+ - id: ruff
33
+ args: ["--fix"]
34
+ - id: ruff-format
ennbo-0.0.7/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 yubo research
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
ennbo-0.0.7/PKG-INFO ADDED
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: ennbo
3
+ Version: 0.0.7
4
+ Summary: Epistemic Nearest Neighbors
5
+ Project-URL: Homepage, https://github.com/yubo-research/enn
6
+ Project-URL: Source, https://github.com/yubo-research/enn
7
+ Author-email: YUBO Lab <david.sweet@yu.edu>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2025 yubo research
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Classifier: Intended Audience :: Science/Research
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Programming Language :: Python :: 3
33
+ Classifier: Programming Language :: Python :: 3.11
34
+ Classifier: Programming Language :: Python :: 3.12
35
+ Classifier: Topic :: Scientific/Engineering
36
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
37
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
38
+ Requires-Python: >=3.11
39
+ Requires-Dist: faiss-cpu==1.9.0
40
+ Requires-Dist: gpytorch==1.13
41
+ Requires-Dist: numpy==1.26.4
42
+ Requires-Dist: scipy==1.15.3
43
+ Requires-Dist: torch==2.5.1
44
+ Description-Content-Type: text/markdown
45
+
46
+ # Epistemic Nearest Neighbors
47
+ A fast, alternative surrogate for Bayesian optimization
48
+
49
+ ENN estimates a function's value and associated epistemic uncertainty using a K-Nearest Neighbors model. Queries take $O(N lnK)$ time, where $N$ is the number of observations available for KNN lookups. Compare to an exact GP, which takes $O(N^2)$ time. Additionally, measured running times are very small compared to GPs and other alternative surrogates. [1]
50
+
51
+ ## Contents
52
+ - ENN model, [`EpistemicNearestNeighbors`](https://github.com/yubo-research/enn/blob/main/src/enn/core.py) [1]
53
+ - TuRBO-ENN optimizer, class [`TurboOptimizer`](https://github.com/yubo-research/enn/blob/main/src/enn/turbo_optimizer.py) has four modes
54
+ - `TURBO_ONE` - A clone of the TuRBO [2] reference [code](https://github.com/uber-research/TuRBO), reworked to have an `ask()`/`tell()` interface.
55
+ - `TURBO_ENN` - Same as TURBO_ONE, except uses ENN instead of GP and Pareto(mu, se) instead of Thompson sampling.
56
+ - `TURBO_ZERO` - Same as TURBO_ONE, except randomly-chosen RAASP [3] candidates are picked to be proposals. There is no surrogate.
57
+ - `LHD_ONLY` - Just creates an LHD design for every `ask()`. Good for a baseline and for testing.
58
+
59
+ [1] **Sweet, D., & Jadhav, S. A. (2025).** Taking the GP Out of the Loop. *arXiv preprint arXiv:2506.12818*.
60
+ https://arxiv.org/abs/2506.12818
61
+ [2] **Eriksson, D., Pearce, M., Gardner, J. R., Turner, R., & Poloczek, M. (2020).** Scalable Global Optimization via Local Bayesian Optimization. *Advances in Neural Information Processing Systems, 32*.
62
+ https://arxiv.org/abs/1910.01739
63
+ [3] **Rashidi, B., Johnstonbaugh, K., & Gao, C. (2024).** Cylindrical Thompson Sampling for High-Dimensional Bayesian Optimization. *Proceedings of The 27th International Conference on Artificial Intelligence and Statistics* (pp. 3502–3510). PMLR.
64
+ https://proceedings.mlr.press/v238/rashidi24a.html
65
+
66
+
67
+ ## Installation
68
+ `pip install ennbo`
69
+
70
+ ## Demonstration
71
+ [`demo_enn.ipynb`](https://github.com/yubo-research/enn/tree/main/examples) - Shows how to use [`EpistemicNearestNeighbors`](https://github.com/yubo-research/enn/blob/main/src/enn/core.py) to build and query an ENN model.
72
+ [`demo_turbo_enn.ipynb`](https://github.com/yubo-research/enn/tree/main/examples) - Shows how to use [`TurboOptimizer`](https://github.com/yubo-research/enn/blob/main/src/enn/turbo_optimizer.py) to optimize the Ackley function.
73
+
74
+
75
+
ennbo-0.0.7/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Epistemic Nearest Neighbors
2
+ A fast, alternative surrogate for Bayesian optimization
3
+
4
+ ENN estimates a function's value and associated epistemic uncertainty using a K-Nearest Neighbors model. Queries take $O(N lnK)$ time, where $N$ is the number of observations available for KNN lookups. Compare to an exact GP, which takes $O(N^2)$ time. Additionally, measured running times are very small compared to GPs and other alternative surrogates. [1]
5
+
6
+ ## Contents
7
+ - ENN model, [`EpistemicNearestNeighbors`](https://github.com/yubo-research/enn/blob/main/src/enn/core.py) [1]
8
+ - TuRBO-ENN optimizer, class [`TurboOptimizer`](https://github.com/yubo-research/enn/blob/main/src/enn/turbo_optimizer.py) has four modes
9
+ - `TURBO_ONE` - A clone of the TuRBO [2] reference [code](https://github.com/uber-research/TuRBO), reworked to have an `ask()`/`tell()` interface.
10
+ - `TURBO_ENN` - Same as TURBO_ONE, except uses ENN instead of GP and Pareto(mu, se) instead of Thompson sampling.
11
+ - `TURBO_ZERO` - Same as TURBO_ONE, except randomly-chosen RAASP [3] candidates are picked to be proposals. There is no surrogate.
12
+ - `LHD_ONLY` - Just creates an LHD design for every `ask()`. Good for a baseline and for testing.
13
+
14
+ [1] **Sweet, D., & Jadhav, S. A. (2025).** Taking the GP Out of the Loop. *arXiv preprint arXiv:2506.12818*.
15
+ https://arxiv.org/abs/2506.12818
16
+ [2] **Eriksson, D., Pearce, M., Gardner, J. R., Turner, R., & Poloczek, M. (2020).** Scalable Global Optimization via Local Bayesian Optimization. *Advances in Neural Information Processing Systems, 32*.
17
+ https://arxiv.org/abs/1910.01739
18
+ [3] **Rashidi, B., Johnstonbaugh, K., & Gao, C. (2024).** Cylindrical Thompson Sampling for High-Dimensional Bayesian Optimization. *Proceedings of The 27th International Conference on Artificial Intelligence and Statistics* (pp. 3502–3510). PMLR.
19
+ https://proceedings.mlr.press/v238/rashidi24a.html
20
+
21
+
22
+ ## Installation
23
+ `pip install ennbo`
24
+
25
+ ## Demonstration
26
+ [`demo_enn.ipynb`](https://github.com/yubo-research/enn/tree/main/examples) - Shows how to use [`EpistemicNearestNeighbors`](https://github.com/yubo-research/enn/blob/main/src/enn/core.py) to build and query an ENN model.
27
+ [`demo_turbo_enn.ipynb`](https://github.com/yubo-research/enn/tree/main/examples) - Shows how to use [`TurboOptimizer`](https://github.com/yubo-research/enn/blob/main/src/enn/turbo_optimizer.py) to optimize the Ackley function.
28
+
29
+
30
+
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Run from repo root so paths from git status are correct
5
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6
+ cd "$REPO_ROOT"
7
+
8
+ echo "Scanning for untracked or modified Python files (git status)..."
9
+
10
+ status_output="$(git status --porcelain --untracked-files=all)"
11
+
12
+ if [[ -z "$status_output" ]]; then
13
+ echo "Working tree clean. No forgotten .py files."
14
+ exit 0
15
+ fi
16
+
17
+ # Select only:
18
+ # - untracked files ("??")
19
+ # - or files with unstaged changes (second status column non-space)
20
+ # We parse the raw porcelain line as:
21
+ # XY <space> PATH
22
+ # where X is index status, Y is work-tree status.
23
+ py_files="$(
24
+ printf '%s\n' "$status_output" |
25
+ awk '{
26
+ line = $0
27
+ x = substr(line, 1, 1)
28
+ y = substr(line, 2, 1)
29
+ path = substr(line, 4)
30
+ if ((x == "?" && y == "?") || y != " ")
31
+ print path
32
+ }' |
33
+ grep -E '\.py$' || true
34
+ )"
35
+
36
+ if [[ -z "$py_files" ]]; then
37
+ echo "No forgotten .py files detected."
38
+ exit 0
39
+ fi
40
+
41
+ echo "Potential forgotten Python files:"
42
+ printf "%s\n" "$py_files"
43
+ exit 1