Merge remaining tox/pytest/MSRV work (#31) into main - #32
Conversation
- scripts/validate-agents.sh used `declare -A`, which crashes on bash 3.2 (the default /bin/bash on every stock macOS install, despite the repo's own "Platform: macOS" badge). Replaced with a portable space-delimited set so the script actually runs on macOS. - tools/claude-pipeline.py `list` used a backslash-escaped quote inside an f-string nested in another f-string — a SyntaxError on every Python version, so `claude-pipeline list`/`--help` never worked at all. Hoisted the value into a local variable instead. - Added __pycache__/*.pyc to .gitignore (generated while testing tools/). Found while verifying the autonomy/lessons/MCP-guide changes in this branch.
Harness type (tight/loose/adaptive) controls output constraint; autonomy level is a separate axis for how much human checking a task needs before or after the AI acts. Adds the 5-level model (L0 human-only .. L4 fully autonomous, L2 draft+review as the common default) from the AI Agent autonomy article this branch is based on. - autonomy: field added to all 11 agent frontmatters, assigned per role - claude-harness.py: new required check (autonomy declared), `autonomy` subcommand printing the L0-L4 table, templates updated - harness-designer (09): new design step + Autonomy Level output field - docs/HARNESS-GUIDE.md(.ko): new Autonomy Levels section - README Agent Roster table: new Autonomy column - /harness command + cheatsheets: autonomy validate check + prompts Closes #26
claude-handoff captures session state; nothing in this project recorded WHY something failed and HOW it was fixed, so the next session (or agent) had no way to avoid repeating a past mistake. Unlike handoffs, lessons accumulate indefinitely and are searchable by tag/keyword rather than pruned by age. claude-lessons add # symptom / root cause / fix / tags claude-lessons list # recent, optionally filtered by tag claude-lessons search Q # keyword search claude-lessons context # pipeable into claude for session-start context Stdlib-only, mirrors tools/claude-handoff.py conventions. Wired into Makefile install-tools/status/test-python and install.sh's TOOLS array. Closes #27
Documents when to convert a CLI tool into an MCP server (Claude calls it mid-conversation) vs. keeping it a slash command or manual pipe (human stays in control of when it runs) — including a guideline against wrapping mutating/write actions as auto-callable MCP tools, tied to the autonomy levels added earlier in this branch. examples/mcp-lessons-server.py wraps tools/claude-lessons.py (add_lesson, search_lessons, recent_lessons) via the `mcp` Python SDK's FastMCP API. Lives under examples/, not tools/, since tools/ must stay dependency-free per docs/CONTRIBUTING.md — documented there as the one exception. Verified end-to-end against a real `mcp` install: `pip install mcp` now pulls a 2.x release that reworked/moved FastMCP, so the guide and example both pin `mcp>=1.2,<2`, confirmed working with 1.29.0. Closes #28
…guide - Agent Roster: new Autonomy column + explanation - Tools: 7 -> 8, new Tool 8 (claude-lessons) section, /lessons row, repo layout tree, context-cost-tips row - Nav bars + repo layout: MCP-GUIDE.md(.ko) link - README.ko.md also gets the harness/pipeline Tool 6/7 detail sections and full 11-agent repo layout it was missing — it had fallen out of sync with README.md (only the slash-command table and top badges had been updated when those tools were added), which this branch's changes would otherwise have made worse
* test: add pytest suite + tox for tools/*.py Nothing verified tools/*.py across Python versions before this — no pytest suite, no CI job, just a handful of ad-hoc --help/smoke commands in Makefile's test-python target. Adds: - tests/conftest.py: run_tool/home/git_repo fixtures — every test runs the real CLI as a subprocess against an isolated $HOME (and, for the two tools that shell out to git, an isolated git repo), so nothing touches the machine's real ~/.claude/ - tests/test_*.py: one file per tool, 44 tests total, functional not just --help (add/list/search/show roundtrips, error paths, exit codes) - tox.ini: py38-py313 (skip_missing_interpreters), lint (ruff check), fmt-check (ruff format --check) - pyproject.toml: scoped ruff config — E/F/I/UP only, not the full default ruleset (which includes bandit/blind-except/naive-datetime rules that fight this codebase's deliberate style), E402 ignored since every tool puts VERSION right after the docstring before imports - fmt-check / scripts/fmt.sh scoped to tools/claude-lessons.py + tests/ only, not all of tools/: the older tools use a deliberate hand-aligned style (aligned `=`, aligned dict values) that `ruff format` would flatten repo-wide — not forcing that as a side effect of adding tox - make tox: runs the full matrix (requires: pip install tox ruff) Verified locally: tox (py39/py312/py313 available here, py38/py310/py311 skip cleanly), lint, and fmt-check all pass. * fix: bugs found by the new pytest/tox matrix Real, independently-reproduced bugs the new test suite caught: - claude-cost.py: `set-budget` crashed with FileNotFoundError on a fresh install — `_save_budget` never created ~/.claude/ before writing cost-budget.json (snippet.py and claude-handoff.py already did this correctly; claude-cost.py's budget path was the one that didn't). - claude-remind.py / claude-review-diff.py: both use `X | None` (PEP 604) type annotations, which raise TypeError at import time on Python < 3.10 — every subcommand, including --help, was broken on 3.8/3.9 despite the "Python 3.8+" badge. Fixed with `from __future__ import annotations` (defers annotation evaluation, no behavior change). Confirmed via tox -e py39 before/after. Also applies the (small, low-risk) findings from the new ruff lint gate across tools/*.py: 4 unused imports, 1 dead local variable (claude-handoff.py's `list`, left over from a removed date column), 4 `if x: y` one-liners split to satisfy E701, and one ambiguous single- letter loop variable (`l` -> `line`). No behavior changes; tests still pass after each. * ci: add Python version matrix + Rust MSRV verification - python-tests job: runs `tox -e py` across Python 3.8-3.13 via actions/setup-python (this is what actually caught the claude-remind.py/claude-review-diff.py 3.8/3.9 breakage in the previous commit — nothing exercised those versions before) - python-lint job: `tox -e lint,fmt-check` - rust-version = "1.75" declared in claude-tools/Cargo.toml, matching the README's existing "Rust 1.75+" badge (previously just a claim, not enforced anywhere) - msrv job: `cargo msrv verify` — actually builds against the declared 1.75 toolchain instead of only ever testing against `stable` No cargo/rustc available in the environment this branch was authored in, so the Rust-side change (rust-version field + msrv CI job) is verified by CI on push, the same as the rest of this project's Rust checks. * docs: document tox/pytest and cargo-msrv workflow (EN/KO) README.md/.ko.md: Makefile section (make tox, make msrv), repo layout (tests/, tox.ini, pyproject.toml). Also fixes "make lint # clippy + ruff" in both READMEs — the lint target only ever ran clippy; ruff wasn't wired into it before this branch. docs/CONTRIBUTING.md/.ko.md: tox usage in Development Setup, tests/ step added to "Adding a New Tool", tox/cargo-msrv checks added to the PR checklist, cargo-msrv install/verify commands. Closes #30 * fix: MSRV verify failed — Cargo.lock is lockfile v4, needs cargo 1.78+ CI caught this immediately: cargo-msrv installs rustc 1.75 per the declared rust-version, but that toolchain's bundled cargo can't parse Cargo.lock's `version = 4` format (stabilized in cargo 1.78) — a structural incompatibility independent of whether the crate's actual code needs 1.75 or newer. The pre-existing "Rust 1.75+" badge this number was copied from was apparently never verified against the checked-in lockfile either. Bumped rust-version to 1.78 (README/README.ko badges updated to match) and letting CI's msrv job re-verify — no cargo available locally to confirm further. * ci: use cargo-msrv find instead of verify — real MSRV is unknown 1.78 also failed: a transitive dep (clap 4.6.1's Cargo.toml) requires the `edition2024` cargo feature, stabilized in Cargo 1.85 — unrelated to claude-tools' own code, just how new the pinned dependency tree is. No local cargo to bisect this by hand, and guessing one version per push-and-wait-5-minutes cycle is slow. `cargo msrv find` bisects the actual minimum compatible version in one run instead of asserting a guess. Once it reports the real number, Cargo.toml's rust-version will be set to match and the job switched back to `verify`.
📝 WalkthroughWalkthroughPython 도구의 subprocess 테스트 스위트와 공통 fixture를 추가했다. tox와 Ruff 검사를 CI에 연결했다. Rust MSRV를 1.78로 지정하고 검증 작업을 추가했다. README와 기여 문서를 새 절차에 맞게 갱신했다. Changes품질 검증 체계
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ci.yml:
- Around line 171-173: Update the “Discover the real MSRV” workflow step to
validate the declared Rust 1.78 MSRV instead of discovering a version: replace
the cargo msrv find operation with cargo msrv verify, or run cargo check
--locked using the Rust 1.78 toolchain.
- Line 71: Update all nine actions/checkout steps in .github/workflows/ci.yml at
lines 54, 71, 91, 108, 126, 144, 160, 184, and 214 to disable persisted
credentials with persist-credentials: false, and add top-level permissions
granting contents: read.
In `@Makefile`:
- Around line 76-80: Add msrv to the .PHONY declaration alongside the existing
test and tox targets, ensuring the msrv validation target always runs even when
an msrv file or directory exists.
In `@tests/test_claude_cost.py`:
- Around line 35-36: Update the monthly-output assertions in the test around
run_tool("claude-cost", "month") to verify that the configured $20.00 budget or
its budget status appears in the command output, while retaining the successful
return-code check.
In `@tests/test_claude_review_diff.py`:
- Around line 34-38: Update test_staged_only_reviews_staged_changes to stage
README.md after writing the change, then assert the command succeeds without “No
diff found” and that the staged diff output contains the staged README content,
while preserving verification that unstaged changes are excluded.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 49421d08-8a3a-415a-a67f-9c59192149f9
📒 Files selected for processing (26)
.github/workflows/ci.ymlMakefileREADME.ko.mdREADME.mddocs/CONTRIBUTING.ko.mddocs/CONTRIBUTING.mdpyproject.tomlrust/claude-tools/Cargo.tomlscripts/fmt.shtests/conftest.pytests/test_claude_cost.pytests/test_claude_handoff.pytests/test_claude_harness.pytests/test_claude_lessons.pytests/test_claude_pipeline.pytests/test_claude_remind.pytests/test_claude_review_diff.pytests/test_snippet.pytools/claude-cost.pytools/claude-handoff.pytools/claude-harness.pytools/claude-lessons.pytools/claude-pipeline.pytools/claude-remind.pytools/claude-review-diff.pytox.ini
| matrix: | ||
| python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] | ||
| steps: | ||
| - uses: actions/checkout@v4 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 3 'permissions:|actions/checkout|persist-credentials|pull_request:|push:' \
.github/workflows/ci.ymlRepository: BcKmini/claude-code-use
Length of output: 1987
🏁 Script executed:
#!/bin/bash
set -euo pipefail
cat -n .github/workflows/ci.ymlRepository: BcKmini/claude-code-use
Length of output: 7137
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
path = Path(".github/workflows/ci.yml")
lines = path.read_text().splitlines()
jobs = []
current_job = None
checkout_sites = []
for number, line in enumerate(lines, 1):
job = re.match(r"^ ([A-Za-z0-9_-]+):\s*$", line)
if job and number >= 46:
current_job = job.group(1)
jobs.append(current_job)
if re.search(r"^\s+- uses:\s+actions/checkout@v4\s*$", line):
checkout_sites.append((number, current_job))
print("checkout sites:")
for line, job in checkout_sites:
print(f" line {line}: job={job}")
print(f"total checkout sites: {len(checkout_sites)}")
print("jobs with checkout:", ", ".join(job for _, job in checkout_sites))
print("workflow permissions declaration:",
any(re.match(r"^permissions:\s*$", line) for line in lines))
print("checkout sites with persist-credentials: false:")
for line, job in checkout_sites:
following = lines[line:line+3]
if any("persist-credentials: false" in item for item in following):
print(f" line {line}: job={job}")
PYRepository: BcKmini/claude-code-use
Length of output: 626
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
path = Path(".github/workflows/ci.yml")
lines = path.read_text().splitlines()
current_job = None
checkout_sites = []
for number, line in enumerate(lines, 1):
job = re.match(r"^ ([A-Za-z0-9_-]+):\s*$", line)
if job and number >= 46:
current_job = job.group(1)
if re.search(r"^\s+- uses:\s+actions/checkout@v4\s*$", line):
checkout_sites.append((number, current_job))
print("checkout sites:")
for line, job in checkout_sites:
print(f" line {line}: job={job}")
print(f"total checkout sites: {len(checkout_sites)}")
print("workflow permissions declaration:",
any(re.match(r"^permissions:\s*$", line) for line in lines))
print("checkout sites with persist-credentials: false:")
for line, job in checkout_sites:
following = lines[line:line+3]
if any("persist-credentials: false" in item for item in following):
print(f" line {line}: job={job}")
PYRepository: BcKmini/claude-code-use
Length of output: 514
모든 actions/checkout 단계에서 토큰 지속 저장을 비활성화하세요.
9개 checkout 단계(54, 71, 91, 108, 126, 144, 160, 184, 214행)에 persist-credentials: false를 추가하세요. 각 작업은 checkout 후 저장소 제어 코드를 실행하므로, 지속된 토큰을 읽을 수 있습니다. 또한 최상위에 permissions: contents: read를 설정하세요.
🧰 Tools
🪛 zizmor (1.29.0)
[warning] 71-71: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
📍 Affects 1 file
.github/workflows/ci.yml#L71-L71(this comment).github/workflows/ci.yml#L91-L91.github/workflows/ci.yml#L160-L160
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci.yml at line 71, Update all nine actions/checkout steps
in .github/workflows/ci.yml at lines 54, 71, 91, 108, 126, 144, 160, 184, and
214 to disable persisted credentials with persist-credentials: false, and add
top-level permissions granting contents: read.
Source: Linters/SAST tools
| - name: Discover the real MSRV | ||
| working-directory: rust/claude-tools | ||
| run: cargo msrv find |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
In cargo-msrv documentation, what is the behavioral difference between cargo msrv findandcargo msrv verify? Does cargo msrv findfail when the discovered MSRV differs frompackage.rust-version in Cargo.toml?
💡 Result:
In cargo-msrv, the primary behavioral difference between find and verify lies in their objective: - cargo msrv find is used to discover what the minimum supported Rust version (MSRV) of a project is [1][2][3]. It performs a search (binary or linear) across available Rust toolchains to identify the earliest toolchain that satisfies the project's requirements [3]. - cargo msrv verify is used to confirm whether a project is compatible with a pre-defined MSRV, typically one already specified in the project's Cargo.toml manifest (via package.rust-version or package.metadata.msrv) [1][2][4]. It does not perform a search; instead, it runs a single compatibility check against the specified version and returns success (exit code 0) or failure (non-zero exit code) [1][4]. Regarding your second question: No, cargo msrv find does not fail specifically because the discovered MSRV differs from the package.rust-version in Cargo.toml. The purpose of cargo msrv find is to independently determine the MSRV based on the project's actual compatibility [3]. If you run cargo msrv find and the resulting MSRV does not match the version currently written in your Cargo.toml, the tool simply reports the newly discovered version (and can optionally update the manifest if the --write-msrv flag is used) [5][3]. The tool treats the discovery process as an independent assessment of compatibility [3]. If you want to check for discrepancies, you would typically use cargo msrv verify, which evaluates whether the currently defined MSRV is still valid [4].
Citations:
- 1: https://docs.rs/crate/cargo-msrv/latest
- 2: https://github.com/foresterre/cargo-msrv/blob/main/README.md
- 3: https://gribnau.dev/cargo-msrv/commands/find.html
- 4: https://gribnau.dev/cargo-msrv/commands/verify.html
- 5: https://gribnau.dev/cargo-msrv/output-formats/human.html
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- workflow context ---'
sed -n '145,185p' .github/workflows/ci.yml
printf '%s\n' '--- MSRV declarations and cargo-msrv setup ---'
rg -n -C 3 'cargo-msrv|msrv|rust-version' .github/workflows/ci.yml rust/claude-tools/Cargo.tomlRepository: BcKmini/claude-code-use
Length of output: 2750
선언된 MSRV를 검증하세요.
cargo msrv find는 최소 버전을 탐색할 뿐, rust-version = "1.78"의 호환성을 검증하지 않습니다. cargo msrv verify를 사용하거나 Rust 1.78 toolchain에서 cargo check --locked를 실행하세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci.yml around lines 171 - 173, Update the “Discover the
real MSRV” workflow step to validate the declared Rust 1.78 MSRV instead of
discovering a version: replace the cargo msrv find operation with cargo msrv
verify, or run cargo check --locked using the Rust 1.78 toolchain.
| .PHONY: test test-rust test-python test-agents tox | ||
| test: test-rust test-python ## Run all tests (fast, zero-dependency smoke checks) | ||
|
|
||
| tox: ## Full Python test matrix + lint (requires: pip install tox ruff) | ||
| tox |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
msrv를 .PHONY에 추가하세요.
msrv 파일 또는 디렉터리가 존재하면 make msrv가 Line 120의 검증 명령을 실행하지 않습니다. 문서가 이 타겟을 검증 절차로 안내하므로 항상 실행되게 해야 합니다.
수정 예시
-.PHONY: test test-rust test-python test-agents tox
+.PHONY: test test-rust test-python test-agents tox msrv📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .PHONY: test test-rust test-python test-agents tox | |
| test: test-rust test-python ## Run all tests (fast, zero-dependency smoke checks) | |
| tox: ## Full Python test matrix + lint (requires: pip install tox ruff) | |
| tox | |
| .PHONY: test test-rust test-python test-agents tox msrv | |
| test: test-rust test-python ## Run all tests (fast, zero-dependency smoke checks) | |
| tox: ## Full Python test matrix + lint (requires: pip install tox ruff) | |
| tox |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Makefile` around lines 76 - 80, Add msrv to the .PHONY declaration alongside
the existing test and tox targets, ensuring the msrv validation target always
runs even when an msrv file or directory exists.
| month = run_tool("claude-cost", "month") | ||
| assert month.returncode == 0 |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
월별 출력에서 설정한 예산을 검증하십시오.
Line 35-36은 month 명령의 종료 코드만 검사합니다. 저장한 $20.00 예산을 month가 읽지 않아도 이 테스트는 통과합니다. 월별 출력에 예산 값 또는 예산 상태가 표시되는지 검증하십시오.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/test_claude_cost.py` around lines 35 - 36, Update the monthly-output
assertions in the test around run_tool("claude-cost", "month") to verify that
the configured $20.00 budget or its budget status appears in the command output,
while retaining the successful return-code check.
| def test_staged_only_reviews_staged_changes(run_tool, git_repo): | ||
| (git_repo / "README.md").write_text("unstaged change\n", encoding="utf-8") | ||
| r = run_tool("claude-review-diff", "--staged", cwd=git_repo) | ||
| assert r.returncode == 0 | ||
| assert "No diff found" in r.stderr |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
--staged의 양성 경로를 검증하십시오.
Line 34-38은 unstaged 변경이 제외되는지만 검사합니다. 구현이 항상 No diff found를 반환해도 이 테스트는 통과합니다. 변경을 git add한 뒤 출력에 staged diff와 staged 내용이 포함되는지 검사하십시오.
수정 예시
+import subprocess
+
def test_staged_only_reviews_staged_changes(run_tool, git_repo):
- (git_repo / "README.md").write_text("unstaged change\n", encoding="utf-8")
+ readme = git_repo / "README.md"
+ readme.write_text("staged change\n", encoding="utf-8")
+ subprocess.run(["git", "add", "README.md"], cwd=git_repo, check=True)
+ readme.write_text("unstaged change\n", encoding="utf-8")
+
r = run_tool("claude-review-diff", "--staged", cwd=git_repo)
assert r.returncode == 0
- assert "No diff found" in r.stderr
+ assert "## Diff" in r.stdout
+ assert "staged change" in r.stdout
+ assert "unstaged change" not in r.stdout📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def test_staged_only_reviews_staged_changes(run_tool, git_repo): | |
| (git_repo / "README.md").write_text("unstaged change\n", encoding="utf-8") | |
| r = run_tool("claude-review-diff", "--staged", cwd=git_repo) | |
| assert r.returncode == 0 | |
| assert "No diff found" in r.stderr | |
| import subprocess | |
| def test_staged_only_reviews_staged_changes(run_tool, git_repo): | |
| readme = git_repo / "README.md" | |
| readme.write_text("staged change\n", encoding="utf-8") | |
| subprocess.run(["git", "add", "README.md"], cwd=git_repo, check=True) | |
| readme.write_text("unstaged change\n", encoding="utf-8") | |
| r = run_tool("claude-review-diff", "--staged", cwd=git_repo) | |
| assert r.returncode == 0 | |
| assert "## Diff" in r.stdout | |
| assert "staged change" in r.stdout | |
| assert "unstaged change" not in r.stdout |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/test_claude_review_diff.py` around lines 34 - 38, Update
test_staged_only_reviews_staged_changes to stage README.md after writing the
change, then assert the command succeeds without “No diff found” and that the
staged diff output contains the staged README content, while preserving
verification that unstaged changes are excluded.
PR #29 was squash-merged into `main` (commit c04b867), but PR #31 was opened against `feat/autonomy-lessons-mcp` (not `main`) and merged there with a regular merge — so PR #31's actual content (tox.ini, pytest suite, pyproject.toml, CI Python/MSRV jobs, the bug fixes it found) never made it into `main`. `main` only has PR #29's content right now.
This PR brings the rest across. Since main already has PR #29's changes (via the squash commit) and this branch has the same changes via its original commits plus PR #31 on top, this should merge cleanly with no new content conflicts for the PR #29 portion — only PR #31's actual new files/changes should show as the diff.
Not merging automatically — flagging for review since it touches main.
Summary by CodeRabbit
테스트
문서
개선