Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
name: CI

# Explicit, not "*.md": CONTRIBUTING.md and docs/ are asserted/executed by tests (guarded by tests/test_documentation/test_ci_paths_ignore.py).
# Explicit, not "*.md": CONTRIBUTING.md, docs/, CLAUDE.md and AGENTS.md are asserted/executed by tests (guarded by tests/test_ci_paths_ignore.py).
# Safe only while CI is not a required status check on main: a skipped required check never reports, blocking docs-only PRs.
on:
push:
branches: [ "main" ]
paths-ignore: &docs_only
- AGENTS.md
- CLAUDE.md
- CODE_OF_CONDUCT.md
- NOTICE.md
- README.md
Expand Down
9 changes: 8 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- **Orchestration Only**: Coordinate Test-Driven Development cycles between specialized agents
- **No Code Implementation**: NEVER write implementation code or tests directly
- **Agent Delegation**: Use Red Agent for test writing, Green Agent for implementation
- **Session root**: `.claude/agents/red-agent.md` and `.claude/agents/green-agent.md` are only auto-loaded when Claude Code's session is rooted at this repository. If working from a parent or workspace directory, start a session inside the clone (`cd code/mloda && claude`) so the agents are available natively.

## TDD Workflow

Expand Down Expand Up @@ -51,7 +52,7 @@ Each phase should be a clean, validated checkpoint with all tests passing and ch
**CRITICAL**: If agent behavior is unexpected or incorrect:

1. **Update Agent Configuration**: Modify `.claude/agents/red-agent.md` or `.claude/agents/green-agent.md` to refine instructions, constraints, or workflow
2. **Update This File**: Modify `CLAUDE.md` and `AGENTS.md` (keep them in sync) to clarify orchestration rules or add missing guidance
2. **Update This File**: Modify `CLAUDE.md` and `AGENTS.md` (`tests/test_agent_docs_sync.py` pins them identical) to clarify orchestration rules or add missing guidance
3. **Document Changes**: Briefly explain what was learned and why the change improves behavior

This enables continuous learning and improvement of the TDD workflow based on actual usage patterns.
Expand Down Expand Up @@ -85,6 +86,12 @@ source .venv/bin/activate
- **`attribution/ATTRIBUTION.md`**: `tox` regenerates this file from the installed dependency versions on every run, so a dependency change shows up as a diff here. This is intended: commit the update as part of the same change so the tracked file stays current. The release workflow does not regenerate it; it ships the committed copy, so keeping it up to date in PRs is what keeps releases accurate.
- **Commits**: use [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `chore:`, `docs:`, `test:`, `refactor:`, `minor:`). semantic-release computes the next version. This project deviates from the standard: the minor version (middle number) bumps only on `minor:` commits; `feat:` is treated as a patch bump.

### mypy iteration notes

- `mypy --strict --ignore-missing-imports .` (what tox runs) checks `tests/` as well as `mloda/` and `mloda_plugins/`. Running `mypy mloda/` locally will miss test-tree type errors that block the tox gate. `tox` (or `tox -e python310`) is the only trustworthy mypy invocation.
- A stray local build (`python -m build` or `pip wheel .`) creates a `build/` directory. mypy picks it up and reports spurious errors. Clean it with `rm -rf build/` before running tox. The `[tool.mypy]` exclude list in `pyproject.toml` includes `build/` to prevent this at the gate.
- Running `mypy --strict .` in the dev venv (outside tox) may surface pyspark-related errors that tox's isolated env does not see; trust the tox run as the source of truth.

## Issue Creation

When filing a GitHub issue (via `gh issue create` or otherwise), follow the structure in `.github/ISSUE_TEMPLATE/issue.yml`:
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Each phase should be a clean, validated checkpoint with all tests passing and ch
**CRITICAL**: If agent behavior is unexpected or incorrect:

1. **Update Agent Configuration**: Modify `.claude/agents/red-agent.md` or `.claude/agents/green-agent.md` to refine instructions, constraints, or workflow
2. **Update This File**: Modify `CLAUDE.md` and `AGENTS.md` (keep them in sync) to clarify orchestration rules or add missing guidance
2. **Update This File**: Modify `CLAUDE.md` and `AGENTS.md` (`tests/test_agent_docs_sync.py` pins them identical) to clarify orchestration rules or add missing guidance
3. **Document Changes**: Briefly explain what was learned and why the change improves behavior

This enables continuous learning and improvement of the TDD workflow based on actual usage patterns.
Expand Down
39 changes: 39 additions & 0 deletions tests/test_agent_docs_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""CLAUDE.md and AGENTS.md are one instruction set for different agents, so the two files must be identical."""

import difflib
from pathlib import Path


PROJECT_ROOT = Path(__file__).resolve().parent.parent

# Naming both files here makes tests/test_ci_paths_ignore.py treat them as protected: a change to
# either must run the gate, so neither may stay in the ci.yaml docs-only paths-ignore list.
CLAUDE_MD = PROJECT_ROOT / "CLAUDE.md"
AGENTS_MD = PROJECT_ROOT / "AGENTS.md"


def _lines(path: Path) -> list[str]:
assert path.is_file(), f"{path.name} must exist at the project root: each agent reads only its own file."
# Bytes, not read_text(): universal-newline translation would let a CRLF rewrite compare equal.
return path.read_bytes().decode("utf-8").split("\n")


def _diff(claude: list[str], agents: list[str]) -> str:
"""Diff of repr-ed lines, so whitespace-only drift is not printed as two identical-looking lines."""
return "\n".join(
difflib.unified_diff(
[repr(line) for line in claude],
[repr(line) for line in agents],
fromfile="CLAUDE.md",
tofile="AGENTS.md",
lineterm="",
)
)


def test_claude_md_and_agents_md_are_identical() -> None:
claude, agents = _lines(CLAUDE_MD), _lines(AGENTS_MD)
assert claude == agents, (
f"CLAUDE.md and AGENTS.md have drifted:\n{_diff(claude, agents)}\n\n"
"Neither file carries content of its own: whatever belongs in one belongs in the other, verbatim."
)
2 changes: 2 additions & 0 deletions tests/test_ci_paths_ignore.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ def test_protected_set_is_not_stale() -> None:
protected = _protected_paths()
assert "docs/docs/index.md" in protected, "docs/ markdown is executed by tests/test_documentation"
assert "CONTRIBUTING.md" in protected, "CONTRIBUTING.md content is asserted by tests/test_project_structure.py"
assert "CLAUDE.md" in protected, "CLAUDE.md content is asserted by tests/test_agent_docs_sync.py"
assert "AGENTS.md" in protected, "AGENTS.md content is asserted by tests/test_agent_docs_sync.py"


def test_paths_ignore_declared_for_push_and_pull_request() -> None:
Expand Down
Loading