Skip to content

Commit 39a8931

Browse files
authored
Merge pull request #21 from GregoryKogan/dev
Dev
2 parents 8fb18aa + 69c6b4f commit 39a8931

103 files changed

Lines changed: 9179 additions & 350 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
description: Use the project Conda env yt-framework for all shell/Python commands; no ad-hoc venvs
3+
alwaysApply: true
4+
---
5+
6+
# Python environment (Conda)
7+
8+
Human-readable setup: [CONTRIBUTING.md](CONTRIBUTING.md) (Development Setup). Environment name: **`yt-framework`**.
9+
10+
## Default for agents
11+
12+
- Run verification and tooling through that env, for example:
13+
- `conda run -n yt-framework -- <command>`
14+
- Prefer `conda run` so activation hooks are not required. For compound commands from the repo root:
15+
- `conda run -n yt-framework -- bash -lc '...'`
16+
17+
## Do not
18+
19+
- Create `.venv` / `venv` or run `python -m venv` for routine tests, docs, or formatting unless the user explicitly asks.
20+
21+
## If Conda or the env is missing
22+
23+
- Do not silently fall back to a disposable venv; tell the user to follow [CONTRIBUTING.md](CONTRIBUTING.md).
24+
- If Conda is available but `yt-framework` does not exist, a one-time setup is acceptable (keep commands aligned with CONTRIBUTING):
25+
- `conda create -n yt-framework python=3.11 -y`
26+
- `conda run -n yt-framework -- python -m pip install -e ".[dev,docs]"` (from the repository root; use `python -m pip` so installs target the env, not a system/Homebrew `pip`)
27+
28+
## If Conda is not installed
29+
30+
- State that clearly; do not invent a replacement workflow without user direction.

.cursor/rules/documentation.mdc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
description: Keep user-facing docs aligned with code; verify coverage while working
3+
alwaysApply: true
4+
---
5+
6+
# Documentation maintenance
7+
8+
## Surfaces
9+
10+
Treat as documentation: `docs/**`, root `README.md`, `CONTRIBUTING.md`, and `examples/**/README.md` when the change touches behavior, CLI, config, or workflows those files describe. Public API docstrings follow [standards.mdc](standards.mdc); keep them consistent with any narrative docs that mention the same API.
11+
12+
## When to update docs
13+
14+
Update docs in the same change set when behavior could confuse a reader: new/changed CLI commands or flags, defaults, config keys, env vars, user- or operator-visible errors, pipeline/stage semantics, YT/S3 integration, Docker or code upload flows, troubleshooting symptoms, or anything else someone would look up under `docs/` or READMEs.
15+
16+
## Obligation
17+
18+
Update every affected surface (or add a focused new section for new behavior). Do not leave stale commands, options, or explanations that contradict the code.
19+
20+
## While working
21+
22+
- **Early**: Map edited modules/features to the relevant `docs/` pages and READMEs; read them and note gaps or drift.
23+
- **Before done**: Reconcile implementation with those docs; if public API changed, fix docstrings per `standards.mdc` and any prose that describes that API.
24+
25+
## Proof
26+
27+
- **Touches `docs/` or example/root README/CONTRIBUTING**: run `make -C docs html` after installing doc dependencies (e.g. `pip install -e ".[docs]"` using the `docs` optional extra in repo-root `pyproject.toml`); cite success or fix warnings/errors Sphinx reports.
28+
- **Docstrings only, no prose under `docs/` or those READMEs**: state that Sphinx was not required; still ensure docstrings match behavior.
29+
- **Pure prose doc edits**: `make -C docs html` is the primary verification.
30+
31+
Done = proof, same spirit as Standards: do not claim completion without evidence appropriate to what changed.

.cursor/rules/standards.mdc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
description: Plan-first, minimal-impact, verifiable changes; SOLID/DRY/KISS/YAGNI; types & style
3+
alwaysApply: true
4+
---
5+
6+
# Standards
7+
8+
## Plan & approval
9+
10+
- Non-trivial (3+ steps or architecture): Research → Plan → implement only after human approval. Artifacts: `./agent-artifacts/tasks/<task>/research.md`, `plan.md`, todo list. No implementation before approval.
11+
- Never deviate from an approved plan without asking; one approval does not cover later deviations.
12+
13+
## Impact & verification
14+
15+
- Touch only required files. Root-cause fixes over rewrites. No destructive git without request. No dead code.
16+
- Done = proof (test/log/command output). No "done" without evidence. Divergence → re-plan, don’t patch.
17+
18+
## Code
19+
20+
- SOLID, DRY, KISS, YAGNI. Explicit intent; no dead code. Clarify ambiguity before implementing.
21+
- Types: prefer dataclasses over `tuple[str,str,int]`, `Union[...]`, or big `dict[str, Any]`; use `Optional`, `List`, etc.; `TYPE_CHECKING` for type-only imports.
22+
- Public API: type hints + docstrings (Args/Returns); Literal for constrained params. Small, single-purpose functions; early returns; compact style (single-line calls, chained transforms, 88-char line). Prefer `assert` with message; comments only as `TODO:` for team decisions.
23+
- Quality: run `black --check --diff` on changed files; after changes, align diff with style and update .cursor/rules if needed.
24+
25+
## Artifacts & files
26+
27+
- Artifacts under `./cursor/artifacts/`: `tasks/<task>/`, `user-preferences/`, `project-details/`.
28+
- Ensure .cursor/rules exists and is up to date.
29+
30+
## Shell
31+
32+
- Use `cp`/`mv` for new/renamed files.
33+
- Avoid `| head`, `| tail`, `less`, `more` for monitoring; use command-native limits (e.g. `git log -n 10`) or read files directly.

.cursor/rules/test-authoring.mdc

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
description: Angry-Tests-aligned pytest style, naming, and mandatory debug loop for agents
3+
alwaysApply: true
4+
---
5+
6+
# Test authoring and debugging (Angry Tests)
7+
8+
Philosophy is aligned with Yegor Bugayenko’s [Angry Tests](https://www.yegor256.com/angry-tests.html) and related posts: [On the Layout of Tests](https://www.yegor256.com/2023/01/19/layout-of-tests.html), [single-statement / single-assertion tests](https://www.yegor256.com/2017/05/17/single-statement-unit-tests.html), and [unit testing anti-patterns](https://www.yegor256.com/2018/12/11/unit-testing-anti-patterns.html). Adaptations below are **pytest-specific** for this repo.
9+
10+
Cross-links: proof obligations in [testing.mdc](testing.mdc); Conda commands in [conda-environment.mdc](conda-environment.mdc); general code style in [standards.mdc](standards.mdc).
11+
12+
## Mindset
13+
14+
- Tests are a **safety net**: a test that turns red after a change did its job—it localized a mistake. Fix **production code** or **update the test deliberately** when the contract changed; do not weaken assertions to “make green” without a stated reason.
15+
- Prefer tests that **pinpoint** the broken unit or scenario so the failure message and test name narrow the search space (layout-of-tests).
16+
17+
## Naming and file placement
18+
19+
- Prefer **`tests/test_<module_basename>.py`** mapping to a single primary **system under test** (SUT), e.g. `test_job_command.py` for `yt_framework.operations.job_command`.
20+
- For flows that **do not** map 1:1 to one module, use **`tests/integration/`** (or `tests/it/`) and **scenario-oriented** module names (IT-style), still with clear docstrings.
21+
- Test function names describe **behavior or rule**, not `test1` / `test_foo` (layout-of-tests).
22+
23+
## Assertions (one logical outcome)
24+
25+
- Aim for **one logical outcome per test**: one main `assert`, or one `pytest.raises(...)` block, or one structured equality check. If you need two checks, split the test unless they are inseparable duplicates of the same outcome.
26+
- Use **`match=`** on `pytest.raises` when the message is part of the contract.
27+
- Use **`assert expr, "short reason"`** when the default pytest output would be obscure (layout-of-tests: descriptive failures).
28+
29+
## Structure: Arrange / Act / Assert
30+
31+
- Keep tests **short**. Visible three phases: build inputs → call the API → assert.
32+
- **Shared setup:** Prefer **fake objects** or small factories that live next to production code (or clearly named helpers) over opaque shared fixtures. Avoid “god” fixtures and cross-test coupling ([anti-patterns](https://www.yegor256.com/2018/12/11/unit-testing-anti-patterns.html)).
33+
- If you add **`tests/support/`**, keep helpers **explicit and minimal**; document what SUT they serve.
34+
35+
## Mocks
36+
37+
- Use **`unittest.mock` / `patch` sparingly**. This codebase is YT/S3-heavy: prefer **real pure logic**, **fakes**, **dev-mode** boundaries, or narrow integration tests over mocking large client surfaces unless there is no cheaper option.
38+
39+
## Mandatory fix loop (agents)
40+
41+
When a test fails or you are debugging tests, follow this loop **in order**; do not skip straight to refactors.
42+
43+
1. **Reproduce:** `conda run -n yt-framework -- pytest <path>::<test_name> -xvs` (or `-k` with a unique substring). Confirm the failure is stable.
44+
2. **Read:** Study the **assertion output**, **exception message**, and **traceback top**—identify the failing line in test and production code.
45+
3. **Locate SUT:** Open the production module that owns the behavior; confirm whether the test expectation or the code is wrong.
46+
4. **Hypothesis:** State one sentence: e.g. “Off-by-one in path normalization” or “Test encodes old API.”
47+
5. **Minimal change:** Apply the **smallest** edit that addresses that hypothesis only (standards: root-cause, no drive-by rewrites).
48+
6. **Re-run:** Same single test, then full `conda run -n yt-framework -- pytest`.
49+
7. **If still red:** Do **not** stack unrelated edits. Return to step 2 with fresh output. If the failure is environmental (Conda, missing env), fix the environment or document `--no-verify` only as an emergency (see CONTRIBUTING).
50+
51+
Proof of done: cite pytest output per [testing.mdc](testing.mdc).
52+
53+
## Project conventions
54+
55+
- Run pytest through **`conda run -n yt-framework --`** when verifying locally ([conda-environment.mdc](conda-environment.mdc)).
56+
- Test code: type hints and Black like production ([standards.mdc](standards.mdc)).
57+
- For breadth of coverage and repo status, see [.cursor/artifacts/project-details/testing-readiness-report.md](../artifacts/project-details/testing-readiness-report.md).

.cursor/rules/testing.mdc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
description: Keep automated and manual verification aligned with code; prove changes with pytest or documented alternatives
3+
alwaysApply: true
4+
---
5+
6+
# Testing maintenance
7+
8+
For **how** to write and debug tests (Angry Tests–aligned pytest style and mandatory fix loop), use [test-authoring.mdc](test-authoring.mdc). This file focuses on **when** to test and **proof** obligations.
9+
10+
## Surfaces
11+
12+
Treat as testing scope: `tests/**` for **pytest** (dev dependency in repo-root `pyproject.toml`). Complementary checks when automated coverage is thin or behavior is pipeline/integration-heavy: **dev mode** and **example pipelines** as described in `CONTRIBUTING.md` (Testing). Test code follows [standards.mdc](standards.mdc) for style and clarity (types, assertions, `black` on changed Python files).
13+
14+
## When to add or update tests
15+
16+
Add or extend tests in the same change set when you change behavior, fix bugs, adjust public contracts, or touch logic that already has pytest coverage. Prefer focused tests next to the modules they exercise under `tests/`. When the suite does not yet cover an area, use dev mode and/or a minimal example pipeline to verify—and add pytest when the behavior is stable enough to automate.
17+
18+
## Obligation
19+
20+
Do not claim a change is verified without evidence. Update or add tests so they match the new semantics; remove or rewrite assertions that encode obsolete behavior. If you rely on dev mode or examples instead of pytest, say what you ran and the outcome.
21+
22+
## While working
23+
24+
- **Early**: Identify which modules or flows changed and whether `tests/` already covers them; note gaps.
25+
- **Before done**: Run the relevant pytest scope (full suite or targeted). For integration-only or uncovered paths, run the dev-mode or example steps from `CONTRIBUTING.md` and record the result.
26+
27+
## Proof
28+
29+
Run commands through the project Conda env: [conda-environment.mdc](conda-environment.mdc) (e.g. `conda run -n yt-framework -- …`).
30+
31+
- **Touches production code or existing tests**: run pytest from the repo root, e.g. `conda run -n yt-framework -- pytest`. For a narrow check: `conda run -n yt-framework -- pytest tests/<file>.py` or `pytest path::test_name`. Optional coverage: `conda run -n yt-framework -- pytest --cov=yt_framework`.
32+
- **Integration-heavy or uncovered areas**: cite dev-mode or example runs per `CONTRIBUTING.md` (and add pytest when practical).
33+
- **Test-only or rule-only edits (no `docs/` or user-facing README/CONTRIBUTING changes)**: Sphinx is not required; still run pytest if Python tests changed.
34+
35+
Done = proof, same spirit as [standards.mdc](standards.mdc): do not claim completion without evidence appropriate to what changed.

.github/workflows/ci.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- "**"
7+
pull_request:
8+
branches: [main, dev]
9+
10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: true
13+
14+
jobs:
15+
test:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
21+
- name: Set up Python
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version: "3.11"
25+
26+
- name: Install dependencies
27+
run: |
28+
python -m pip install --upgrade pip
29+
pip install -e ".[dev]"
30+
31+
- name: Run tests
32+
run: |
33+
pytest \
34+
--cov=yt_framework \
35+
--cov=ytjobs \
36+
--cov-report=term-missing \
37+
--cov-report=json:coverage.json
38+
39+
- name: Publish coverage badge to gist
40+
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && vars.COVERAGE_GIST_ID != ''
41+
env:
42+
COVERAGE_GIST_ID: ${{ vars.COVERAGE_GIST_ID }}
43+
COVERAGE_GIST_TOKEN: ${{ secrets.COVERAGE_GIST_TOKEN }}
44+
run: |
45+
python <<'PY'
46+
import json
47+
import os
48+
import urllib.error
49+
import urllib.request
50+
51+
with open("coverage.json", encoding="utf-8") as f:
52+
cov = json.load(f)
53+
pct = round(float(cov["totals"]["percent_covered"]), 1)
54+
if pct >= 80.0:
55+
color = "brightgreen"
56+
elif pct >= 60.0:
57+
color = "yellow"
58+
else:
59+
color = "red"
60+
payload = {
61+
"schemaVersion": 1,
62+
"label": "coverage",
63+
"message": f"{pct}%",
64+
"color": color,
65+
}
66+
body = json.dumps(
67+
{"files": {"coverage-shields.json": {"content": json.dumps(payload)}}}
68+
)
69+
req = urllib.request.Request(
70+
f"https://api.github.com/gists/{os.environ['COVERAGE_GIST_ID']}",
71+
data=body.encode(),
72+
method="PATCH",
73+
headers={
74+
"Authorization": f"Bearer {os.environ['COVERAGE_GIST_TOKEN']}",
75+
"Accept": "application/vnd.github+json",
76+
"X-GitHub-Api-Version": "2022-11-28",
77+
},
78+
)
79+
try:
80+
with urllib.request.urlopen(req) as resp:
81+
assert 200 <= resp.status < 300, resp.status
82+
except urllib.error.HTTPError as e:
83+
raise SystemExit(f"gist PATCH failed: HTTP {e.code}") from e
84+
PY

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ Thumbs.db
1616

1717
# IDEs
1818
.vscode/
19-
.cursor/
19+
.cursor/*
20+
!.cursor/rules/
21+
!.cursor/rules/**
2022
.idea/
2123
*.swp
2224
*.swo
@@ -29,6 +31,10 @@ Thumbs.db
2931
# Checkpoints
3032
*.pt
3133

34+
# Coverage
35+
.coverage
36+
coverage.json
37+
3238
# Virtual environment
3339
.venv/
3440
venv/

.pre-commit-config.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
repos:
2+
- repo: https://github.com/psf/black
3+
rev: 26.3.1
4+
hooks:
5+
- id: black
6+
language_version: python3.11
7+
args: [--target-version=py311]
8+
9+
- repo: local
10+
hooks:
11+
- id: pytest
12+
name: pytest (pre-push)
13+
entry: bash -c 'conda run -n yt-framework -- pytest'
14+
language: system
15+
pass_filenames: false
16+
always_run: true
17+
stages: [pre-push]

0 commit comments

Comments
 (0)