agent-nook 0.1.2__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 (37) hide show
  1. agent_nook-0.1.2/.github/workflows/publish-to-pypi.yml +143 -0
  2. agent_nook-0.1.2/.github/workflows/tests.yml +65 -0
  3. agent_nook-0.1.2/.gitignore +143 -0
  4. agent_nook-0.1.2/.lsp.json +9 -0
  5. agent_nook-0.1.2/.ruff.toml +154 -0
  6. agent_nook-0.1.2/AGENTS.md +52 -0
  7. agent_nook-0.1.2/LICENSE +665 -0
  8. agent_nook-0.1.2/PKG-INFO +506 -0
  9. agent_nook-0.1.2/README.md +480 -0
  10. agent_nook-0.1.2/pyproject.toml +242 -0
  11. agent_nook-0.1.2/run_lint.sh +17 -0
  12. agent_nook-0.1.2/run_tests.sh +7 -0
  13. agent_nook-0.1.2/src/agent_nook/__init__.py +35 -0
  14. agent_nook-0.1.2/src/agent_nook/__main__.py +356 -0
  15. agent_nook-0.1.2/src/agent_nook/config/__init__.py +40 -0
  16. agent_nook-0.1.2/src/agent_nook/config/config.py +468 -0
  17. agent_nook-0.1.2/src/agent_nook/config/loader.py +776 -0
  18. agent_nook-0.1.2/src/agent_nook/config/opencode-sandbox.yaml +196 -0
  19. agent_nook-0.1.2/src/agent_nook/config/sandbox.yaml +197 -0
  20. agent_nook-0.1.2/src/agent_nook/sandbox/__init__.py +26 -0
  21. agent_nook-0.1.2/src/agent_nook/sandbox/builder.py +252 -0
  22. agent_nook-0.1.2/src/agent_nook/sandbox/bwrap_sandbox.py +140 -0
  23. agent_nook-0.1.2/src/agent_nook/utils/directories.py +33 -0
  24. agent_nook-0.1.2/src/agent_nook/utils/logger.py +257 -0
  25. agent_nook-0.1.2/tests/README.md +264 -0
  26. agent_nook-0.1.2/tests/config/test_config.py +251 -0
  27. agent_nook-0.1.2/tests/config/test_env_var_expansion.py +553 -0
  28. agent_nook-0.1.2/tests/config/test_loader.py +258 -0
  29. agent_nook-0.1.2/tests/conftest.py +181 -0
  30. agent_nook-0.1.2/tests/integration/test_auto_init.py +132 -0
  31. agent_nook-0.1.2/tests/integration/test_cli.py +693 -0
  32. agent_nook-0.1.2/tests/integration/test_logs_follow.py +182 -0
  33. agent_nook-0.1.2/tests/integration/test_unset_all_env_config.yaml +14 -0
  34. agent_nook-0.1.2/tests/integration/test_unset_all_env_config_no_fallback.yaml +14 -0
  35. agent_nook-0.1.2/tests/sandbox/test_builder.py +412 -0
  36. agent_nook-0.1.2/tests/sandbox/test_bwrap_sandbox.py +347 -0
  37. agent_nook-0.1.2/tests/test_logger.py +97 -0
@@ -0,0 +1,143 @@
1
+ name: Publish Python 🐍 distribution 📦 to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+ - 'b*'
8
+
9
+ jobs:
10
+ build:
11
+ name: Build distribution 📦
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - name: Checkout repository
15
+ uses: actions/checkout@v6
16
+
17
+ - name: Set up Python
18
+ uses: actions/setup-python@v7
19
+ with:
20
+ python-version: '3.x'
21
+
22
+ - name: Install build
23
+ run: pip install build
24
+
25
+ - name: Build a binary wheel and a source tarball
26
+ run: python -m build
27
+
28
+ - name: Store the distribution packages
29
+ uses: actions/upload-artifact@v7
30
+ with:
31
+ name: python-package-distributions
32
+ path: dist/
33
+ retention-days: 3
34
+
35
+ validate-tag:
36
+ name: Validate tag format
37
+ runs-on: ubuntu-latest
38
+ outputs:
39
+ is-valid-version: ${{ steps.validate.outputs.is-valid-version }}
40
+ is-prerelease: ${{ steps.validate.outputs.is-prerelease }}
41
+ version-matches: ${{ steps.validate.outputs.version-matches }}
42
+ steps:
43
+ - name: Checkout repository
44
+ uses: actions/checkout@v6
45
+
46
+ - name: Validate tag
47
+ id: validate
48
+ run: |
49
+ TAG="${{ github.ref_name }}"
50
+ VERSION_FILE="src/agent_nook/__init__.py"
51
+
52
+ # Single source of truth for the version; hatch reads this dynamically.
53
+ SRC_VERSION="$(grep -E "^[[:space:]]*__version__[[:space:]]*=" "$VERSION_FILE" \
54
+ | head -n1 \
55
+ | sed -E 's/.*= *"([^"]+)".*/\1/')"
56
+
57
+ # Strip a single leading v/b prefix from the tag to get the numeric version.
58
+ TAG_VERSION="$(printf '%s' "$TAG" | sed -E 's/^[vb]//')"
59
+
60
+ echo "tag='${TAG}' tag-version='${TAG_VERSION}' package-version='${SRC_VERSION}'"
61
+
62
+ # The tag version must match the version declared in the package.
63
+ if [[ "$TAG_VERSION" != "$SRC_VERSION" ]]; then
64
+ echo "::error::Tag version '${TAG_VERSION}' does not match the package version '${SRC_VERSION}' in src/agent_nook/__init__.py"
65
+ exit 1
66
+ fi
67
+ echo "version-matches=true" >> $GITHUB_OUTPUT
68
+
69
+ # Tag format: vMAJOR.MINOR.PATCH is a stable release, bMAJOR.MINOR.PATCH is a beta.
70
+ if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
71
+ echo "is-valid-version=true" >> $GITHUB_OUTPUT
72
+ echo "is-prerelease=false" >> $GITHUB_OUTPUT
73
+ elif [[ "$TAG" =~ ^b[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
74
+ echo "is-valid-version=true" >> $GITHUB_OUTPUT
75
+ echo "is-prerelease=true" >> $GITHUB_OUTPUT
76
+ else
77
+ echo "is-valid-version=false" >> $GITHUB_OUTPUT
78
+ echo "is-prerelease=false" >> $GITHUB_OUTPUT
79
+ fi
80
+
81
+ publish:
82
+ name: Publish Python 🐍 distribution 📦 to PyPI
83
+ runs-on: ubuntu-latest
84
+ needs: [build, validate-tag]
85
+ environment:
86
+ name: pypi
87
+ url: https://pypi.org/p/agent-nook
88
+ permissions:
89
+ id-token: write
90
+ if: ${{ needs.validate-tag.outputs.is-valid-version == 'true' && needs.validate-tag.outputs.is-prerelease == 'false' && needs.validate-tag.outputs.version-matches == 'true' }}
91
+ steps:
92
+ - name: Download all the dists
93
+ uses: actions/download-artifact@v8
94
+ with:
95
+ name: python-package-distributions
96
+ path: dist/
97
+
98
+ - name: Publish distribution 📦 to PyPI
99
+ uses: pypa/gh-action-pypi-publish@release/v1
100
+
101
+ publish-test:
102
+ name: Publish Python 🐍 distribution 📦 to TestPyPI
103
+ runs-on: ubuntu-latest
104
+ needs: [build, validate-tag]
105
+ environment:
106
+ name: testpypi
107
+ url: https://test.pypi.org/p/agent-nook
108
+ permissions:
109
+ id-token: write
110
+ if: ${{ needs.validate-tag.outputs.is-valid-version == 'true' && needs.validate-tag.outputs.version-matches == 'true' }}
111
+ steps:
112
+ - name: Download all the dists
113
+ uses: actions/download-artifact@v8
114
+ with:
115
+ name: python-package-distributions
116
+ path: dist/
117
+
118
+ - name: Publish distribution 📦 to TestPyPI
119
+ uses: pypa/gh-action-pypi-publish@release/v1
120
+ with:
121
+ repository-url: https://test.pypi.org/legacy/
122
+
123
+ release:
124
+ name: Create GitHub Release 🏷️
125
+ runs-on: ubuntu-latest
126
+ needs: [build, validate-tag]
127
+ permissions:
128
+ contents: write
129
+ if: ${{ needs.validate-tag.outputs.is-valid-version == 'true' && needs.validate-tag.outputs.version-matches == 'true' }}
130
+ steps:
131
+ - name: Download all the dists
132
+ uses: actions/download-artifact@v8
133
+ with:
134
+ name: python-package-distributions
135
+ path: dist/
136
+
137
+ - name: Create GitHub Release
138
+ id: create_release
139
+ uses: softprops/action-gh-release@v3
140
+ with:
141
+ files: dist/*
142
+ body: ${{ needs.validate-tag.outputs.is-prerelease == 'false' && '🎉 A new stable release is available!' || '🧪 A new beta/pre-release is available!' }}
143
+ prerelease: ${{ needs.validate-tag.outputs.is-prerelease == 'true' }}
@@ -0,0 +1,65 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ concurrency:
8
+ group: ${{ github.workflow }}-${{ github.ref }}
9
+ cancel-in-progress: true
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ test:
16
+ name: pytest (Python ${{ matrix.python-version }})
17
+ runs-on: ubuntu-latest
18
+ strategy:
19
+ fail-fast: false
20
+ matrix:
21
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
22
+ steps:
23
+ - name: Checkout repository
24
+ uses: actions/checkout@v6
25
+
26
+ - name: Set up Python
27
+ uses: actions/setup-python@v7
28
+ with:
29
+ python-version: ${{ matrix.python-version }}
30
+ cache: pip
31
+
32
+ - name: Install bubblewrap
33
+ run: |
34
+ sudo apt-get update
35
+ sudo apt-get install -y bubblewrap
36
+ # Ubuntu 24.04 blocks unprivileged user namespaces by default
37
+ sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
38
+
39
+ - name: Verify bwrap works
40
+ run: bwrap --bind / / -- echo bwrap-ok
41
+
42
+ - name: Install package with dev extras
43
+ run: pip install -e ".[dev]"
44
+
45
+ - name: Run test suite
46
+ run: ./run_tests.sh
47
+
48
+ lint:
49
+ name: ruff
50
+ runs-on: ubuntu-latest
51
+ steps:
52
+ - name: Checkout repository
53
+ uses: actions/checkout@v6
54
+
55
+ - name: Set up Python
56
+ uses: actions/setup-python@v7
57
+ with:
58
+ python-version: "3.14"
59
+ cache: pip
60
+
61
+ - name: Install package with dev extras
62
+ run: pip install -e ".[dev]"
63
+
64
+ - name: Run lint checks
65
+ run: ./run_lint.sh --check
@@ -0,0 +1,143 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+
27
+ # PyInstaller
28
+ *.manifest
29
+ *.spec
30
+
31
+ # Installer logs
32
+ pip-log.txt
33
+ pip-delete-this-directory.txt
34
+
35
+ # Unit test / coverage reports
36
+ htmlcov/
37
+ .tox/
38
+ .coverage
39
+ .coverage.*
40
+ .cache
41
+ nosetests.xml
42
+ coverage.xml
43
+ *.cover
44
+ *.py,cover
45
+ .hypothesis/
46
+ .pytest_cache/
47
+
48
+ # Translations
49
+ *.mo
50
+ *.pot
51
+
52
+ # Django stuff:
53
+ *.log
54
+ local_settings.py
55
+ db.sqlite3
56
+ db.sqlite3-journal
57
+
58
+ # Flask stuff:
59
+ instance/
60
+ .webassets-cache
61
+
62
+ # Scrapy stuff:
63
+ .scrapy
64
+
65
+ # Sphinx documentation
66
+ docs/_build/
67
+
68
+ # PyBuilder
69
+ target/
70
+
71
+ # Jupyter Notebook
72
+ .ipynb_checkpoints
73
+
74
+ # IPython
75
+ profile_default/
76
+ ipython_config.py
77
+
78
+ # pyenv
79
+ .python-version
80
+
81
+ # pipenv
82
+ Pipfile.lock
83
+
84
+ # PEP 582
85
+ __pypackages__/
86
+
87
+ # Celery stuff
88
+ celerybeat-schedule
89
+ celerybeat.pid
90
+
91
+ # SageMath parsed files
92
+ *.sage.py
93
+
94
+ # Environments
95
+ .env
96
+ .venv
97
+ env/
98
+ venv/
99
+ ENV/
100
+ env.bak/
101
+ venv.bak/
102
+
103
+ # Spyder project settings
104
+ .spyderproject
105
+ .spyproject
106
+
107
+ # Rope project settings
108
+ .ropeproject
109
+
110
+ # mkdocs documentation
111
+ /site
112
+
113
+ # mypy
114
+ .mypy_cache/
115
+ .dmypy.json
116
+ dmypy.json
117
+
118
+ # Pyre type checker
119
+ .pyre/
120
+
121
+ # pytype static type analyzer
122
+ .pytype/
123
+
124
+ # Cython debug symbols
125
+ cython_debug/
126
+
127
+ # IDE
128
+ .idea/
129
+ .vscode/
130
+ *.swp
131
+ *.swo
132
+ *~
133
+ .DS_Store
134
+ Thumbs.db
135
+
136
+ # OS files
137
+ .DS_Store
138
+ .DS_Store?
139
+ ._*
140
+ .Spotlight-V100
141
+ .Trashes
142
+ ehthumbs.db
143
+ Thumbs.db
@@ -0,0 +1,9 @@
1
+ {
2
+ "python": {
3
+ "command": "pylsp",
4
+ "args": ["--check-parent-process"],
5
+ "extensionToLanguage": {
6
+ ".py": "python"
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,154 @@
1
+ # Ruff configuration for pylsp LSP integration
2
+ # This file is loaded by pylsp when it uses ruff for linting
3
+ # See: https://github.com/charliermarsh/ruff-lsp
4
+
5
+ # ───────────────────────────────────────────────────────────────────────────────
6
+ # LSP CONFIGURATION RULES
7
+ # ───────────────────────────────────────────────────────────────────────────────
8
+ #
9
+ # This configuration is specifically designed for the agent-nook project.
10
+ # It enforces the following rules:
11
+ #
12
+ # ✅ FIX AUTOMATICALLY (Error/Warning level):
13
+ # - E, W: pycodestyle errors/warnings (except minor style issues)
14
+ # - F: flake8 errors (undefined names, imports, etc.)
15
+ # - B: bugbear rules (bug-prone patterns)
16
+ # - I: import sorting
17
+ # - N: pep8-naming
18
+ # - C4: comprehensions
19
+ # - TCH: type checking hints
20
+ # - UP: pyupgrade (modernize code)
21
+ # - PLC, PLR, PLW: pylint rules (errors only, not warnings)
22
+ # - RUF: ruff-specific rules
23
+ # - SIM: flake8-simplify (simplify code)
24
+ # - BLE: flake8-blind-except
25
+ # - A: flake8-builtins (shadowing builtins)
26
+ #
27
+ # 🟡 IGNORE (Warning only, or suppress entirely):
28
+ # - D: pydocstyle (we use comprehensive docstrings)
29
+ # - ERA: eradicate (commented code)
30
+ # - FA: flake8-future-annotations (we use future imports)
31
+ # - PGH: pygrep-hooks (not needed)
32
+ # - PT: flake8-pytest-style (we have our own conventions)
33
+ # - RSE: flake8-raise (we use logging for errors)
34
+ # - S: flake8-bandit (security - we handle security manually)
35
+ # - SLF: flake8-self (private method access)
36
+ # - TID: flake8-tidy-imports (we allow relative imports)
37
+ # - TRY: tryceratops (we handle exceptions intentionally)
38
+ # - W191, W291, W293: Minor style issues (let humans decide)
39
+ # - PYI034, PYI036: Type hints (handled with type: ignore if needed)
40
+ # - C901: Too complex function (we keep complexity manageable)
41
+ # - PLR0911-PLR0917: Return/branch/arg/statement counts (we manage manually)
42
+ # - RUF012: Mutable defaults (we use dataclasses with field())
43
+ # - SIM102: Nested if statements (useful in some cases)
44
+ # - BLE001: Blind except (we handle specific exception types)
45
+ # - PLC0415: Function-level imports (needed for CLI)
46
+ # - PLR2004: Magic values (intentional)
47
+ # - PLW2901: Loop variable overwritten (intentional)
48
+ # - E712: Equality with True (use for clarity)
49
+ # - B007: Unused loop variable (intentional pattern)
50
+ # - PLW0603: Global statement (used in logger init)
51
+ # - TC003: Type checking imports (conditional use)
52
+ # - E501: Line too long (we have 135 char limit)
53
+ #
54
+ # ❌ NEVER FIX (Critical issues we must handle manually):
55
+ # - No critical security issues (S*) - we handle security manually
56
+ # - No deprecated code (deprecated is warning only)
57
+ # - No known issues in production code
58
+ # ───────────────────────────────────────────────────────────────────────────────
59
+
60
+ # Line length (matches pep8, but we allow longer lines for docstrings)
61
+ line-length = 135
62
+
63
+ # Target Python version
64
+ target-version = "py310"
65
+
66
+ # Exclude patterns
67
+ exclude = [
68
+ "__pycache__",
69
+ ".git",
70
+ "dist",
71
+ "build",
72
+ ".venv",
73
+ "venv",
74
+ ".pytest_cache",
75
+ ".mypy_cache",
76
+ ".pytype",
77
+ "*.egg-info",
78
+ ]
79
+
80
+ # Enable automatic fixing (safe fixes only)
81
+ fix = true
82
+
83
+ [lint]
84
+ select = [
85
+ # pycodestyle errors and warnings
86
+ "E", "W",
87
+ # flake8
88
+ "F",
89
+ # bugbear
90
+ "B",
91
+ # isort
92
+ "I",
93
+ # pep8 naming
94
+ "N",
95
+ # comprehensions
96
+ "C4",
97
+ # type checking
98
+ "TCH",
99
+ # pyupgrade
100
+ "UP",
101
+ # pylint (errors only, not warnings)
102
+ "PLC", "PLR", "PLW",
103
+ # ruff-specific
104
+ "RUF",
105
+ # flake8-simplify
106
+ "SIM",
107
+ # flake8-blind-except
108
+ "BLE",
109
+ # flake8-builtins
110
+ "A",
111
+ ]
112
+
113
+ # Rules to ignore
114
+ ignore = [
115
+ "W191", # Tab before character
116
+ "W291", # Trailing whitespace (minor)
117
+ "W293", # Blank line with whitespace (minor)
118
+ "PYI034", # Non-self return type
119
+ "PYI036", # Bad exit annotation
120
+ "C901", # Too complex function
121
+ "PLR0911", # Too many return statements
122
+ "PLR0912", # Too many branches
123
+ "PLR0913", # Too many arguments
124
+ "PLR0915", # Too many statements
125
+ "PLR0917", # Too many positional arguments
126
+ "RUF012", # Mutable default value
127
+ "SIM102", # Collapsible if
128
+ "BLE001", # Blind except
129
+ "PLC0415", # Import at function level (CLI needs late imports)
130
+ "PLR2004", # Magic values
131
+ "PLW2901", # Loop variable overwritten
132
+ "E712", # Equality with True
133
+ "B007", # Unused loop variable
134
+ "PLW0603", # Global statement
135
+ "PLW0602", # Global statement with no assignment (logger.py)
136
+ "TC003", # Type checking imports
137
+ "E501", # Line too long (we have 135 char limit)
138
+ ]
139
+
140
+ [lint.per-file-ignores]
141
+ "src/agent_nook/config/loader.py" = ["TID252"]
142
+ "src/agent_nook/config/config.py" = ["TID252"]
143
+ "src/agent_nook/sandbox/builder.py" = ["TID252"]
144
+
145
+ [lint.isort]
146
+ force-single-line = true
147
+ lines-after-imports = 2
148
+ known-first-party = ["agent_nook"]
149
+
150
+ [lint.pyflakes]
151
+ allowed-unused-imports = []
152
+
153
+ [lint.pycodestyle]
154
+ max-line-length = 135
@@ -0,0 +1,52 @@
1
+ # AGENTS.md — Rules & Guidelines
2
+
3
+ ## Core Principles
4
+ - **Simplicity first**: Straightforward solutions, no overengineering.
5
+ - **Solid engineering**: Follow best practices, document everything.
6
+ - **Unprivileged**: NO root/sudo. Runs as normal user.
7
+ - **Sandboxing**: Bubblewrap (`bwrap`) ONLY. NO podman, docker, or apptainer. No dedicated images; starts from host FS. Uses `--unshare=cgroup`.
8
+
9
+ ## XDG Configuration & Paths
10
+ - **Config**: `~/.config/agent-nook/sandbox.yaml` (default copied from `src/agent_nook/config/sandbox.yaml`), `logging.yaml`
11
+ - **State/Logs**: `~/.local/state/agent-nook/`
12
+ - **Cache**: `~/.local/state/agent-nook/cache/`
13
+ - **Format**: Human-readable YAML. `/home` mounted as tmpfs by default.
14
+
15
+ ## Code Quality & DRY
16
+ - **NEVER DUPLICATE CODE**: Do not copy-paste logic. If you write the same logic twice, you failed.
17
+ - **Rule of Two (MANDATORY)**: If identical control flow, validation, or data processing appears ≥2 times, you MUST extract it into a shared, generic helper.
18
+ - **Search First**: Always check existing utilities before writing new helpers, array manipulations, or API wrappers. Re-use and extend existing code.
19
+ - **Single Source of Truth**: Business logic (validation, calculations, permissions) must exist in EXACTLY ONE authoritative location. NEVER duplicate domain rules across files.
20
+ - **Avoid Over-Engineering**: Keep extracted helpers minimal and single-purpose. No complex "do-everything" functions with boolean flags.
21
+ - **Permitted Duplication**: ONLY allowed for Unit/Integration tests (prioritize DAMP readability) and decoupled DTOs/schemas across distinct service boundaries.
22
+ - **Refactoring Protocol**: If removing duplication requires changing public signatures or adding shared modules, PROPOSE the plan first and wait for approval.
23
+
24
+ ## Testing & Verification (MANDATORY)
25
+ Run after EVERY change:
26
+ - `./run_tests.sh`
27
+ - `ruff check src/ --fix`
28
+ - `ruff format src/`
29
+ - Update tests for all behavior changes. Tests are the contract.
30
+
31
+ ## Linting (Ruff)
32
+ - **Tool**: Ruff only. Line length: 135.
33
+ - **Action**: Auto-fix safe violations. Use `# type: ignore` for intentional type issues.
34
+ - **Disabled entirely**: D, ERA, FA, PGH, PT, RSE, S, SLF, TID, TRY.
35
+ - **Ignore (manual review)**: W191, W291, W293, PYI034, PYI036, C901, PLR0911-PLR0917, RUF012, SIM102, BLE001, PLC0415, PLR2004, PLW2901, E712, B007, PLW0603, TC003, E501.
36
+
37
+ ## Documentation Contract
38
+ Update docs for ANY change:
39
+ - Signature change → Update docstrings.
40
+ - Config/CLI change → Update README + examples.
41
+ - New/Removed feature → Update README.
42
+ - **Pre-commit**: Verify docstrings, README examples run, and remove dead code.
43
+
44
+ ## Error Handling
45
+ - Wrap agent execution in `try/except` with specific exceptions.
46
+ - Log `ERROR` with stack traces for debugging.
47
+ - Use `sys.exit(non-zero)` for unrecoverable errors. Prominently report sandbox failures.
48
+
49
+ ## Git Hygiene (STRICT)
50
+ - ONLY `git add <specific files>` or `git add -p`.
51
+ - NEVER `git add -A` or `git add .`.
52
+ - NEVER delete untracked files unless explicitly requested or part of the feature. Ask if unsure.