From 5eca7d9507ccff501e9c65c9fc75fba98cfff786 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Mon, 26 Jan 2026 19:19:57 +0800 Subject: [PATCH 01/18] Add AGENT.md --- AGENT.md | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 AGENT.md diff --git a/AGENT.md b/AGENT.md new file mode 100644 index 00000000..91ab4ab2 --- /dev/null +++ b/AGENT.md @@ -0,0 +1,95 @@ +# Git-Mastery Exercises - AI Assistant Context + +This repository contains modular Git exercises with automated validation. Each exercise teaches specific Git concepts through hands-on practice. + +## Quick Start for AI Assistants + +This project uses the **Agent Skills** standard for AI documentation. Detailed instructions are in `.claude/skills/`: + +- **[project-overview](file:.claude/skills/project-overview/SKILL.md)**: Repository structure, architecture, and workflows +- **[exercise-development](file:.claude/skills/exercise-development/SKILL.md)**: Creating and modifying exercises +- **[exercise-utils](file:.claude/skills/exercise-utils/SKILL.md)**: Shared utility modules reference +- **[coding-standards](file:.claude/skills/coding-standards/SKILL.md)**: Code style and quality guidelines +- **[testing](file:.claude/skills/testing/SKILL.md)**: Testing patterns and best practices + +## Repository Structure + +``` +exercises/ +├── / # 40+ self-contained exercises +│ ├── download.py # Setup logic +│ ├── verify.py # Validation logic +│ ├── test_verify.py # Pytest tests +│ ├── README.md # Student instructions +│ └── res/ # (Optional) Resources +├── hands_on/ # Exploratory scripts (no validation) +├── exercise_utils/ # Shared utilities +│ ├── git.py # Git command wrappers +│ ├── github_cli.py # GitHub CLI wrappers +│ ├── cli.py # General CLI helpers +│ ├── gitmastery.py # Git-Mastery specific helpers +│ ├── file.py # File operations +│ └── test.py # Test scaffolding +├── .claude/skills/ # AI assistant documentation +├── setup.sh # Environment setup +├── test.sh # Test runner +└── requirements.txt # Python dependencies +``` + +## Key Principles + +1. **Use Shared Utilities**: Always use functions from `exercise_utils/` instead of raw subprocess calls +2. **Exercise Isolation**: Each exercise is completely independent and self-contained +3. **Composable Validation**: Use git-autograder's rule system for validation +4. **Comprehensive Testing**: Every exercise must have thorough test coverage +5. **Type Safety**: Use type hints for all functions + +## Common Tasks + +### Create New Exercise +```bash +./new.sh # Interactive scaffolding +``` + +### Test Exercise +```bash +./test.sh # Runs pytest with verbose output +``` + +### Setup Environment +```bash +./setup.sh # Creates venv and installs dependencies +``` + +## Tech Stack + +- **Python**: 3.8+ (development on 3.13) +- **Testing**: pytest, git-autograder, repo-smith +- **Quality**: ruff (linting/formatting), mypy (type checking) +- **External**: Git CLI, GitHub CLI (gh) + +## Development Workflow + +1. Understand exercise requirements from issue/discussion +2. Use `./new.sh` to scaffold exercise structure +3. Implement `download.py` (setup), `verify.py` (validation), `README.md` (instructions) +4. Write comprehensive tests in `test_verify.py` +5. Run `./test.sh ` to verify +6. Ensure code passes: `ruff format . && ruff check . && mypy .` + +## Documentation + +- **Developer Guide**: https://git-mastery.github.io/developers +- **Exercise Directory**: https://git-mastery.github.io/exercises +- **Contributing**: [.github/CONTRIBUTING.md](.github/CONTRIBUTING.md) + +## For More Details + +Load the skills in `.claude/skills/` for comprehensive documentation on: +- Repository architecture and patterns +- Exercise development lifecycle +- Utility module API reference +- Coding standards and conventions +- Testing strategies and patterns + +Each skill file follows the Agent Skills standard and provides detailed, actionable guidance for working with this repository. From 868cb545fd947e7d6841dcf1f923531af2b4e322 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Mon, 26 Jan 2026 20:01:55 +0800 Subject: [PATCH 02/18] Add coding-standards skill --- .claude/skills/coding-standards/SKILL.md | 141 +++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 .claude/skills/coding-standards/SKILL.md diff --git a/.claude/skills/coding-standards/SKILL.md b/.claude/skills/coding-standards/SKILL.md new file mode 100644 index 00000000..a13042cc --- /dev/null +++ b/.claude/skills/coding-standards/SKILL.md @@ -0,0 +1,141 @@ +--- +name: coding-standards +description: Code style and quality guidelines. Use when writing or reviewing code to ensure consistency. +--- + +# Coding Standards + +## Overview +Follow these standards to ensure consistency, readability, and maintainability. + +## Quick Rules + +### Style +- **Line length**: 88 characters (Black/Ruff default) +- **Indentation**: 4 spaces +- **Formatter**: ruff (`ruff format .`) +- **Linter**: ruff (`ruff check .`) +- **Type checker**: mypy + +### Naming +- **Functions/variables**: `snake_case` +- **Constants**: `UPPER_SNAKE_CASE` +- **Classes**: `PascalCase` +- **Directories**: `kebab-case` + +### Type Hints +```python +# Always include types +def my_function(param: str, verbose: bool) -> Optional[str]: + pass +``` + +## Detailed Guides + +📄 **[style-guide.md](style-guide.md)** - Formatting, line length, imports, indentation + +📄 **[naming-conventions.md](naming-conventions.md)** - Functions, classes, files, directories + +📄 **[type-hints.md](type-hints.md)** - Using type annotations, common patterns + +📄 **[documentation.md](documentation.md)** - Docstrings, comments, when to document + +📄 **[best-practices.md](best-practices.md)** - DRY, composition, error handling, patterns + +## Quick Checks + +```bash +# Format code +ruff format . + +# Check linting +ruff check . + +# Auto-fix +ruff check --fix . + +# Type check +mypy / + +# All checks +ruff format . && ruff check . && mypy . +``` + +## Common Patterns + +### Import Organization +```python +# 1. Standard library +import os +from typing import List, Optional + +# 2. Third party +from git_autograder import GitAutograderExercise + +# 3. Local +from exercise_utils.git import add, commit +``` + +### Function Documentation +```python +def merge_with_message( + target_branch: str, ff: bool, message: str, verbose: bool +) -> None: + """Merge target branch with custom message. + + Args: + target_branch: Branch to merge + ff: Allow fast-forward if True + message: Custom commit message + verbose: Print output if True + """ + pass +``` + +## Anti-Patterns + +### ❌ Don't +```python +# Raw subprocess +import subprocess +subprocess.run(["git", "add", "file.txt"]) + +# No type hints +def process(data): + return data.strip() + +# Magic values +if count > 5: + pass +``` + +### ✅ Do +```python +# Use wrappers +from exercise_utils.git import add +add(["file.txt"], verbose) + +# Type hints +def process(data: str) -> str: + return data.strip() + +# Named constants +MAX_RETRIES = 5 +if count > MAX_RETRIES: + pass +``` + +## Pre-Commit Checklist + +- ✓ `ruff format .` - Code formatted +- ✓ `ruff check .` - No lint errors +- ✓ `mypy .` - Type hints valid +- ✓ Docstrings on public functions +- ✓ No hardcoded values +- ✓ Imports organized + +## Related Skills + +- **[exercise-development](../exercise-development/SKILL.md)** - Applying standards +- **[exercise-utils](../exercise-utils/SKILL.md)** - Using utilities correctly +- **[testing](../testing/SKILL.md)** - Test code standards From 2833de836462f827272ef48a2240926c7557f68b Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Mon, 26 Jan 2026 20:20:21 +0800 Subject: [PATCH 03/18] Add coding-standards skill --- .../skills/coding-standards/best-practices.md | 358 ++++++++++++++++++ .../skills/coding-standards/documentation.md | 219 +++++++++++ .../coding-standards/naming-conventions.md | 140 +++++++ .../skills/coding-standards/style-guide.md | 126 ++++++ .claude/skills/coding-standards/type-hints.md | 213 +++++++++++ 5 files changed, 1056 insertions(+) create mode 100644 .claude/skills/coding-standards/best-practices.md create mode 100644 .claude/skills/coding-standards/documentation.md create mode 100644 .claude/skills/coding-standards/naming-conventions.md create mode 100644 .claude/skills/coding-standards/style-guide.md create mode 100644 .claude/skills/coding-standards/type-hints.md diff --git a/.claude/skills/coding-standards/best-practices.md b/.claude/skills/coding-standards/best-practices.md new file mode 100644 index 00000000..675d81e2 --- /dev/null +++ b/.claude/skills/coding-standards/best-practices.md @@ -0,0 +1,358 @@ +# best-practices.md + +## DRY (Don't Repeat Yourself) + +### Extract Common Logic + +**Bad:** +```python +def fork_repo(repo_name: str, fork_name: str, verbose: bool): + result = subprocess.run(["gh", "repo", "fork", repo_name], capture_output=True) + if verbose: + print(result.stdout) + +def delete_repo(repo_name: str, verbose: bool): + result = subprocess.run(["gh", "repo", "delete", repo_name], capture_output=True) + if verbose: + print(result.stdout) +``` + +**Good:** +```python +def run(command: List[str], verbose: bool) -> CommandResult: + result = subprocess.run(command, capture_output=True) + if verbose: + print(result.stdout) + return CommandResult(result) + +def fork_repo(repo_name: str, fork_name: str, verbose: bool): + run(["gh", "repo", "fork", repo_name], verbose) + +def delete_repo(repo_name: str, verbose: bool): + run(["gh", "repo", "delete", repo_name], verbose) +``` + +## Error Handling + +### Early Returns + +**Good:** +```python +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + if not os.path.exists(shopping_list_file_path): + raise exercise.wrong_answer([SHOPPING_LIST_FILE_MISSING]) + + if not added_items: + comments.append(NO_ADD) + + if not deleted_items: + comments.append(NO_REMOVE) + + if comments: + raise exercise.wrong_answer(comments) + + return exercise.to_output(["Success"], GitAutograderStatus.SUCCESSFUL) +``` + +### Specific Exceptions + +```python +try: + result = subprocess.run(command, check=True) +except FileNotFoundError: + # Handle missing command + pass +except PermissionError: + # Handle permission issues + pass +except subprocess.CalledProcessError as e: + # Handle command failure + pass +``` + +## Composition Over Inheritance + +### Prefer Helper Classes + +**Good:** +```python +class GitMasteryHelper(Helper): + def __init__(self, repo: Repo, verbose: bool) -> None: + super().__init__(repo, verbose) + + def create_start_tag(self) -> None: + # Specific functionality + pass + +# Usage +rs.add_helper(GitMasteryHelper) +rs.helper(GitMasteryHelper).create_start_tag() +``` + +## Function Design + +### Single Responsibility + +Each function should do **one thing**: + +**Bad:** +```python +def setup_and_verify(exercise): + # Setup + create_files() + init_repo() + # Verify + check_files() + check_commits() + return result +``` + +**Good:** +```python +def setup(exercise): + create_files() + init_repo() + +def verify(exercise): + check_files() + check_commits() + return result +``` + +### Small Functions + +Keep functions short (< 50 lines ideally): + +```python +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + validate_file_exists(exercise) + current_list = parse_shopping_list(exercise) + changes = detect_changes(current_list) + validate_changes(exercise, changes) + return success_output(exercise) +``` + +### Clear Parameters + +Limit parameters (< 5 preferred): + +**OK:** +```python +def run(command: List[str], verbose: bool) -> CommandResult: + ... +``` + +**Many params - consider config object:** +```python +# If you need many parameters: +@dataclass +class MergeConfig: + target_branch: str + ff: bool + message: Optional[str] + verbose: bool + +def merge(config: MergeConfig) -> None: + ... +``` + +## Variable Scope + +### Keep Scope Small + +```python +def verify(exercise): + # Only define when needed + repo_path = exercise.exercise_path + + if needs_shopping_list: + # Define close to usage + shopping_list_path = os.path.join(repo_path, "shopping-list.txt") + with open(shopping_list_path) as f: + content = f.read() +``` + +### Constants at Top + +```python +# Module-level constants +QUESTION_ONE = "Which file was added?" +QUESTION_TWO = "Which file was edited?" +REPOSITORY_NAME = "amateur-detective" + +def verify(exercise): + # Use constants + exercise.answers.add_validation(QUESTION_ONE, NotEmptyRule()) +``` + +## Context Managers + +### Use for Resource Management + +```python +# File handling +with open(filepath, "r") as f: + content = f.read() + +# Test setup/teardown +with loader.start() as (test, rs): + rs.files.create_or_update("file.txt", "content") + output = test.run() + # Automatic cleanup +``` + +### Custom Context Managers + +```python +from contextlib import contextmanager + +@contextmanager +def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]: + with loader.start() as (test, rs): + # Common setup + rs.files.create_or_update("shopping-list.txt", initial_content) + rs.git.add(["shopping-list.txt"]) + rs.git.commit(message="Initial commit") + rs.helper(GitMasteryHelper).create_start_tag() + + yield test, rs + # Automatic cleanup +``` + +## Data Structures + +### Choose Appropriate Types + +```python +# Sets for membership testing +ORIGINAL_SHOPPING_LIST = {"Milk", "Eggs", "Bread"} # O(1) lookup + +# Lists for ordered collections +commits = ["abc123", "def456", "ghi789"] + +# Dicts for key-value lookups +mock_answers = {"question1": "answer1", "question2": "answer2"} +``` + +### Use dataclasses + +```python +from dataclasses import dataclass + +@dataclass +class CommandResult: + result: CompletedProcess[str] + + def is_success(self) -> bool: + return self.result.returncode == 0 + + @property + def stdout(self) -> str: + return self.result.stdout.strip() +``` + +## Testing Best Practices + +### One Assertion Per Test + +**Good:** +```python +def test_no_answers(): + with loader.start(mock_answers={}) as (test, _): + output = test.run() + assert_output(output, GitAutograderStatus.UNSUCCESSFUL) + +def test_wrong_answer(): + with loader.start(mock_answers={QUESTION: "wrong"}) as (test, _): + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [HasExactValueRule.NOT_EXACT.format(question=QUESTION)] + ) +``` + +### Descriptive Test Names + +```python +# Good - describes what's being tested +def test_no_answers(): + ... + +def test_incomplete_answer(): + ... + +def test_wrong_question_one(): + ... + +def test_valid_answers(): + ... +``` + +### Use Test Fixtures + +```python +@contextmanager +def base_setup(): + """Common setup for all tests.""" + with loader.start() as (test, rs): + # Setup + yield test, rs + +def test_case_one(): + with base_setup() as (test, rs): + # Test-specific logic + ... +``` + +## Performance Considerations + +### Avoid Premature Optimization + +- Write clear code first +- Optimize only when needed +- Profile before optimizing + +### Use Comprehensions + +```python +# Good +current_items = {line[2:].strip() for line in content.splitlines() if line.startswith("- ")} + +# Instead of +current_items = set() +for line in content.splitlines(): + if line.startswith("- "): + current_items.add(line[2:].strip()) +``` + +## Code Smells to Avoid + +❌ **Long functions** (> 50 lines) +❌ **Deep nesting** (> 3 levels) +❌ **Magic numbers** (use named constants) +❌ **Mutable defaults** (`def func(items=[]):` ❌) +❌ **Global state** +❌ **Commented-out code** (delete it) +❌ **Unclear variable names** (`x`, `tmp`, `data`) + +## Security + +### Command Injection + +```python +# Good - list arguments +subprocess.run(["git", "commit", "-m", user_message], ...) + +# Bad - shell=True with user input +subprocess.run(f"git commit -m '{user_message}'", shell=True) # ❌ +``` + +### Path Traversal + +```python +# Validate paths +file_path = os.path.join(work_dir, filename) +if not file_path.startswith(work_dir): + raise ValueError("Invalid file path") +``` diff --git a/.claude/skills/coding-standards/documentation.md b/.claude/skills/coding-standards/documentation.md new file mode 100644 index 00000000..73292b7c --- /dev/null +++ b/.claude/skills/coding-standards/documentation.md @@ -0,0 +1,219 @@ +# documentation.md + +## Module Docstrings + +Every module should have a docstring at the top: + +```python +"""Wrapper for Git CLI commands.""" + +import os +from typing import List +``` + +**Guidelines:** +- One-line summary of module purpose +- Use triple double quotes `"""` +- End with period + +## Function Docstrings + +### Simple Functions +One-line docstring for obvious functions: + +```python +def tag(tag_name: str, verbose: bool) -> None: + """Tags the latest commit with the given tag_name.""" + run_command(["git", "tag", tag_name], verbose) + +def init(verbose: bool) -> None: + """Initializes the current folder as a Git repository.""" + run_command(["git", "init", "--initial-branch=main"], verbose) +``` + +### Complex Functions +Multi-line docstring with details: + +```python +def merge(target_branch: str, ff: bool, verbose: bool) -> None: + """Merges the current branch with the target one. + + Forcefully sets --no-edit to avoid requiring the student to enter the commit + message. + """ + if ff: + run_command(["git", "merge", target_branch, "--no-edit"], verbose) + else: + run_command(["git", "merge", target_branch, "--no-edit", "--no-ff"], verbose) +``` + +**Format:** +1. Summary line (< 80 chars) +2. Blank line +3. Additional details, notes, warnings + +### With Parameters + +For very complex functions, document parameters: + +```python +def run( + command: List[str], + verbose: bool, + env: Dict[str, str] = {}, + exit_on_error: bool = False, +) -> CommandResult: + """Runs the given command, logging the output if verbose is True. + + Args: + command: The command and arguments to run + verbose: Whether to print output to console + env: Additional environment variables + exit_on_error: Exit program on command failure + + Returns: + CommandResult with stdout, returncode, and is_success() + """ +``` + +**Note:** Parameters are already typed in signature, so docstring parameters are optional unless behavior is non-obvious. + +## Class Docstrings + +```python +class GitAutograderTestLoader: + """Test runner factory for exercise validation. + + Provides context manager for setting up isolated test environments + with repo-smith integration. + """ + + def __init__(self, exercise_name: str, grade_func: Callable) -> None: + """Initialize test loader. + + Args: + exercise_name: Name of the exercise being tested + grade_func: Verification function from verify.py + """ + self.exercise_name = exercise_name + self.grade_func = grade_func +``` + +## Comments + +### When to Use + +**Good reasons:** +- Explain **why** code exists +- Document workarounds or non-obvious choices +- Warn about edge cases +- Mark TODOs + +```python +# Force --initial-branch to ensure 'main' is used instead of system default +run_command(["git", "init", "--initial-branch=main"], verbose) + +# TODO(woojiahao): Maybe these should be built from a class like builder +def commit(message: str, verbose: bool) -> None: + ... + +# Avoid interactive prompts in automated exercises +run_command(["git", "merge", target_branch, "--no-edit"], verbose) +``` + +**Bad reasons:** +- Restating what code does +- Obvious statements +- Commented-out code (delete it instead) + +```python +# Bad - obvious from code +# Loop through files +for file in files: + add(file) + +# Bad - redundant +i = i + 1 # Increment i +``` + +### Comment Style + +```python +# Single line comment - space after # + +# Multi-line comments should be split +# across multiple lines, each starting +# with # and a space. + +# For long explanations, consider if a docstring +# or better variable names would be clearer. +``` + +### TODO Comments + +Format: `# TODO(username): Description` + +```python +# TODO(woojiahao): Reconsider if this should be inlined within repo-smith +def create_start_tag() -> None: + ... +``` + +## README Files + +Every exercise should have a README.md: + +```markdown +# Exercise Name + +Brief description of what the exercise teaches. + +## Objectives + +- Learn concept A +- Practice skill B + +## Instructions + +1. Step one +2. Step two + +## Verification + +Describe what will be checked. +``` + +## Constants Documentation + +Document error message constants with context: + +```python +# Error messages +NO_ADD = "There are no new grocery list items added to the shopping list." +NO_REMOVE = "There are no grocery list items removed from the shopping list." +SHOPPING_LIST_FILE_MISSING = "The shopping-list.txt file should not be deleted." +ADD_NOT_COMMITTED = ( + "New grocery list items added to shopping-list.txt are not committed." +) +``` + +## Documentation Best Practices + +1. **Keep it concise** - Users read code more than docs +2. **Update with code** - Outdated docs are worse than no docs +3. **Prefer clarity** - Good names > comments > docstrings +4. **Document the why** - The code shows the "what" and "how" +5. **Test examples** - Code in docstrings should work + +## What NOT to Document + +```python +# Don't document obvious type conversions +name = str(raw_name) # Bad: convert to string + +# Don't document standard library usage +files = os.listdir(path) # Bad: get list of files + +# Don't document clear variable assignments +username = get_github_username() # Bad: get username +``` diff --git a/.claude/skills/coding-standards/naming-conventions.md b/.claude/skills/coding-standards/naming-conventions.md new file mode 100644 index 00000000..30b15189 --- /dev/null +++ b/.claude/skills/coding-standards/naming-conventions.md @@ -0,0 +1,140 @@ +# naming-conventions.md + +## General Rules +- Names should be **descriptive** and **unambiguous** +- Avoid abbreviations except common ones (e.g., `repo`, `config`, `args`) +- Never use single-letter names except loop counters (`i`, `j`) or math formulas + +## Functions +**snake_case** - lowercase with underscores + +```python +# Good +def create_start_tag(verbose: bool): + ... + +def get_github_username(verbose: bool) -> str: + ... + +def has_fork(repository_name: str, owner_name: str) -> bool: + ... +``` + +**Naming patterns:** +- Actions: `create_`, `add_`, `remove_`, `delete_`, `update_` +- Queries: `get_`, `find_`, `has_`, `is_`, `check_` +- Boolean returns: `is_*`, `has_*`, `should_*`, `can_*` + +## Variables +**snake_case** - lowercase with underscores + +```python +repository_name = "my-repo" +user_commits = [] +is_fork = False +file_path = "/path/to/file" +``` + +## Constants +**UPPER_SNAKE_CASE** - all uppercase with underscores + +```python +QUESTION_ONE = "Which file was added?" +SHOPPING_LIST_FILE_MISSING = "The shopping-list.txt file should not be deleted." +ORIGINAL_SHOPPING_LIST = {"Milk", "Eggs", "Bread"} +REPOSITORY_NAME = "amateur-detective" +``` + +## Classes +**PascalCase** - capitalize each word, no separators + +```python +class GitAutograderTest: + ... + +class GitMasteryHelper: + ... + +class CommandResult: + ... +``` + +## Modules/Files +**snake_case.py** - lowercase with underscores + +``` +exercise_utils/ + git.py + github_cli.py + file.py + test.py +``` + +## Directories +**snake_case** - lowercase with underscores + +``` +exercises/ + amateur_detective/ + grocery_shopping/ + branch_compare/ + exercise_utils/ +``` + +**Exceptions:** +- Hands-on directories: hyphenated (e.g., `hands_on/`) +- Exercise names: may use hyphens in repo names but underscores in directories + +## Test Functions +Prefix with `test_`, describe what's being tested + +```python +def test_no_answers(): + ... + +def test_wrong_question_one(): + ... + +def test_valid_answers(): + ... + +def test_incomplete_answer(): + ... +``` + +## Private/Internal +Prefix with single underscore `_` + +```python +class GitAutograderTest: + def __init__(self): + self._internal_state = None + self.__rs: Optional[RepoSmith] = None # Name mangled + +def _internal_helper(): + """Not part of public API""" + ... +``` + +## Parameters +**Common parameter names:** +- `verbose: bool` - For output verbosity +- `exercise: GitAutograderExercise` - Exercise instance +- `repo: Repo` - Git repository +- `rs: RepoSmith` - RepoSmith instance +- `test: GitAutograderTest` - Test instance +- `output: GitAutograderOutput` - Verification output + +## Examples from Codebase + +```python +# Good naming throughout +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + repo_root = exercise.exercise_path + repo_folder = exercise.config.exercise_repo.repo_name + work_dir = os.path.join(repo_root, repo_folder) + + shopping_list_file_path = os.path.join(work_dir, "shopping-list.txt") + if not os.path.exists(shopping_list_file_path): + raise exercise.wrong_answer([SHOPPING_LIST_FILE_MISSING]) +``` diff --git a/.claude/skills/coding-standards/style-guide.md b/.claude/skills/coding-standards/style-guide.md new file mode 100644 index 00000000..2f529dc1 --- /dev/null +++ b/.claude/skills/coding-standards/style-guide.md @@ -0,0 +1,126 @@ +# style-guide.md + +## Code Formatting + +### Line Length +- **Maximum 88 characters** (Black default) +- Break long lines at logical points + +### Imports +**Order:** +1. Standard library imports +2. Third-party imports +3. Local application imports + +**Separate groups with blank lines:** +```python +import os +from typing import List, Optional + +from git_autograder import GitAutograderExercise +from repo_smith.repo_smith import RepoSmith + +from exercise_utils.git import commit, checkout +from .verify import verify +``` + +### String Quotes +- Use **double quotes** `"` for strings by default +- Single quotes `'` acceptable but be consistent within a file +- Use triple quotes `"""` for multiline strings and docstrings + +### Whitespace +- 2 blank lines between top-level functions/classes +- 1 blank line between methods in a class +- No trailing whitespace +- End files with single newline + +### Indentation +- **4 spaces** (no tabs) +- Continuation lines: align or use hanging indent + +```python +# Good - aligned +result = some_function( + arg1, arg2, + arg3, arg4 +) + +# Good - hanging indent +result = some_function( + arg1, + arg2, + arg3, +) +``` + +## Function/Method Style + +### Arguments +```python +# Short - single line +def commit(message: str, verbose: bool) -> None: + ... + +# Long - one per line +def fork_repo( + repository_name: str, + fork_name: str, + verbose: bool, + default_branch_only: bool = True, +) -> None: + ... +``` + +### Return Statements +- Early returns for error conditions +- Main logic with minimal nesting + +```python +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + if not exercise.repo: + raise exercise.invalid_state(["No repository found"]) + + # Main logic here + return exercise.to_output(["Success"], GitAutograderStatus.SUCCESSFUL) +``` + +## Comments +- Use sparingly - code should be self-documenting +- Explain **why**, not **what** +- TODO comments: `# TODO(username): Description` + +```python +# Good - explains why +if create_branch: + # Force --initial-branch to ensure 'main' is used + run_command(["git", "init", "--initial-branch=main"], verbose) + +# Bad - restates the code +# Loop through files +for file in files: + ... +``` + +## File Organization + +### Module Structure +1. Module docstring +2. Imports +3. Constants (UPPER_CASE) +4. Functions/Classes + +```python +"""Module description.""" + +import os +from typing import List + +from git_autograder import GitAutograderExercise + +ERROR_MESSAGE = "Something went wrong" + + +def main_function(): + ... +``` diff --git a/.claude/skills/coding-standards/type-hints.md b/.claude/skills/coding-standards/type-hints.md new file mode 100644 index 00000000..fb16917e --- /dev/null +++ b/.claude/skills/coding-standards/type-hints.md @@ -0,0 +1,213 @@ +# type-hints.md + +## General Policy +- **All function signatures** must have type hints +- **Public functions** must annotate parameters and return types +- **Internal/private functions** should have type hints when practical +- **Variables** should be typed when type isn't obvious + +## Function Signatures + +### Required +```python +def commit(message: str, verbose: bool) -> None: + """Creates a commit with the given message.""" + run_command(["git", "commit", "-m", message], verbose) + +def get_github_username(verbose: bool) -> str: + """Returns the currently authenticated Github user's username.""" + result = run(["gh", "api", "user", "-q", ".login"], verbose) + if result.is_success(): + return result.stdout.splitlines()[0] + return "" +``` + +### Optional Parameters +```python +def clone_repo_with_git( + repository_url: str, + verbose: bool, + name: Optional[str] = None +) -> None: + if name is not None: + run(["git", "clone", repository_url, name], verbose) + else: + run(["git", "clone", repository_url], verbose) +``` + +### Multiple Types +```python +from typing import Union +import pathlib + +def create_or_update_file( + filepath: str | pathlib.Path, # Python 3.10+ + contents: Optional[str] = None +) -> None: + ... + +# For Python 3.9 compatibility: +def create_or_update_file( + filepath: Union[str, pathlib.Path], + contents: Optional[str] = None +) -> None: + ... +``` + +## Collections + +### Lists +```python +from typing import List + +def add(files: List[str], verbose: bool) -> None: + run_command(["git", "add", *files], verbose) + +def get_commits() -> List[str]: + return ["abc123", "def456"] +``` + +### Dictionaries +```python +from typing import Dict + +def run( + command: List[str], + verbose: bool, + env: Dict[str, str] = {}, +) -> CommandResult: + ... +``` + +### Sets +```python +from typing import Set + +ORIGINAL_SHOPPING_LIST: Set[str] = {"Milk", "Eggs", "Bread"} +``` + +## Return Types + +### None +```python +def init(verbose: bool) -> None: + """Initializes the current folder as a Git repository.""" + run_command(["git", "init"], verbose) +``` + +### Optional +```python +from typing import Optional + +def run_command(command: List[str], verbose: bool) -> Optional[str]: + """Returns stdout or None on failure.""" + try: + result = subprocess.run(command, capture_output=True, text=True, check=True) + return result.stdout + except subprocess.CalledProcessError: + return None +``` + +### Complex Types +```python +from typing import Tuple, Iterator +from contextlib import contextmanager + +@contextmanager +def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]: + with loader.start() as (test, rs): + # setup + yield test, rs +``` + +## Class Attributes + +```python +from typing import Optional +from dataclasses import dataclass + +@dataclass +class CommandResult: + result: CompletedProcess[str] + + @property + def stdout(self) -> str: + return self.result.stdout.strip() + + @property + def returncode(self) -> int: + return self.result.returncode + +class GitAutograderTest: + def __init__(self, exercise_name: str) -> None: + self.exercise_name: str = exercise_name + self.__rs: Optional[RepoSmith] = None + + @property + def rs(self) -> RepoSmith: + assert self.__rs is not None + return self.__rs +``` + +## Forward References + +```python +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from git import Repo + +class GitMasteryHelper: + def __init__(self, repo: Repo, verbose: bool) -> None: + self.repo = repo +``` + +## Common Imports + +```python +from typing import ( + Any, + Callable, + Dict, + Iterator, + List, + Optional, + Set, + Tuple, + Union, +) +``` + +## When to Skip Type Hints + +### Lambda functions (optional) +```python +# OK without types +files = sorted(files, key=lambda f: f.name) +``` + +### Very short internal helpers +```python +# OK for simple one-liners +def _strip(s): + return s.strip() +``` + +### Tests with obvious types +```python +# Function signature should be typed +def test_no_answers(): + # Local variables in tests can skip types when obvious + expected = "error message" + output = test.run() +``` + +## Type Checking + +Use `mypy` for type checking: + +```bash +mypy exercise_utils/ +mypy amateur_detective/verify.py +``` From 7f5998102b63ba300331be799b97f865960a33c9 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Mon, 26 Jan 2026 20:46:07 +0800 Subject: [PATCH 04/18] Add skills for AI agent --- .../skills/coding-standards/best-practices.md | 172 +------ .../skills/coding-standards/documentation.md | 22 +- .../coding-standards/naming-conventions.md | 152 +----- .../skills/coding-standards/style-guide.md | 113 +---- .claude/skills/coding-standards/type-hints.md | 147 +----- .claude/skills/exercise-development/SKILL.md | 130 ++++++ .../exercise-development/common-patterns.md | 355 ++++++++++++++ .../exercise-development/hands-on-scripts.md | 373 +++++++++++++++ .../standard-exercises.md | 439 ++++++++++++++++++ .../exercise-development/testing-exercises.md | 293 ++++++++++++ .claude/skills/exercise-utils/SKILL.md | 108 +++++ .claude/skills/exercise-utils/cli-module.md | 19 + .claude/skills/exercise-utils/file-module.md | 15 + .claude/skills/exercise-utils/git-module.md | 15 + .../skills/exercise-utils/github-module.md | 18 + .../exercise-utils/gitmastery-module.md | 13 + .claude/skills/exercise-utils/test-module.md | 25 + .claude/skills/project-overview/SKILL.md | 313 +++++++++++++ .../skills/project-overview/architecture.md | 127 +++++ .../skills/project-overview/dependencies.md | 222 +++++++++ .../project-overview/quick-reference.md | 258 ++++++++++ .claude/skills/project-overview/workflows.md | 349 ++++++++++++++ .claude/skills/testing/SKILL.md | 151 ++++++ 23 files changed, 3290 insertions(+), 539 deletions(-) create mode 100644 .claude/skills/exercise-development/SKILL.md create mode 100644 .claude/skills/exercise-development/common-patterns.md create mode 100644 .claude/skills/exercise-development/hands-on-scripts.md create mode 100644 .claude/skills/exercise-development/standard-exercises.md create mode 100644 .claude/skills/exercise-development/testing-exercises.md create mode 100644 .claude/skills/exercise-utils/SKILL.md create mode 100644 .claude/skills/exercise-utils/cli-module.md create mode 100644 .claude/skills/exercise-utils/file-module.md create mode 100644 .claude/skills/exercise-utils/git-module.md create mode 100644 .claude/skills/exercise-utils/github-module.md create mode 100644 .claude/skills/exercise-utils/gitmastery-module.md create mode 100644 .claude/skills/exercise-utils/test-module.md create mode 100644 .claude/skills/project-overview/SKILL.md create mode 100644 .claude/skills/project-overview/architecture.md create mode 100644 .claude/skills/project-overview/dependencies.md create mode 100644 .claude/skills/project-overview/quick-reference.md create mode 100644 .claude/skills/project-overview/workflows.md create mode 100644 .claude/skills/testing/SKILL.md diff --git a/.claude/skills/coding-standards/best-practices.md b/.claude/skills/coding-standards/best-practices.md index 675d81e2..ac42644e 100644 --- a/.claude/skills/coding-standards/best-practices.md +++ b/.claude/skills/coding-standards/best-practices.md @@ -1,173 +1,35 @@ # best-practices.md -## DRY (Don't Repeat Yourself) - -### Extract Common Logic - -**Bad:** -```python -def fork_repo(repo_name: str, fork_name: str, verbose: bool): - result = subprocess.run(["gh", "repo", "fork", repo_name], capture_output=True) - if verbose: - print(result.stdout) - -def delete_repo(repo_name: str, verbose: bool): - result = subprocess.run(["gh", "repo", "delete", repo_name], capture_output=True) - if verbose: - print(result.stdout) -``` - -**Good:** -```python -def run(command: List[str], verbose: bool) -> CommandResult: - result = subprocess.run(command, capture_output=True) - if verbose: - print(result.stdout) - return CommandResult(result) - -def fork_repo(repo_name: str, fork_name: str, verbose: bool): - run(["gh", "repo", "fork", repo_name], verbose) - -def delete_repo(repo_name: str, verbose: bool): - run(["gh", "repo", "delete", repo_name], verbose) -``` +## DRY +Extract common logic into helpers. ## Error Handling +Use early returns and specific exceptions. -### Early Returns - -**Good:** ```python -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - if not os.path.exists(shopping_list_file_path): - raise exercise.wrong_answer([SHOPPING_LIST_FILE_MISSING]) - - if not added_items: - comments.append(NO_ADD) - - if not deleted_items: - comments.append(NO_REMOVE) - - if comments: - raise exercise.wrong_answer(comments) - +def verify(exercise): + if not os.path.exists(file_path): + raise exercise.wrong_answer([FILE_MISSING]) + if not items: + raise exercise.wrong_answer([NO_ITEMS]) return exercise.to_output(["Success"], GitAutograderStatus.SUCCESSFUL) ``` -### Specific Exceptions - -```python -try: - result = subprocess.run(command, check=True) -except FileNotFoundError: - # Handle missing command - pass -except PermissionError: - # Handle permission issues - pass -except subprocess.CalledProcessError as e: - # Handle command failure - pass -``` - -## Composition Over Inheritance - -### Prefer Helper Classes - -**Good:** -```python -class GitMasteryHelper(Helper): - def __init__(self, repo: Repo, verbose: bool) -> None: - super().__init__(repo, verbose) - - def create_start_tag(self) -> None: - # Specific functionality - pass - -# Usage -rs.add_helper(GitMasteryHelper) -rs.helper(GitMasteryHelper).create_start_tag() -``` - ## Function Design +- Single responsibility +- < 50 lines +- < 5 parameters +- Early returns -### Single Responsibility - -Each function should do **one thing**: - -**Bad:** -```python -def setup_and_verify(exercise): - # Setup - create_files() - init_repo() - # Verify - check_files() - check_commits() - return result -``` - -**Good:** -```python -def setup(exercise): - create_files() - init_repo() - -def verify(exercise): - check_files() - check_commits() - return result -``` - -### Small Functions - -Keep functions short (< 50 lines ideally): - -```python -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - validate_file_exists(exercise) - current_list = parse_shopping_list(exercise) - changes = detect_changes(current_list) - validate_changes(exercise, changes) - return success_output(exercise) -``` - -### Clear Parameters - -Limit parameters (< 5 preferred): - -**OK:** -```python -def run(command: List[str], verbose: bool) -> CommandResult: - ... -``` - -**Many params - consider config object:** +## Context Managers ```python -# If you need many parameters: -@dataclass -class MergeConfig: - target_branch: str - ff: bool - message: Optional[str] - verbose: bool +with open(filepath) as f: + content = f.read() -def merge(config: MergeConfig) -> None: +with loader.start() as (test, rs): + # test setup ... ``` - -## Variable Scope - -### Keep Scope Small - -```python -def verify(exercise): - # Only define when needed - repo_path = exercise.exercise_path - - if needs_shopping_list: - # Define close to usage - shopping_list_path = os.path.join(repo_path, "shopping-list.txt") with open(shopping_list_path) as f: content = f.read() ``` diff --git a/.claude/skills/coding-standards/documentation.md b/.claude/skills/coding-standards/documentation.md index 73292b7c..cc3ca08f 100644 --- a/.claude/skills/coding-standards/documentation.md +++ b/.claude/skills/coding-standards/documentation.md @@ -1,21 +1,25 @@ # documentation.md ## Module Docstrings +```python +"""One-line module summary.""" +``` -Every module should have a docstring at the top: +## Function Docstrings +```python +def tag(tag_name: str, verbose: bool) -> None: + """Tags the latest commit with the given tag_name.""" + ... +``` +## Comments ```python -"""Wrapper for Git CLI commands.""" +# Explain why, not what +run_command(["git", "init", "--initial-branch=main"], verbose) # Force main as default -import os -from typing import List +# TODO(username): Description of future work ``` -**Guidelines:** -- One-line summary of module purpose -- Use triple double quotes `"""` -- End with period - ## Function Docstrings ### Simple Functions diff --git a/.claude/skills/coding-standards/naming-conventions.md b/.claude/skills/coding-standards/naming-conventions.md index 30b15189..3f242234 100644 --- a/.claude/skills/coding-standards/naming-conventions.md +++ b/.claude/skills/coding-standards/naming-conventions.md @@ -1,140 +1,16 @@ # naming-conventions.md -## General Rules -- Names should be **descriptive** and **unambiguous** -- Avoid abbreviations except common ones (e.g., `repo`, `config`, `args`) -- Never use single-letter names except loop counters (`i`, `j`) or math formulas - -## Functions -**snake_case** - lowercase with underscores - -```python -# Good -def create_start_tag(verbose: bool): - ... - -def get_github_username(verbose: bool) -> str: - ... - -def has_fork(repository_name: str, owner_name: str) -> bool: - ... -``` - -**Naming patterns:** -- Actions: `create_`, `add_`, `remove_`, `delete_`, `update_` -- Queries: `get_`, `find_`, `has_`, `is_`, `check_` -- Boolean returns: `is_*`, `has_*`, `should_*`, `can_*` - -## Variables -**snake_case** - lowercase with underscores - -```python -repository_name = "my-repo" -user_commits = [] -is_fork = False -file_path = "/path/to/file" -``` - -## Constants -**UPPER_SNAKE_CASE** - all uppercase with underscores - -```python -QUESTION_ONE = "Which file was added?" -SHOPPING_LIST_FILE_MISSING = "The shopping-list.txt file should not be deleted." -ORIGINAL_SHOPPING_LIST = {"Milk", "Eggs", "Bread"} -REPOSITORY_NAME = "amateur-detective" -``` - -## Classes -**PascalCase** - capitalize each word, no separators - -```python -class GitAutograderTest: - ... - -class GitMasteryHelper: - ... - -class CommandResult: - ... -``` - -## Modules/Files -**snake_case.py** - lowercase with underscores - -``` -exercise_utils/ - git.py - github_cli.py - file.py - test.py -``` - -## Directories -**snake_case** - lowercase with underscores - -``` -exercises/ - amateur_detective/ - grocery_shopping/ - branch_compare/ - exercise_utils/ -``` - -**Exceptions:** -- Hands-on directories: hyphenated (e.g., `hands_on/`) -- Exercise names: may use hyphens in repo names but underscores in directories - -## Test Functions -Prefix with `test_`, describe what's being tested - -```python -def test_no_answers(): - ... - -def test_wrong_question_one(): - ... - -def test_valid_answers(): - ... - -def test_incomplete_answer(): - ... -``` - -## Private/Internal -Prefix with single underscore `_` - -```python -class GitAutograderTest: - def __init__(self): - self._internal_state = None - self.__rs: Optional[RepoSmith] = None # Name mangled - -def _internal_helper(): - """Not part of public API""" - ... -``` - -## Parameters -**Common parameter names:** -- `verbose: bool` - For output verbosity -- `exercise: GitAutograderExercise` - Exercise instance -- `repo: Repo` - Git repository -- `rs: RepoSmith` - RepoSmith instance -- `test: GitAutograderTest` - Test instance -- `output: GitAutograderOutput` - Verification output - -## Examples from Codebase - -```python -# Good naming throughout -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - repo_root = exercise.exercise_path - repo_folder = exercise.config.exercise_repo.repo_name - work_dir = os.path.join(repo_root, repo_folder) - - shopping_list_file_path = os.path.join(work_dir, "shopping-list.txt") - if not os.path.exists(shopping_list_file_path): - raise exercise.wrong_answer([SHOPPING_LIST_FILE_MISSING]) -``` +## Conventions +- **Functions/Variables**: `snake_case` +- **Constants**: `UPPER_SNAKE_CASE` +- **Classes**: `PascalCase` +- **Tests**: `test_description` +- **Private**: `_name` + +## Patterns +- Actions: `create_*`, `add_*`, `remove_*` +- Queries: `get_*`, `has_*`, `is_*` +- Boolean: `is_*`, `has_*`, `can_*` + +## Common Names +- `verbose`, `exercise`, `repo`, `rs`, `test`, `output` diff --git a/.claude/skills/coding-standards/style-guide.md b/.claude/skills/coding-standards/style-guide.md index 2f529dc1..762dc154 100644 --- a/.claude/skills/coding-standards/style-guide.md +++ b/.claude/skills/coding-standards/style-guide.md @@ -1,126 +1,45 @@ # style-guide.md -## Code Formatting +## Formatting +- **88 char line length** +- **4 spaces** indentation (no tabs) +- **Double quotes** for strings +- 2 blank lines between functions, 1 between methods -### Line Length -- **Maximum 88 characters** (Black default) -- Break long lines at logical points +## Imports +Order: stdlib → third-party → local (separate with blank lines) -### Imports -**Order:** -1. Standard library imports -2. Third-party imports -3. Local application imports - -**Separate groups with blank lines:** ```python import os -from typing import List, Optional +from typing import List from git_autograder import GitAutograderExercise -from repo_smith.repo_smith import RepoSmith - -from exercise_utils.git import commit, checkout -from .verify import verify -``` - -### String Quotes -- Use **double quotes** `"` for strings by default -- Single quotes `'` acceptable but be consistent within a file -- Use triple quotes `"""` for multiline strings and docstrings - -### Whitespace -- 2 blank lines between top-level functions/classes -- 1 blank line between methods in a class -- No trailing whitespace -- End files with single newline - -### Indentation -- **4 spaces** (no tabs) -- Continuation lines: align or use hanging indent - -```python -# Good - aligned -result = some_function( - arg1, arg2, - arg3, arg4 -) -# Good - hanging indent -result = some_function( - arg1, - arg2, - arg3, -) +from exercise_utils.git import commit ``` -## Function/Method Style - -### Arguments +## Functions ```python -# Short - single line +# Short def commit(message: str, verbose: bool) -> None: ... -# Long - one per line +# Long - one param per line def fork_repo( repository_name: str, fork_name: str, verbose: bool, - default_branch_only: bool = True, ) -> None: ... ``` -### Return Statements -- Early returns for error conditions -- Main logic with minimal nesting - -```python -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - if not exercise.repo: - raise exercise.invalid_state(["No repository found"]) - - # Main logic here - return exercise.to_output(["Success"], GitAutograderStatus.SUCCESSFUL) -``` - ## Comments -- Use sparingly - code should be self-documenting -- Explain **why**, not **what** -- TODO comments: `# TODO(username): Description` - -```python -# Good - explains why -if create_branch: - # Force --initial-branch to ensure 'main' is used - run_command(["git", "init", "--initial-branch=main"], verbose) +- Explain **why**, not what +- `# TODO(username): Description` +- Minimize - prefer clear code -# Bad - restates the code -# Loop through files -for file in files: - ... -``` - -## File Organization - -### Module Structure +## File Structure 1. Module docstring 2. Imports 3. Constants (UPPER_CASE) 4. Functions/Classes - -```python -"""Module description.""" - -import os -from typing import List - -from git_autograder import GitAutograderExercise - -ERROR_MESSAGE = "Something went wrong" - - -def main_function(): - ... -``` diff --git a/.claude/skills/coding-standards/type-hints.md b/.claude/skills/coding-standards/type-hints.md index fb16917e..394762f0 100644 --- a/.claude/skills/coding-standards/type-hints.md +++ b/.claude/skills/coding-standards/type-hints.md @@ -1,154 +1,21 @@ # type-hints.md -## General Policy -- **All function signatures** must have type hints -- **Public functions** must annotate parameters and return types -- **Internal/private functions** should have type hints when practical -- **Variables** should be typed when type isn't obvious +## Policy +- All function signatures need type hints +- Variables when type isn't obvious -## Function Signatures - -### Required +## Common Types ```python -def commit(message: str, verbose: bool) -> None: - """Creates a commit with the given message.""" - run_command(["git", "commit", "-m", message], verbose) - -def get_github_username(verbose: bool) -> str: - """Returns the currently authenticated Github user's username.""" - result = run(["gh", "api", "user", "-q", ".login"], verbose) - if result.is_success(): - return result.stdout.splitlines()[0] - return "" -``` - -### Optional Parameters -```python -def clone_repo_with_git( - repository_url: str, - verbose: bool, - name: Optional[str] = None -) -> None: - if name is not None: - run(["git", "clone", repository_url, name], verbose) - else: - run(["git", "clone", repository_url], verbose) -``` - -### Multiple Types -```python -from typing import Union -import pathlib - -def create_or_update_file( - filepath: str | pathlib.Path, # Python 3.10+ - contents: Optional[str] = None -) -> None: +def func(name: str, count: int, verbose: bool) -> None: ... -# For Python 3.9 compatibility: -def create_or_update_file( - filepath: Union[str, pathlib.Path], - contents: Optional[str] = None -) -> None: +def get_data(path: Optional[str] = None) -> List[str]: ... -``` -## Collections - -### Lists -```python -from typing import List - -def add(files: List[str], verbose: bool) -> None: - run_command(["git", "add", *files], verbose) - -def get_commits() -> List[str]: - return ["abc123", "def456"] -``` - -### Dictionaries -```python -from typing import Dict - -def run( - command: List[str], - verbose: bool, - env: Dict[str, str] = {}, -) -> CommandResult: +def process(items: List[str], config: Dict[str, str]) -> Tuple[int, bool]: ... ``` -### Sets -```python -from typing import Set - -ORIGINAL_SHOPPING_LIST: Set[str] = {"Milk", "Eggs", "Bread"} -``` - -## Return Types - -### None -```python -def init(verbose: bool) -> None: - """Initializes the current folder as a Git repository.""" - run_command(["git", "init"], verbose) -``` - -### Optional -```python -from typing import Optional - -def run_command(command: List[str], verbose: bool) -> Optional[str]: - """Returns stdout or None on failure.""" - try: - result = subprocess.run(command, capture_output=True, text=True, check=True) - return result.stdout - except subprocess.CalledProcessError: - return None -``` - -### Complex Types -```python -from typing import Tuple, Iterator -from contextlib import contextmanager - -@contextmanager -def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]: - with loader.start() as (test, rs): - # setup - yield test, rs -``` - -## Class Attributes - -```python -from typing import Optional -from dataclasses import dataclass - -@dataclass -class CommandResult: - result: CompletedProcess[str] - - @property - def stdout(self) -> str: - return self.result.stdout.strip() - - @property - def returncode(self) -> int: - return self.result.returncode - -class GitAutograderTest: - def __init__(self, exercise_name: str) -> None: - self.exercise_name: str = exercise_name - self.__rs: Optional[RepoSmith] = None - - @property - def rs(self) -> RepoSmith: - assert self.__rs is not None - return self.__rs -``` - ## Forward References ```python diff --git a/.claude/skills/exercise-development/SKILL.md b/.claude/skills/exercise-development/SKILL.md new file mode 100644 index 00000000..0b39ace5 --- /dev/null +++ b/.claude/skills/exercise-development/SKILL.md @@ -0,0 +1,130 @@ +--- +name: exercise-development +description: Guidelines for creating and modifying Git-Mastery exercises. Use when developing new exercises, understanding exercise types, or following exercise patterns. +--- + +# Exercise Development + +## Quick Guide + +### Two Types of Content + +**Standard Exercises**: Structured learning with validation (1-2 hours to create) +- Complete directory with download, verify, test, README +- Automated validation using git-autograder +- Comprehensive testing required + +**Hands-On Scripts**: Quick demonstrations without validation (5-10 min to create) +- Single Python file in `hands_on/` +- No validation, tests, or README +- Focus on demonstration + +## When to Use Each Type + +| Use Standard Exercise When... | Use Hands-On Script When... | +|------------------------------|----------------------------| +| ✓ Need to assess understanding | ✓ Just demonstrating a concept | +| ✓ Have specific success criteria | ✓ Exploring open-ended scenarios | +| ✓ Want automated grading | ✓ Showing command effects | +| ✓ Building structured curriculum | ✓ Quick experimentation | + +## Detailed Guides + +### Standard Exercises +📄 **[standard-exercises.md](standard-exercises.md)** - Complete guide to creating exercises with validation +- Proposal and approval process +- Scaffolding with `./new.sh` +- Implementing download.py, verify.py, test_verify.py, README.md +- Testing and troubleshooting + +### Hands-On Scripts +📄 **[hands-on-scripts.md](hands-on-scripts.md)** - Quick guide to creating demonstration scripts +- When and why to create +- Implementation steps +- Common patterns +- Examples + +### Common Patterns +📄 **[common-patterns.md](common-patterns.md)** - Reusable code patterns for exercises +- Repository setup patterns +- GitHub integration patterns +- Validation patterns +- File operation patterns + +### Testing Exercises +📄 **[testing-exercises.md](testing-exercises.md)** - How to test your exercises +- Running tests +- Manual testing +- Debugging test failures +- Pre-commit checklist + +## Quick Start + +### Create Standard Exercise +```bash +# 1. Get approval (create GitHub issue) +# 2. Generate scaffolding +./new.sh + +# 3. Implement required files: +# - download.py +# - verify.py +# - test_verify.py +# - README.md + +# 4. Test +./test.sh + +# 5. Quality check +ruff format . && ruff check . && mypy / +``` + +### Create Hands-On Script +```bash +# 1. Create file +touch hands_on/my_demo.py + +# 2. Implement download(verbose: bool) +# Add __requires_git__ and __requires_github__ + +# 3. Test manually +python hands_on/my_demo.py +``` + +## Exercise Conventions + +### Naming +- **Directories**: kebab-case (`branch-forward`, `conflict-mediator`) +- **Files**: snake_case (`download.py`, `test_verify.py`) +- **Constants**: UPPER_SNAKE_CASE (`QUESTION_ONE`, `REPOSITORY_NAME`) + +### Required Elements (Standard Exercises) +- `__init__.py` - Package marker +- `download.py` - With `__requires_git__`, `__requires_github__`, `download()` +- `verify.py` - With `verify()` function +- `test_verify.py` - With `REPOSITORY_NAME` and test functions +- `README.md` - With scenario, task, hints + +### Required Elements (Hands-On) +- `__requires_git__` and `__requires_github__` variables +- `download(verbose: bool)` function + +## Examples + +### Standard Exercise Examples +- **Simple**: `amateur_detective/` - Answer validation +- **Branching**: `branch_bender/` - Branch operations +- **Remote**: `remote_branch_pull/` - GitHub integration +- **Complex**: `conflict_mediator/` - Merge conflicts + +### Hands-On Examples +- `hands_on/add_files.py` - Staging demonstration +- `hands_on/branch_delete.py` - Branch deletion +- `hands_on/remote_branch_pull.py` - Remote operations + +## Related Skills + +- **[exercise-utils](../exercise-utils/SKILL.md)** - Utility functions reference +- **[coding-standards](../coding-standards/SKILL.md)** - Code style guidelines +- **[testing](../testing/SKILL.md)** - Testing strategies +- **[project-overview](../project-overview/SKILL.md)** - Repository structure diff --git a/.claude/skills/exercise-development/common-patterns.md b/.claude/skills/exercise-development/common-patterns.md new file mode 100644 index 00000000..14d5f03e --- /dev/null +++ b/.claude/skills/exercise-development/common-patterns.md @@ -0,0 +1,355 @@ +# common-patterns.md + +## Setup Examples +See [grocery_shopping/download.py](../../grocery_shopping/download.py) for local repository setup. + +See [fork_repo/download.py](../../fork_repo/download.py) for GitHub fork/clone pattern. + +## Validation Examples +See [amateur_detective/verify.py](../../amateur_detective/verify.py) for answer-based validation. + +See [grocery_shopping/verify.py](../../grocery_shopping/verify.py) for: +- File existence checks +- File content validation +- Commit validation + +See [branch_compare/verify.py](../../branch_compare/verify.py) for branch validation. + +See [tags_add/verify.py](../../tags_add/verify.py) for tag validation. + +## Key Utilities +- `exercise_utils.git` - Git commands +- `exercise_utils.github_cli` - GitHub operations +- `exercise_utils.file` - File operations +- `exercise_utils.gitmastery` - Start tag creation + +### With Tags + +```python +from exercise_utils.git import tag +from exercise_utils.gitmastery import create_start_tag + +def setup(verbose: bool = False): + init(verbose) + create_or_update_file("file.txt", "content") + add(["file.txt"], verbose) + commit("Initial commit", verbose) + + # Create Git-Mastery start tag + create_start_tag(verbose) + + # Create version tags + tag("v1.0.0", verbose) +``` + +## GitHub Integration Patterns + +### Fork and Clone + +```python +from exercise_utils.github_cli import ( + get_github_username, + has_fork, + fork_repo, + get_fork_name, + clone_repo_with_gh, +) + +def setup(verbose: bool = False): + username = get_github_username(verbose) + owner = "git-mastery" + repo_name = "exercise-base" + + # Check if fork exists + if not has_fork(repo_name, owner, username, verbose): + # Create fork + fork_repo( + f"{owner}/{repo_name}", + f"{username}-{repo_name}", + verbose, + default_branch_only=True + ) + + # Get fork name and clone + fork_name = get_fork_name(repo_name, owner, username, verbose) + clone_repo_with_gh(f"{username}/{fork_name}", verbose, name=repo_name) +``` + +### Create and Clone Repository + +```python +from exercise_utils.github_cli import create_repo, clone_repo_with_gh, delete_repo + +def setup(verbose: bool = False): + username = get_github_username(verbose) + repo_name = "my-exercise" + full_name = f"{username}/{repo_name}" + + # Clean up if exists + if has_repo(full_name, is_fork=False, verbose=verbose): + delete_repo(full_name, verbose) + + # Create new repository + create_repo(repo_name, verbose) + + # Clone it + clone_repo_with_gh(full_name, verbose, name=repo_name) + + # Navigate and setup + os.chdir(repo_name) + create_or_update_file("README.md", "# My Exercise") + add(["README.md"], verbose) + commit("Initial commit", verbose) +``` + +### Push to Remote + +```python +from exercise_utils.git import add_remote, push + +def setup(verbose: bool = False): + username = get_github_username(verbose) + repo_name = "my-repo" + + # Setup local repo + init(verbose) + create_or_update_file("README.md", "# Project") + add(["README.md"], verbose) + commit("Initial commit", verbose) + + # Add remote and push + remote_url = f"https://github.com/{username}/{repo_name}.git" + add_remote("origin", remote_url, verbose) + push("origin", "main", verbose) +``` + +## Validation Patterns + +### Answer-Based Validation + +```python +from git_autograder import GitAutograderExercise, GitAutograderOutput, GitAutograderStatus +from git_autograder.answers.rules import HasExactValueRule, NotEmptyRule + +QUESTION_ONE = "What is the answer?" +QUESTION_TWO = "What is another answer?" + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + # Validate answers + ( + exercise.answers + .add_validation(QUESTION_ONE, NotEmptyRule()) + .add_validation(QUESTION_ONE, HasExactValueRule("correct_answer")) + .add_validation(QUESTION_TWO, NotEmptyRule()) + .add_validation(QUESTION_TWO, HasExactValueRule("another_answer")) + .validate() + ) + + return exercise.to_output( + ["Congratulations! You got it right!"], + GitAutograderStatus.SUCCESSFUL + ) +``` + +### File Existence Check + +```python +import os + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + repo_path = exercise.exercise_path + repo_name = exercise.config.exercise_repo.repo_name + work_dir = os.path.join(repo_path, repo_name) + + file_path = os.path.join(work_dir, "required-file.txt") + if not os.path.exists(file_path): + raise exercise.wrong_answer(["The required-file.txt is missing"]) + + # File exists, continue validation + return exercise.to_output(["Success!"], GitAutograderStatus.SUCCESSFUL) +``` + +### File Content Validation + +```python +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + work_dir = os.path.join( + exercise.exercise_path, + exercise.config.exercise_repo.repo_name + ) + + file_path = os.path.join(work_dir, "data.txt") + with open(file_path, "r") as f: + content = f.read() + + # Parse content + items = {line.strip() for line in content.splitlines() if line.strip()} + + # Validate + if "required_item" not in items: + raise exercise.wrong_answer(["Missing required item"]) + + return exercise.to_output(["Success!"], GitAutograderStatus.SUCCESSFUL) +``` + +### Commit Validation + +```python +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + main_branch = exercise.repo.branches.branch("main") + + # Check commits exist + if not main_branch.user_commits: + raise exercise.wrong_answer(["No commits found"]) + + # Check specific file in last commit + latest_commit = main_branch.latest_user_commit.commit + if "file.txt" not in latest_commit.tree: + raise exercise.wrong_answer(["file.txt not in latest commit"]) + + # Read file from commit + file_blob = latest_commit.tree / "file.txt" + content = file_blob.data_stream.read().decode() + + if "expected_content" not in content: + raise exercise.wrong_answer(["File doesn't contain expected content"]) + + return exercise.to_output(["Success!"], GitAutograderStatus.SUCCESSFUL) +``` + +### Branch Validation + +```python +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + # Check branch exists + if not exercise.repo.branches.has_branch("feature"): + raise exercise.wrong_answer(["Branch 'feature' doesn't exist"]) + + feature_branch = exercise.repo.branches.branch("feature") + main_branch = exercise.repo.branches.branch("main") + + # Check commits on branch + if not feature_branch.user_commits: + raise exercise.wrong_answer(["No commits on feature branch"]) + + # Check if merged + feature_commits = {c.commit.hexsha for c in feature_branch.user_commits} + main_commits = {c.commit.hexsha for c in main_branch.user_commits} + + if not feature_commits.issubset(main_commits): + raise exercise.wrong_answer(["Feature branch not merged into main"]) + + return exercise.to_output(["Success!"], GitAutograderStatus.SUCCESSFUL) +``` + +### Tag Validation + +```python +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + # Check tag exists + tags = {tag.name for tag in exercise.repo.repo.tags} + if "v1.0.0" not in tags: + raise exercise.wrong_answer(["Tag 'v1.0.0' not found"]) + + # Get tag + tag = exercise.repo.repo.tags["v1.0.0"] + + # Check tag points to correct commit + expected_commit = exercise.repo.branches.branch("main").latest_user_commit + if tag.commit.hexsha != expected_commit.commit.hexsha: + raise exercise.wrong_answer(["Tag on wrong commit"]) + + return exercise.to_output(["Success!"], GitAutograderStatus.SUCCESSFUL) +``` + +## File Operation Patterns + +### Multi-line Content + +```python +from exercise_utils.file import create_or_update_file + +def setup(verbose: bool = False): + create_or_update_file( + "shopping-list.txt", + """ + - Milk + - Eggs + - Bread + - Apples + """ + ) + # Content is auto-dedented +``` + +### Nested Directories + +```python +def setup(verbose: bool = False): + # Directories created automatically + create_or_update_file("src/utils/helper.py", "def helper(): pass") + create_or_update_file("tests/test_helper.py", "def test(): pass") +``` + +### Append Pattern + +```python +from exercise_utils.file import append_to_file + +def setup(verbose: bool = False): + create_or_update_file("log.txt", "Initial entry\n") + append_to_file("log.txt", "Second entry\n") + append_to_file("log.txt", "Third entry\n") +``` + +## Error Handling Patterns + +### Multiple Error Messages + +```python +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + comments = [] + + if not condition_1: + comments.append("Error 1") + + if not condition_2: + comments.append("Error 2") + + if comments: + raise exercise.wrong_answer(comments) + + return exercise.to_output(["Success!"], GitAutograderStatus.SUCCESSFUL) +``` + +### Early Returns + +```python +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + # Check critical requirement first + if not os.path.exists(required_file): + raise exercise.wrong_answer(["Critical file missing"]) + + # Continue with other checks + if not condition: + raise exercise.wrong_answer(["Condition not met"]) + + return exercise.to_output(["Success!"], GitAutograderStatus.SUCCESSFUL) +``` + +## Constants Pattern + +```python +# Define at module level +QUESTION_ONE = "What is the answer?" +ERROR_FILE_MISSING = "The required file is missing" +ERROR_NOT_COMMITTED = "Changes not committed" +SUCCESS_MESSAGE = "Great work!" + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + # Use constants + if not file_exists: + raise exercise.wrong_answer([ERROR_FILE_MISSING]) + + return exercise.to_output([SUCCESS_MESSAGE], GitAutograderStatus.SUCCESSFUL) +``` diff --git a/.claude/skills/exercise-development/hands-on-scripts.md b/.claude/skills/exercise-development/hands-on-scripts.md new file mode 100644 index 00000000..d4896ba6 --- /dev/null +++ b/.claude/skills/exercise-development/hands-on-scripts.md @@ -0,0 +1,373 @@ +# Creating Hands-On Scripts + +Hands-on scripts are simple demonstration scripts that show Git operations without validation or testing. + +## When to Create Hands-On Scripts + +Use hands-on scripts when: +- **Demonstrating** how a Git command works +- **Showing effects** of operations (e.g., what happens when you delete a branch) +- **Exploratory learning** without right/wrong answers +- **Quick demonstrations** that don't need validation +- **Teaching through observation** rather than assessment + +## Implementation Steps + +### 1. Create Script File + +Simply create a new `.py` file in the `hands_on/` directory: + +```bash +# No scaffolding needed - just create the file +touch hands_on/my_demo.py +``` + +### 2. Implement Required Variables + +```python +# At top of file +__requires_git__ = True # Set True if uses Git commands +__requires_github__ = False # Set True if uses GitHub CLI +``` + +### 3. Implement download() Function + +This is the only required function - it performs the demonstration: + +```python +import os +from exercise_utils.git import init, add, commit, checkout +from exercise_utils.file import create_or_update_file + +__requires_git__ = True +__requires_github__ = False + +def download(verbose: bool): + """Demonstrate creating and switching branches.""" + # Setup initial repository + os.makedirs("demo-repo") + os.chdir("demo-repo") + + init(verbose) + create_or_update_file("README.md", "# Demo Project\n") + add(["README.md"], verbose) + commit("Initial commit", verbose) + + # Demonstrate the concept + checkout("feature-branch", create_branch=True, verbose=verbose) + create_or_update_file("feature.txt", "New feature\n") + add(["feature.txt"], verbose) + commit("Add feature", verbose) + + # Show the result + if verbose: + print("\n✓ Created feature-branch with new commit") + print("Run 'git log --oneline --all --graph' to see the result") +``` + +### 4. Focus on Demonstration + +Key principles: +- **Use verbose output** to show what's happening +- **Add print statements** to guide understanding +- **Leave repository** in an interesting state for exploration +- **Suggest commands** for users to run next + +## Common Patterns + +### Pattern: Simple Git Operation Demo + +```python +import os +from exercise_utils.git import init, checkout +from exercise_utils.file import create_or_update_file + +__requires_git__ = True +__requires_github__ = False + +def download(verbose: bool): + """Show how to rename a branch.""" + os.makedirs("rename-demo") + os.chdir("rename-demo") + + init(verbose) + + if verbose: + print("\n✓ Repository initialized") + print("Try running: git branch -m main trunk") + print("Then: git branch") +``` + +### Pattern: Branch Operations Demo + +```python +import os +from exercise_utils.git import init, checkout, add, commit +from exercise_utils.file import create_or_update_file + +__requires_git__ = True +__requires_github__ = False + +def download(verbose: bool): + """Demonstrate branch creation and switching.""" + os.makedirs("branch-demo") + os.chdir("branch-demo") + + # Setup + init(verbose) + create_or_update_file("main.txt", "Main branch file\n") + add(["main.txt"], verbose) + commit("Initial on main", verbose) + + # Create feature branch + checkout("feature", create_branch=True, verbose=verbose) + create_or_update_file("feature.txt", "Feature file\n") + add(["feature.txt"], verbose) + commit("Add feature", verbose) + + # Back to main + checkout("main", create_branch=False, verbose=verbose) + + if verbose: + print("\n✓ Two branches created: main and feature") + print("Current branch: main") + print("Try: git log --oneline --all --graph") +``` + +### Pattern: GitHub Operations Demo + +```python +import os +from exercise_utils.git import clone_repo_with_git, checkout, add, commit +from exercise_utils.github_cli import get_github_username, fork_repo, has_repo, delete_repo +from exercise_utils.file import append_to_file + +__requires_git__ = True +__requires_github__ = True + +def download(verbose: bool): + """Demonstrate forking and cloning a repository.""" + TARGET_REPO = "git-mastery/sample-repo" + FORK_NAME = "my-forked-repo" + + username = get_github_username(verbose) + full_repo_name = f"{username}/{FORK_NAME}" + + # Clean up if exists + if has_repo(full_repo_name, True, verbose): + if verbose: + print(f"Deleting existing fork: {full_repo_name}") + delete_repo(full_repo_name, verbose) + + # Fork repository + if verbose: + print(f"Forking {TARGET_REPO}...") + fork_repo(TARGET_REPO, FORK_NAME, verbose, True) + + # Clone locally + if verbose: + print(f"Cloning to local-fork directory...") + clone_repo_with_git( + f"https://github.com/{username}/{FORK_NAME}", + verbose, + "local-fork" + ) + + os.chdir("local-fork") + + # Make a change + checkout("demo-branch", create_branch=True, verbose=verbose) + append_to_file("README.md", "\n## Demo Addition\n") + add(["README.md"], verbose) + commit("Add demo section", verbose) + + if verbose: + print(f"\n✓ Forked {TARGET_REPO} to {username}/{FORK_NAME}") + print("✓ Cloned to local-fork directory") + print("✓ Created demo-branch with changes") + print("\nExplore the repository:") + print(" git log --oneline") + print(" git remote -v") +``` + +### Pattern: Merge Operations Demo + +```python +import os +from exercise_utils.git import init, checkout, add, commit, merge +from exercise_utils.file import create_or_update_file + +__requires_git__ = True +__requires_github__ = False + +def download(verbose: bool): + """Demonstrate merging branches.""" + os.makedirs("merge-demo") + os.chdir("merge-demo") + + # Setup main branch + init(verbose) + create_or_update_file("file.txt", "Line 1\n") + add(["file.txt"], verbose) + commit("Initial commit", verbose) + + # Create and work on feature branch + checkout("feature", create_branch=True, verbose=verbose) + create_or_update_file("file.txt", "Line 1\nLine 2 from feature\n") + add(["file.txt"], verbose) + commit("Add line 2", verbose) + + # Back to main and merge + checkout("main", create_branch=False, verbose=verbose) + merge("feature", ff=False, verbose=verbose) + + if verbose: + print("\n✓ Merged feature branch into main") + print("View the merge: git log --oneline --graph") +``` + +## Best Practices + +### Use Helpful Print Statements + +```python +if verbose: + print("\n=== Demo Complete ===") + print("✓ Created repository with 2 branches") + print("\nNext steps:") + print(" 1. cd demo-repo") + print(" 2. git log --oneline --all --graph") + print(" 3. git branch -v") +``` + +### Leave Repository in Explorable State + +```python +# Good - leaves interesting state +checkout("feature", create_branch=True, verbose=verbose) +commit("Feature work", verbose) +checkout("main", create_branch=False, verbose=verbose) +# Now user can explore both branches + +# Less useful - ends in empty state +init(verbose) +# Not much to explore +``` + +### Guide Without Prescribing + +```python +if verbose: + print("\nTry these commands to explore:") + print(" git status") + print(" git log") + print(" git branch") + # Suggest, don't require +``` + +## Hands-On vs Standard Exercise + +| Aspect | Hands-On Script | Standard Exercise | +|--------|----------------|-------------------| +| **Purpose** | Demonstrate & explore | Teach & validate | +| **Structure** | Single `.py` file | Complete directory | +| **Files** | Just the script | download, verify, test, README | +| **Validation** | None | Required | +| **Testing** | Manual only | Automated with pytest | +| **Instructions** | Optional comments | Required README.md | +| **Success Criteria** | None | Defined rules | +| **User Action** | Run and observe | Complete and verify | +| **Creation Time** | 5-10 minutes | 1-2 hours | +| **Use Case** | Demos, exploration | Structured learning | + +## Testing Hands-On Scripts + +No formal tests required, but manually verify: + +```bash +# 1. Run the script +python hands_on/my_demo.py + +# 2. Check created state +cd demo-repo # or whatever directory it creates +git status +git log --oneline --all --graph +git branch + +# 3. Verify it demonstrates the concept clearly +``` + +## Examples + +### Minimal Example +```python +import os +from exercise_utils.git import init + +__requires_git__ = True +__requires_github__ = False + +def download(verbose: bool): + """Show git init.""" + os.makedirs("init-demo") + os.chdir("init-demo") + init(verbose) + + if verbose: + print("\n✓ Git repository initialized") + print("Run 'git status' to see") +``` + +### Complete Example with GitHub +```python +import os +from exercise_utils.git import clone_repo_with_git, checkout, add, commit, push +from exercise_utils.github_cli import get_github_username, fork_repo +from exercise_utils.file import append_to_file + +__requires_git__ = True +__requires_github__ = True + +TARGET_REPO = "git-mastery/sample-repo" +FORK_NAME = "demo-fork" + +def download(verbose: bool): + """Complete GitHub workflow demonstration.""" + username = get_github_username(verbose) + + # Fork and clone + fork_repo(TARGET_REPO, FORK_NAME, verbose, True) + clone_repo_with_git( + f"https://github.com/{username}/{FORK_NAME}", + verbose, + "demo-repo" + ) + os.chdir("demo-repo") + + # Make changes + checkout("demo-feature", create_branch=True, verbose=verbose) + append_to_file("README.md", "\n## Demo Feature\n") + add(["README.md"], verbose) + commit("Add demo feature", verbose) + push("origin", "demo-feature", verbose) + + if verbose: + print("\n=== Demo Complete ===") + print(f"✓ Forked to {username}/{FORK_NAME}") + print("✓ Cloned to demo-repo") + print("✓ Created demo-feature branch") + print("✓ Pushed changes to GitHub") + print("\nView on GitHub:") + print(f" https://github.com/{username}/{FORK_NAME}") +``` + +## Quick Checklist + +Before committing a hands-on script: +- ✓ Has `__requires_git__` and `__requires_github__` +- ✓ Has `download(verbose: bool)` function +- ✓ Uses utility functions (not raw subprocess) +- ✓ Includes helpful verbose output +- ✓ Creates interesting state to explore +- ✓ Tested manually +- ✓ Follows naming conventions (snake_case) diff --git a/.claude/skills/exercise-development/standard-exercises.md b/.claude/skills/exercise-development/standard-exercises.md new file mode 100644 index 00000000..3fa11f66 --- /dev/null +++ b/.claude/skills/exercise-development/standard-exercises.md @@ -0,0 +1,439 @@ +# Creating Standard Exercises + +This guide covers the complete process of creating a standard exercise with validation and testing. + +## Prerequisites + +Before implementing, you must: + +1. **Create an exercise discussion issue** + - Use GitHub issue template: "exercise discussion" + - Include: exercise name, learning objectives, difficulty level, Git concepts covered + - Tag with: `exercise discussion`, `help wanted` + +2. **Obtain approval** + - Wait for maintainer review and approval + - Address any feedback on scope or approach + +3. **Request remote repository** (if needed) + - Use GitHub issue template: "request exercise repository" + - Some exercises require pre-existing GitHub repositories + +## Step 1: Scaffolding + +Use the `new.sh` script to generate exercise structure: + +```bash +./new.sh +``` + +**Prompts**: +1. **Exercise name**: Use kebab-case (e.g., `branch-forward`, `merge-squash`) +2. **Tags**: Space-separated (e.g., `branch merge intermediate`) +3. **Configuration**: Exercise-specific settings + +**Generated files**: +``` +/ +├── __init__.py +├── download.py +├── verify.py +├── test_verify.py +├── README.md +└── res/ +``` + +## Step 2: Implement download.py + +**Purpose**: Set up the initial Git repository state for the exercise. + +### Required Variables +```python +__requires_git__ = True # Always required +__requires_github__ = False # Set True if exercise uses GitHub +``` + +### Required Function +```python +def download(verbose: bool): + """Setup the exercise repository.""" + # Implementation here +``` + +### Pattern: Local Repository Only +```python +import os +from exercise_utils.git import init, add, commit +from exercise_utils.gitmastery import create_start_tag +from exercise_utils.file import create_or_update_file + +__requires_git__ = True +__requires_github__ = False + +def setup(verbose: bool = False): + # Create initial files + create_or_update_file("file1.txt", "Initial content") + create_or_update_file("file2.txt", "More content") + + # Setup Git repository + add(["file1.txt", "file2.txt"], verbose) + commit("Initial commit", verbose) + + # Make changes for exercise + create_or_update_file("file3.txt", "New file") + + # Create start tag (always last step) + create_start_tag(verbose) +``` + +### Pattern: With GitHub Integration +```python +import os +from exercise_utils.git import clone_repo_with_git, checkout, add, commit, push +from exercise_utils.github_cli import get_github_username, fork_repo, delete_repo, has_repo +from exercise_utils.file import append_to_file + +__requires_git__ = True +__requires_github__ = True + +TARGET_REPO = "git-mastery/sample-repo" +FORK_NAME = "gitmastery-sample-repo" +LOCAL_DIR = "sample-repo" + +def download(verbose: bool): + username = get_github_username(verbose) + full_repo_name = f"{username}/{FORK_NAME}" + + # Clean up existing fork if present + if has_repo(full_repo_name, True, verbose): + delete_repo(full_repo_name, verbose) + + # Create fork + fork_repo(TARGET_REPO, FORK_NAME, verbose, False) + + # Clone locally + clone_repo_with_git(f"https://github.com/{full_repo_name}", verbose, LOCAL_DIR) + os.chdir(LOCAL_DIR) + + # Make changes + checkout("feature-branch", True, verbose) + append_to_file("README.md", "\nNew content") + add(["README.md"], verbose) + commit("Update README", verbose) + push("origin", "feature-branch", verbose) +``` + +### Best Practices +- Use utility functions from `exercise_utils/` - never raw subprocess calls +- Always call `create_start_tag()` as the final step +- Keep setup simple and focused on learning objectives +- Use verbose parameter for all utility calls +- Set appropriate `__requires_*__` flags + +## Step 3: Write README.md + +**Purpose**: Student-facing instructions for the exercise. + +### Required Sections + +1. **Title**: Exercise name (h1) +2. **Scenario/Context**: Engaging story that motivates the exercise +3. **Task**: Clear, actionable objectives +4. **Hints**: Progressive disclosure of help + +### Template +```markdown +# exercise-name + +## Scenario +[Engaging story or context that motivates the exercise. +Make it relatable and interesting.] + +## Task +[Clear description of what students need to accomplish] + +Use `git ` to [specific action]. + +[Additional requirements or constraints] + +Update your answers in `answers.txt`. + +## Hints + +
+Hint 1 + +[First level of help - general guidance] + +
+ +
+Hint 2 + +[Second level - more specific direction] + +
+ +
+Hint 3 + +[Third level - nearly direct answer] + +
+``` + +### Best Practices +- Use engaging scenarios that make Git concepts relatable +- Be specific about expected outcomes +- Provide 3-5 progressive hints +- Mention specific Git commands when appropriate +- Keep instructions concise and scannable + +## Step 4: Implement verify.py + +**Purpose**: Validate student's solution using composable rules. + +### Required Imports +```python +from git_autograder import ( + GitAutograderExercise, + GitAutograderOutput, + GitAutograderStatus, +) +from git_autograder.answers.rules import HasExactValueRule, NotEmptyRule +# Import other rules as needed +``` + +### Required Function +```python +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + """Verify the student's solution.""" + # Validation logic here +``` + +### Pattern: Answer-Based Validation +For exercises where students provide answers in `answers.txt`: + +```python +QUESTION_ONE = "Which file was modified?" +QUESTION_TWO = "Which commit was the change made in?" + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + ( + exercise.answers.add_validation(QUESTION_ONE, NotEmptyRule()) + .add_validation(QUESTION_ONE, HasExactValueRule("expected_file.txt")) + .add_validation(QUESTION_TWO, NotEmptyRule()) + .add_validation(QUESTION_TWO, HasExactValueRule("abc123")) + .validate() + ) + + return exercise.to_output( + ["Congratulations! You solved the exercise!"], + GitAutograderStatus.SUCCESSFUL, + ) +``` + +### Pattern: Repository State Validation +For exercises checking Git repository state: + +```python +from git_autograder.repo.rules import HasBranchRule, HasCommitWithMessageRule + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + ( + exercise.repo.add_validation(HasBranchRule("feature-branch")) + .add_validation(HasCommitWithMessageRule("Add new feature")) + .validate() + ) + + return exercise.to_output( + ["Great work! The repository is in the correct state."], + GitAutograderStatus.SUCCESSFUL, + ) +``` + +### Validation Rule Categories +- **Answer rules**: `NotEmptyRule`, `HasExactValueRule`, `MatchesPatternRule` +- **Repository rules**: `HasBranchRule`, `HasCommitRule`, `HasRemoteRule` +- **Commit rules**: `HasCommitWithMessageRule`, `CommitCountRule` +- **File rules**: `FileExistsRule`, `FileContentsRule` + +### Best Practices +- Chain validations using fluent API +- Provide clear, actionable success messages +- Use status codes appropriately (SUCCESSFUL, UNSUCCESSFUL) +- Test edge cases in your validation logic +- Keep validation focused on learning objectives + +## Step 5: Write test_verify.py + +**Purpose**: Test the verification logic with various scenarios. + +### Required Imports +```python +from exercise_utils.test import GitAutograderTestLoader, assert_output +from git_autograder import GitAutograderStatus +from git_autograder.answers.rules import HasExactValueRule, NotEmptyRule + +from .verify import QUESTION_ONE, QUESTION_TWO, verify +``` + +### Required Setup +```python +REPOSITORY_NAME = "exercise-name" # Must match exercise directory name + +loader = GitAutograderTestLoader(REPOSITORY_NAME, verify) +``` + +### Required Test Scenarios + +#### 1. No Answers +```python +def test_no_answers(): + """Test when student provides no answers.""" + with loader.start(mock_answers={QUESTION_ONE: "", QUESTION_TWO: ""}) as (test, _): + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [ + NotEmptyRule.EMPTY.format(question=QUESTION_ONE), + NotEmptyRule.EMPTY.format(question=QUESTION_TWO), + ], + ) +``` + +#### 2. Partial Answers +```python +def test_partial_answers(): + """Test when some answers missing.""" + with loader.start( + mock_answers={QUESTION_ONE: "correct", QUESTION_TWO: ""} + ) as (test, _): + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [NotEmptyRule.EMPTY.format(question=QUESTION_TWO)], + ) +``` + +#### 3. Wrong Answers +```python +def test_wrong_answers(): + """Test when answers are incorrect.""" + with loader.start( + mock_answers={QUESTION_ONE: "wrong", QUESTION_TWO: "also_wrong"} + ) as (test, _): + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [ + HasExactValueRule.NOT_EXACT.format(question=QUESTION_ONE), + HasExactValueRule.NOT_EXACT.format(question=QUESTION_TWO), + ], + ) +``` + +#### 4. Mixed Answers +```python +def test_mixed_answers(): + """Test mix of correct and incorrect answers.""" + with loader.start( + mock_answers={QUESTION_ONE: "correct", QUESTION_TWO: "wrong"} + ) as (test, _): + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [HasExactValueRule.NOT_EXACT.format(question=QUESTION_TWO)], + ) +``` + +#### 5. Correct Answers +```python +def test_correct_answers(): + """Test successful completion with all correct answers.""" + with loader.start( + mock_answers={QUESTION_ONE: "correct", QUESTION_TWO: "also_correct"} + ) as (test, _): + output = test.run() + assert_output( + output, + GitAutograderStatus.SUCCESSFUL, + ["Congratulations! You solved the exercise!"], + ) +``` + +## Step 6: Add Resources (Optional) + +**Location**: `res/` subdirectory within exercise + +**Common resources**: +- Sample configuration files +- Pre-populated data files +- Scripts that students interact with +- Images or diagrams for README + +**Accessing resources**: +```python +import os +from pathlib import Path + +# In download.py +resource_dir = Path(__file__).parent / "res" +sample_file = resource_dir / "sample.txt" + +# Copy to exercise directory +import shutil +shutil.copy(sample_file, ".") +``` + +## Testing Your Exercise + +### Run Tests +```bash +./test.sh +``` + +### Run Specific Test +```bash +pytest /test_verify.py::test_correct_answers -s -vv +``` + +### Manual Testing +1. Run `download.py` to set up exercise +2. Follow instructions in `README.md` +3. Run `verify.py` to check solution +4. Verify success/failure messages are clear + +## Troubleshooting + +### Tests Failing +1. Run with verbose: `pytest /test_verify.py -s -vv` +2. Check mock answers match validation rules +3. Verify `REPOSITORY_NAME` matches directory name +4. Ensure imports are correct + +### Download Script Errors +1. Check `__requires_git__` and `__requires_github__` flags +2. Verify Git/GitHub CLI is available +3. Test with verbose mode: `download(verbose=True)` +4. Check file paths are relative to exercise directory + +### Validation Not Working +1. Verify validation rules match test expectations +2. Check that `validate()` is called on chain +3. Ensure correct status returned +4. Test with actual exercise setup + +## Pre-Submission Checklist + +- ✓ Exercise discussion approved +- ✓ All tests passing: `./test.sh ` +- ✓ Download script tested +- ✓ README clear and complete +- ✓ Code follows conventions +- ✓ No unused imports or files +- ✓ Quality checks pass: `ruff format . && ruff check . && mypy /` diff --git a/.claude/skills/exercise-development/testing-exercises.md b/.claude/skills/exercise-development/testing-exercises.md new file mode 100644 index 00000000..3029740b --- /dev/null +++ b/.claude/skills/exercise-development/testing-exercises.md @@ -0,0 +1,293 @@ +# testing-exercises.md + +## Examples +See [amateur_detective/test_verify.py](../../amateur_detective/test_verify.py) - Answer-based exercise tests. + +See [grocery_shopping/test_verify.py](../../grocery_shopping/test_verify.py) - Repository state tests with base fixture. + +See [view_commits/test_verify.py](../../view_commits/test_verify.py) - Multiple question exercise. + +## Minimum Tests +1. Success case +2. No action +3. Wrong approach +4. Each error message + +## Running +```bash +pytest exercise_name/ +``` + +## Answer-Based Exercise Tests + +```python +from git_autograder.answers.rules import HasExactValueRule, NotEmptyRule +from .verify import QUESTION_ONE, QUESTION_TWO + + +def test_no_answers(): + with loader.start(mock_answers={QUESTION_ONE: "", QUESTION_TWO: ""}) as (test, _): + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [ + NotEmptyRule.EMPTY.format(question=QUESTION_ONE), + NotEmptyRule.EMPTY.format(question=QUESTION_TWO), + ], + ) + + +def test_wrong_answers(): + with loader.start( + mock_answers={QUESTION_ONE: "wrong", QUESTION_TWO: "wrong"} + ) as (test, _): + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [ + HasExactValueRule.NOT_EXACT.format(question=QUESTION_ONE), + HasExactValueRule.NOT_EXACT.format(question=QUESTION_TWO), + ], + ) + + +def test_correct_answers(): + with loader.start( + mock_answers={QUESTION_ONE: "correct1", QUESTION_TWO: "correct2"} + ) as (test, _): + output = test.run() + assert_output(output, GitAutograderStatus.SUCCESSFUL) +``` + +## Repository State Exercise Tests + +### With Base Setup Fixture + +```python +from contextlib import contextmanager +from typing import Iterator, Tuple +from exercise_utils.test import GitAutograderTest, GitMasteryHelper +from repo_smith.repo_smith import RepoSmith + + +@contextmanager +def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]: + """Common setup for all tests.""" + with loader.start() as (test, rs): + # Initial repository state + rs.files.create_or_update("file.txt", "initial") + rs.git.add(["file.txt"]) + rs.git.commit(message="Initial commit") + rs.helper(GitMasteryHelper).create_start_tag() + + yield test, rs + + +def test_no_changes(): + with base_setup() as (test, rs): + output = test.run() + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [NO_CHANGES]) + + +def test_success(): + with base_setup() as (test, rs): + rs.files.append("file.txt", "new content") + rs.git.add(["file.txt"]) + rs.git.commit(message="Add content") + + output = test.run() + assert_output(output, GitAutograderStatus.SUCCESSFUL) +``` + +## Testing File Operations + +```python +def test_file_missing(): + with base_setup() as (test, rs): + rs.files.delete("required-file.txt") + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + ["File missing error"] + ) + + +def test_file_content_wrong(): + with base_setup() as (test, rs): + rs.files.create_or_update("file.txt", "wrong content") + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + ["Content error"] + ) +``` + +## Testing Git Operations + +```python +def test_not_committed(): + with base_setup() as (test, rs): + rs.files.append("file.txt", "new content") + # Missing: git add and commit + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + ["Not committed error"] + ) + + +def test_branch_missing(): + with base_setup() as (test, rs): + # Expected branch doesn't exist + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + ["Branch missing error"] + ) + + +def test_branch_exists(): + with base_setup() as (test, rs): + rs.git.checkout("feature", create=True) + rs.files.create_or_update("feature.txt", "content") + rs.git.add(["feature.txt"]) + rs.git.commit(message="Add feature") + rs.git.checkout("main") + + output = test.run() + assert_output(output, GitAutograderStatus.SUCCESSFUL) +``` + +## Running Exercise Tests + +```bash +# Run all tests for exercise +pytest my_exercise/ + +# Run specific test +pytest my_exercise/test_verify.py::test_base + +# Verbose output +pytest -vv my_exercise/ + +# Show print statements +pytest -s my_exercise/ +``` + +See [running-tests.md](../../testing/running-tests.md) for detailed testing commands. + +## Test Coverage Requirements + +- **100% coverage** of verify.py +- **All error messages** must have tests +- **All validation paths** must be tested +- **Edge cases** should be covered + +## Common Mistakes + +### ❌ Not Testing All Errors + +```python +# Missing test for ERROR_2 +def test_only_one_error(): + # Tests ERROR_1 but not ERROR_2 + ... +``` + +### ❌ Not Using Base Setup + +```python +# Duplicated setup in every test +def test_1(): + with loader.start() as (test, rs): + rs.files.create_or_update("file.txt", "content") # Repeated + rs.git.add(["file.txt"]) # Repeated + rs.git.commit(message="Initial") # Repeated + ... +``` + +### ❌ Not Creating Start Tag + +```python +# Missing start tag for exercises that need it +def test_without_start_tag(): + with loader.start() as (test, rs): + rs.files.create_or_update("file.txt", "content") + rs.git.add(["file.txt"]) + rs.git.commit(message="Commit") + # Missing: rs.helper(GitMasteryHelper).create_start_tag() + ... +``` + +### ❌ Forgetting to Stage Files + +```python +def test_commit_without_staging(): + with loader.start() as (test, rs): + rs.files.create_or_update("file.txt", "content") + # Missing: rs.git.add(["file.txt"]) + rs.git.commit(message="Commit") # This will fail! +``` + +## Debugging Test Failures + +### Print Repository State + +```python +def test_debug(): + with loader.start() as (test, rs): + rs.files.create_or_update("file.txt", "content") + + # Debug output (run with: pytest -s) + import os + print(f"Files: {os.listdir('.')}") + print(f"Content: {open('file.txt').read()}") + + output = test.run() + print(f"Status: {output.status}") + print(f"Comments: {output.comments}") +``` + +### Use Debugger + +```python +def test_with_debugger(): + with loader.start() as (test, rs): + import pdb; pdb.set_trace() + # Step through test + output = test.run() +``` + +## Test Documentation + +Document test purpose with docstrings: + +```python +def test_no_changes(): + """Test that exercise fails when student makes no changes.""" + with base_setup() as (test, rs): + output = test.run() + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [NO_CHANGES]) + + +def test_partial_completion(): + """Test that exercise fails when only some requirements are met.""" + with base_setup() as (test, rs): + rs.files.append("file.txt", "partial") + # Missing commit step + output = test.run() + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [NOT_COMMITTED]) +``` + +## Complete Test File Example + +See existing exercises for complete examples: +- [amateur_detective/test_verify.py](../../amateur_detective/test_verify.py) - Answer-based exercise +- [grocery_shopping/test_verify.py](../../grocery_shopping/test_verify.py) - Repository state exercise with fixture +- [view_commits/test_verify.py](../../view_commits/test_verify.py) - Multiple question exercise diff --git a/.claude/skills/exercise-utils/SKILL.md b/.claude/skills/exercise-utils/SKILL.md new file mode 100644 index 00000000..623b437c --- /dev/null +++ b/.claude/skills/exercise-utils/SKILL.md @@ -0,0 +1,108 @@ +--- +name: exercise-utils +description: Reference for shared utility modules. Use when working with Git commands, GitHub operations, or need API details for utility functions. +--- + +# Exercise Utils Reference + +## Overview +The `exercise_utils/` package provides reusable wrappers for common operations. **Always use these instead of raw subprocess calls.** + +## Quick Navigation + +### Module References +📄 **[git-module.md](git-module.md)** - Git command wrappers (`init`, `add`, `commit`, `checkout`, `merge`, `push`, etc.) + +📄 **[github-module.md](github-module.md)** - GitHub CLI wrappers (`fork_repo`, `create_repo`, `delete_repo`, etc.) + +📄 **[cli-module.md](cli-module.md)** - General CLI execution (`run`, `run_command`, `CommandResult`) + +📄 **[file-module.md](file-module.md)** - File operations (`create_or_update_file`, `append_to_file`) + +📄 **[gitmastery-module.md](gitmastery-module.md)** - Git-Mastery specific (`create_start_tag`) + +📄 **[test-module.md](test-module.md)** - Test scaffolding (`GitAutograderTestLoader`, `assert_output`) + +## Quick Reference + +### Most Common Functions + +```python +# Git operations +from exercise_utils.git import init, add, commit, checkout +init(verbose) +add(["file.txt"], verbose) +commit("Initial commit", verbose) +checkout("branch-name", create_branch=True, verbose=verbose) + +# GitHub operations +from exercise_utils.github_cli import fork_repo, get_github_username +username = get_github_username(verbose) +fork_repo("owner/repo", "fork-name", verbose) + +# File operations +from exercise_utils.file import create_or_update_file, append_to_file +create_or_update_file("file.txt", "content") +append_to_file("log.txt", "new entry\n") + +# Start tag (always last in download.py) +from exercise_utils.gitmastery import create_start_tag +create_start_tag(verbose) + +# Testing +from exercise_utils.test import GitAutograderTestLoader, assert_output +loader = GitAutograderTestLoader(REPO_NAME, verify) +with loader.start(mock_answers={}) as (test, _): + output = test.run() + assert_output(output, status, messages) +``` + +## Common Patterns + +### Complete Exercise Setup +```python +from exercise_utils.git import init, add, commit +from exercise_utils.file import create_or_update_file +from exercise_utils.gitmastery import create_start_tag + +def download(verbose: bool): + create_or_update_file("README.md", "# Project\n") + init(verbose) + add(["README.md"], verbose) + commit("Initial commit", verbose) + create_start_tag(verbose) # Always last +``` + +### GitHub Fork Pattern +```python +from exercise_utils.github_cli import get_github_username, fork_repo +from exercise_utils.git import clone_repo_with_git + +username = get_github_username(verbose) +fork_repo("git-mastery/repo", "my-fork", verbose) +clone_repo_with_git(f"https://github.com/{username}/my-fork", verbose) +``` + +## Key Principles + +1. **Always pass verbose parameter** - All functions accept it +2. **Use wrappers, not subprocess** - Never call git/gh directly +3. **Type hints required** - All functions are fully typed +4. **Errors handled** - Most functions exit on error + +## Module Dependencies + +``` +cli.py # No dependencies (base module) +├── git.py # Depends on cli +├── github_cli.py # Depends on cli +├── file.py # No dependencies +├── gitmastery.py # Depends on cli +└── test.py # Depends on git, file, gitmastery +``` + +## Related Skills + +- **[exercise-development](../exercise-development/SKILL.md)** - Using utilities in exercises +- **[coding-standards](../coding-standards/SKILL.md)** - Code conventions +- **[testing](../testing/SKILL.md)** - Using test utilities diff --git a/.claude/skills/exercise-utils/cli-module.md b/.claude/skills/exercise-utils/cli-module.md new file mode 100644 index 00000000..b4a935d9 --- /dev/null +++ b/.claude/skills/exercise-utils/cli-module.md @@ -0,0 +1,19 @@ +# cli-module.md + +## Overview +Utilities for running CLI commands with error handling. + +See [exercise_utils/cli.py](../../exercise_utils/cli.py) for implementation. + +## Key Functions +- `run(command, verbose, env={}, exit_on_error=False)` → `CommandResult` + - Returns result with `.stdout`, `.returncode`, `.is_success()` +- `run_command(command, verbose)` → `str | exits` + - Simple runner, exits on failure +- `run_command_no_exit(command, verbose)` → `str | None` + - Returns None on failure + +## Usage Examples +See usages in: +- [exercise_utils/git.py](../../exercise_utils/git.py) +- [exercise_utils/github_cli.py](../../exercise_utils/github_cli.py) diff --git a/.claude/skills/exercise-utils/file-module.md b/.claude/skills/exercise-utils/file-module.md new file mode 100644 index 00000000..bb04366d --- /dev/null +++ b/.claude/skills/exercise-utils/file-module.md @@ -0,0 +1,15 @@ +# file-module.md + +## Overview +File creation and modification utilities. + +See [exercise_utils/file.py](../../exercise_utils/file.py) for implementation. + +## Functions +- `create_or_update_file(filepath, contents=None)` - Create/overwrite file, auto-creates directories +- `append_to_file(filepath, contents)` - Append content to file + +**Features:** Auto-dedenting with `textwrap.dedent()`, automatic directory creation. + +## Usage Examples +See usages in download.py files across exercises. diff --git a/.claude/skills/exercise-utils/git-module.md b/.claude/skills/exercise-utils/git-module.md new file mode 100644 index 00000000..0de6615b --- /dev/null +++ b/.claude/skills/exercise-utils/git-module.md @@ -0,0 +1,15 @@ +# git-module.md + +## Overview +Wrapper functions for Git CLI commands. + +See [exercise_utils/git.py](../../exercise_utils/git.py) for all functions. + +## Functions +- `init(verbose)`, `add(files, verbose)`, `commit(message, verbose)`, `empty_commit(message, verbose)` +- `checkout(branch, create_branch, verbose)`, `merge(target_branch, ff, verbose)` +- `tag(tag_name, verbose)`, `tag_with_options(tag_name, options, verbose)` +- `add_remote(remote, url, verbose)`, `remove_remote(remote, verbose)`, `push(remote, branch, verbose)` +- `track_remote_branch(remote, branch, verbose)`, `clone_repo_with_git(url, verbose, name=None)` + +**Note:** All functions exit on failure, use `verbose=True` to see output. diff --git a/.claude/skills/exercise-utils/github-module.md b/.claude/skills/exercise-utils/github-module.md new file mode 100644 index 00000000..85306a70 --- /dev/null +++ b/.claude/skills/exercise-utils/github-module.md @@ -0,0 +1,18 @@ +# github-module.md + +## Overview +Wrapper functions for GitHub CLI (gh) commands. + +See [exercise_utils/github_cli.py](../../exercise_utils/github_cli.py) for all functions. + +## Functions +- `get_github_username(verbose)` → `str` +- `create_repo(name, verbose)`, `delete_repo(name, verbose)`, `clone_repo_with_gh(name, verbose, name=None)` +- `fork_repo(repo_name, fork_name, verbose, default_branch_only=True)` +- `has_repo(name, is_fork, verbose)` → `bool`, `has_fork(repo, owner, username, verbose)` → `bool` +- `get_fork_name(repo, owner, username, verbose)` → `str` + +**Prerequisites:** GitHub CLI (`gh`) installed and authenticated. + +## Usage Examples +See [fork_repo/download.py](../../fork_repo/download.py) for fork/clone pattern. diff --git a/.claude/skills/exercise-utils/gitmastery-module.md b/.claude/skills/exercise-utils/gitmastery-module.md new file mode 100644 index 00000000..12241637 --- /dev/null +++ b/.claude/skills/exercise-utils/gitmastery-module.md @@ -0,0 +1,13 @@ +# gitmastery-module.md + +## Overview +Git-Mastery specific utility for creating start tags. + +See [exercise_utils/gitmastery.py](../../exercise_utils/gitmastery.py) for implementation. + +## Function +`create_start_tag(verbose)` - Creates tag `git-mastery-start-` on first commit. + +**When to use:** After creating initial repository state, before student modifications. + +**Testing alternative:** Use `GitMasteryHelper` in tests (see test-module.md). diff --git a/.claude/skills/exercise-utils/test-module.md b/.claude/skills/exercise-utils/test-module.md new file mode 100644 index 00000000..0fd4cc50 --- /dev/null +++ b/.claude/skills/exercise-utils/test-module.md @@ -0,0 +1,25 @@ +# test-module.md + +## Overview +Testing utilities for exercise validation. + +See [exercise_utils/test.py](../../exercise_utils/test.py) for implementation. + +## Core Classes +- `GitAutograderTestLoader(exercise_name, grade_func)` - Test runner factory +- `GitMasteryHelper` - Repo-smith helper with `create_start_tag()` method +- `assert_output(output, expected_status, expected_comments=[])` - Assertion helper + +## Usage Examples +See test files: +- [amateur_detective/test_verify.py](../../amateur_detective/test_verify.py) +- [grocery_shopping/test_verify.py](../../grocery_shopping/test_verify.py) + +## Loader Pattern +```python +loader = GitAutograderTestLoader("exercise-name", verify) +with loader.start() as (test, rs): + # setup with rs (RepoSmith) + output = test.run() + assert_output(output, GitAutograderStatus.SUCCESS) +``` diff --git a/.claude/skills/project-overview/SKILL.md b/.claude/skills/project-overview/SKILL.md new file mode 100644 index 00000000..04ef1f86 --- /dev/null +++ b/.claude/skills/project-overview/SKILL.md @@ -0,0 +1,313 @@ +--- +name: project-overview +description: High-level overview of the Git-Mastery exercises repository. Use when first learning about the project or need quick orientation. +user-invocable: false +--- + +# Git-Mastery Exercises Repository + +## Overview +This repository contains 40+ modular, self-contained Git exercises designed to teach specific Git concepts through hands-on practice with automated validation. + +## Repository Purpose +- **Education**: Teach Git concepts through practical, isolated exercises +- **Validation**: Automated testing of student solutions using pytest and git-autograder +- **Modularity**: Each exercise is completely self-contained and independent +- **Consistency**: Shared utilities ensure uniform patterns across all exercises + +## Core Architecture + +### 1. Exercise Types + +#### Standard Exercises (40+ directories) +**Location**: Root directory (e.g., `amateur_detective/`, `branch_bender/`, `conflict_mediator/`) + +**Purpose**: Guided exercises with specific objectives, instructions, and automated validation. + +**Structure**: +``` +/ +├── __init__.py # Python package marker +├── download.py # Setup logic - creates exercise repo state +├── verify.py # Validation logic with verify() function +├── test_verify.py # Pytest tests for verification logic +├── README.md # Student-facing exercise instructions +└── res/ # (Optional) Exercise-specific resources +``` + +**Key Characteristics**: +- Each exercise has a `download.py` that sets up the initial Git repository state +- `verify.py` contains composable validation rules using git-autograder +- `test_verify.py` uses pytest to test the verification logic +- Exercises may require Git only (`__requires_git__`) or both Git and GitHub (`__requires_github__`) +- Start tags are created using `create_start_tag()` from `exercise_utils/gitmastery.py` + +**Example Exercises by Category**: +- **History/Investigation**: `amateur_detective/`, `view_commits/`, `log_and_order/` +- **Branching**: `branch_bender/`, `branch_delete/`, `branch_rename/`, `branch_forward/`, `bonsai_tree/` +- **Working Directory**: `sensors_checkout/`, `sensors_diff/`, `sensors_reset/`, `sensors_revert/` +- **Staging**: `staging_intervention/`, `stage_fright/` +- **Merging**: `conflict_mediator/`, `merge_squash/`, `merge_undo/` +- **Remotes**: `fetch_and_pull/`, `push_over/`, `clone_repo/`, `fork_repo/`, `remote_control/` +- **Tags**: `tags_add/`, `tags_push/`, `tags_update/` + +#### Hands-On Scripts +**Location**: `hands_on/` directory + +**Purpose**: Exploratory learning scripts that demonstrate Git operations without validation. + +**Structure**: +``` +hands_on/ +├── add_files.py +├── branch_delete.py +├── create_branch.py +├── remote_branch_pull.py +└── ... (20+ standalone scripts) +``` + +**Key Characteristics**: +- Each file is a standalone Python script demonstrating a specific Git operation +- No `download.py`, `verify.py`, or `test_verify.py` files +- Users run scripts directly to observe Git behavior +- Ideal for experimentation and understanding command effects +- Scripts follow naming pattern: `.py` + +### 2. Shared Utilities + +**Location**: `exercise_utils/` directory + +**Purpose**: Provide consistent, reusable wrappers for common operations across all exercises. + +**Core Modules**: + +#### `git.py` +- Wrappers for Git CLI commands +- Functions: `add()`, `commit()`, `empty_commit()`, `checkout()`, `merge()`, `tag()`, `init()`, `push()`, `clone_repo_with_git()`, `add_remote()`, `remove_remote()`, `track_remote_branch()` +- All functions accept `verbose: bool` parameter for output control + +#### `github_cli.py` +- Wrappers for GitHub CLI (gh) commands +- Functions: `fork_repo()`, `clone_repo_with_gh()`, `delete_repo()`, `create_repo()`, `get_github_username()`, `has_repo()` +- Requires GitHub CLI to be installed and authenticated + +#### `cli.py` +- General CLI execution helpers +- Functions: `run()`, `run_command()`, `run_command_no_exit()` +- Returns `CommandResult` dataclass with success status and output + +#### `gitmastery.py` +- Git-Mastery specific utilities +- `create_start_tag(verbose: bool)`: Creates exercise start tag from first commit hash +- Tag format: `git-mastery-start-` + +#### `test.py` +- Test scaffolding for exercise verification +- `GitMasteryHelper`: Helper class extending repo-smith's Helper +- `GitAutograderTestLoader`: Loads and runs exercise tests +- `GitAutograderTest`: Test wrapper for exercise grading +- `assert_output()`: Assertion helper for validation output + +#### `file.py` +- File operation helpers +- Functions for creating, updating, and appending to files consistently + +### 3. Dependencies & Environment + +**Python Version**: Python 3.8+ (primary development: Python 3.13) + +**Core Dependencies** (from `requirements.txt`): +- **git-autograder** (v6.*): Exercise validation framework +- **repo-smith**: Repository state creation and manipulation +- **pytest**: Testing framework +- **PyYAML**: YAML parsing +- **PyGithub**: GitHub API interactions +- **requests**: HTTP library + +**Developer Tools**: +- **ruff**: Python linter and formatter +- **mypy**: Static type checker + +**External Requirements**: +- Git CLI (required for all exercises) +- GitHub CLI (`gh`) - required for exercises with `__requires_github__ = True` +- Bash environment (for shell scripts) + +### 4. Development Workflow + +#### Setup Environment +```bash +# Create virtual environment +python -m venv venv + +# Activate (Linux/macOS) +source venv/bin/activate + +# Activate (Windows) +source venv/Scripts/activate + +# Install dependencies +pip install -r requirements.txt +``` + +Or use the provided script: +```bash +./setup.sh +``` + +#### Testing Exercises +```bash +# Test specific exercise +./test.sh + +# Example +./test.sh amateur_detective + +# This runs: python -m pytest /test_verify.py -s -vv +``` + +#### Creating New Exercises +```bash +# Use scaffolding script +./new.sh + +# You'll be prompted for: +# 1. Exercise name (kebab-case recommended) +# 2. Tags (space-separated) +# 3. Exercise configuration +``` + +The script generates the complete exercise structure with all required files. + +#### Testing Downloads +```bash +# Test all exercise download scripts +./test-download.sh +``` + +### 5. Exercise Configuration + +Each exercise has a `.gitmastery-exercise.json` file (not present in current structure but referenced in docs): +```json +{ + "exercise_name": "kebab-case-name", + "tags": ["branch", "merge", "intermediate"] +} +``` + +### 6. Validation Patterns + +Exercises use composable validation with git-autograder: + +```python +from git_autograder import GitAutograderExercise, GitAutograderOutput +from git_autograder.answers.rules import HasExactValueRule, NotEmptyRule + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + exercise.answers.add_validation(QUESTION, NotEmptyRule()) + exercise.answers.add_validation(QUESTION, HasExactValueRule("expected")) + exercise.answers.validate() + + return exercise.to_output( + ["Success message"], + GitAutograderStatus.SUCCESSFUL + ) +``` + +### 7. Test Patterns + +Tests use `GitAutograderTestLoader` for standardized testing: + +```python +from exercise_utils.test import GitAutograderTestLoader, assert_output + +loader = GitAutograderTestLoader(REPOSITORY_NAME, verify) + +def test_something(): + with loader.start(mock_answers={QUESTION: "answer"}) as (test, _): + output = test.run() + assert_output( + output, + GitAutograderStatus.SUCCESSFUL, + ["Expected message"] + ) +``` + +## Important Conventions + +### File Organization +- **One exercise per directory**: No shared state between exercises +- **Self-contained resources**: All exercise resources in `res/` subdirectory +- **Standard naming**: Use kebab-case for exercise directories +- **Python packages**: Every exercise directory has `__init__.py` + +### Git Operations +- **Use wrappers**: Always use `exercise_utils/git.py` functions, not direct subprocess calls +- **Start tags**: Create using `create_start_tag()` after repository setup +- **Verbose mode**: All utility functions support verbose parameter for debugging + +### Testing +- **Test verification logic**: Every `verify.py` must have corresponding `test_verify.py` +- **Use test loader**: Leverage `GitAutograderTestLoader` for consistent test structure +- **Mock answers**: Test various scenarios with different mock answer combinations +- **Assertions**: Use `assert_output()` for standardized validation checking + +### Code Quality +- **Type hints**: Use type annotations for all function parameters and returns +- **Docstrings**: Document all utility functions with clear descriptions +- **Error handling**: Wrap CLI operations with proper error handling +- **Consistency**: Follow patterns from existing exercises + +## Project Integration + +### Documentation +- **README.md**: Main project documentation (currently minimal) +- **summary.md**: Comprehensive repository structure documentation +- **.github/CONTRIBUTING.md**: Contributor guidelines with detailed setup instructions +- **.github/copilot-instructions.md**: AI assistant guidelines (for GitHub Copilot) + +### CI/CD +- GitHub Actions workflows in `.github/workflows/` +- Automated testing via `ci.yml` + +### Version Control +- **Don't modify**: `.git/`, `.github/`, top-level `README.md`, or scripts unless updating global logic +- **Exercise isolation**: Exercise logic stays in exercise directory +- **Shared improvements**: Common utilities go in `exercise_utils/` + +## Quick Reference + +### Common Tasks +- **Add new exercise**: `./new.sh` +- **Test exercise**: `./test.sh ` +- **Setup environment**: `./setup.sh` +- **Run single test**: `pytest /test_verify.py::test_name -s -vv` + +### Key Directories +- `exercise_utils/`: Shared utilities (git, github, cli, test helpers) +- `hands_on/`: Standalone demonstration scripts +- `.github/`: GitHub configuration and templates +- `test-downloads/`: Temporary directory for download testing + +### External Documentation +- Developer docs: https://git-mastery.github.io/developers +- Exercise directory: https://git-mastery.github.io/exercises +- Git-Mastery app: https://git-mastery.github.io/app + +## Working with This Repository + +When modifying this repository: + +1. **Understand exercise isolation**: Each exercise is completely independent +2. **Use shared utilities**: Leverage `exercise_utils/` for all Git/GitHub operations +3. **Test thoroughly**: Run `./test.sh ` after changes +4. **Follow patterns**: Match structure and style of existing exercises +5. **Document changes**: Update relevant documentation when adding features + +## Additional Resources + +For detailed information, see: +- [exercise-development](./exercise-development/SKILL.md) - Creating and modifying exercises +- [exercise-utils](./exercise-utils/SKILL.md) - Using shared utility modules +- [coding-standards](./coding-standards/SKILL.md) - Code style and quality guidelines +- [testing](./testing/SKILL.md) - Testing strategies and patterns diff --git a/.claude/skills/project-overview/architecture.md b/.claude/skills/project-overview/architecture.md new file mode 100644 index 00000000..37a083d4 --- /dev/null +++ b/.claude/skills/project-overview/architecture.md @@ -0,0 +1,127 @@ +# Repository Architecture + +## Exercise Structure + +### Standard Exercise Anatomy +``` +/ +├── __init__.py # Python package marker +├── download.py # Setup logic - creates exercise repo state +├── verify.py # Validation logic with verify() function +├── test_verify.py # Pytest tests for verification logic +├── README.md # Student-facing exercise instructions +└── res/ # (Optional) Exercise-specific resources +``` + +**Key Files**: + +- **`download.py`**: Sets up initial Git repository state + - Required variables: `__requires_git__`, `__requires_github__` + - Required function: `def download(verbose: bool)` + - Creates start tag using `create_start_tag()` + +- **`verify.py`**: Validates student solutions + - Required function: `def verify(exercise: GitAutograderExercise) -> GitAutograderOutput` + - Uses composable validation rules from git-autograder + - Returns success/failure status with messages + +- **`test_verify.py`**: Tests the verification logic + - Uses pytest and `GitAutograderTestLoader` + - Tests multiple scenarios (no answers, wrong answers, correct answers) + - Required constant: `REPOSITORY_NAME` matching directory name + +- **`README.md`**: Student instructions + - Scenario/context + - Task description + - Progressive hints + +### Hands-On Script Anatomy +``` +hands_on/ +└── .py # Single demonstration script +``` + +**Structure**: +- Required variables: `__requires_git__`, `__requires_github__` +- Required function: `def download(verbose: bool)` +- No validation, tests, or README +- Focus on demonstration and exploration + +## Shared Utilities (`exercise_utils/`) + +### Module Overview +- **`git.py`**: Git CLI command wrappers +- **`github_cli.py`**: GitHub CLI (gh) wrappers +- **`cli.py`**: General CLI execution helpers +- **`gitmastery.py`**: Git-Mastery specific utilities (start tags) +- **`file.py`**: File operation helpers +- **`test.py`**: Test scaffolding and helpers + +### Design Patterns +All utility functions follow consistent patterns: +- Accept `verbose: bool` parameter for output control +- Use type hints for all parameters and returns +- Handle errors consistently (most exit on error) +- Return `CommandResult` or specific types + +## Exercise Categories + +### History & Investigation +- `amateur_detective/` - Uncover file changes with git status +- `view_commits/` - Exploring commit history +- `log_and_order/` - Understanding git log + +### Branching +- `branch_bender/` - Creating and managing branches +- `branch_delete/` - Deleting branches safely +- `branch_rename/` - Renaming branches +- `branch_forward/` - Moving branch pointers +- `bonsai_tree/` - Complex branch structures + +### Working Directory +- `sensors_checkout/` - Checking out commits +- `sensors_diff/` - Viewing differences +- `sensors_reset/` - Resetting changes +- `sensors_revert/` - Reverting commits + +### Staging Area +- `staging_intervention/` - Managing staged changes +- `stage_fright/` - Understanding the staging area + +### Merging +- `conflict_mediator/` - Resolving merge conflicts +- `merge_squash/` - Squash merging +- `merge_undo/` - Undoing merges + +### Remotes & Collaboration +- `fetch_and_pull/` - Fetching and pulling changes +- `push_over/` - Pushing to remotes +- `clone_repo/` - Cloning repositories +- `fork_repo/` - Forking on GitHub +- `remote_control/` - Managing remotes + +### Tags +- `tags_add/` - Creating tags +- `tags_push/` - Pushing tags +- `tags_update/` - Updating tags + +## Integration Points + +### External Tools Required +- **Git CLI**: Required for all exercises +- **GitHub CLI (gh)**: Required when `__requires_github__ = True` +- **Python 3.8+**: Runtime environment +- **Bash**: For shell scripts + +### CI/CD Integration +- GitHub Actions workflows in `.github/workflows/` +- Automated testing on PR and merge +- Linting and formatting checks + +## File Organization Principles + +1. **Self-Contained**: Each exercise has all resources in its directory +2. **No Shared State**: Exercises are completely independent +3. **Standard Naming**: Use kebab-case for directories +4. **Python Packages**: Every exercise has `__init__.py` +5. **Resource Isolation**: Exercise-specific resources in `res/` diff --git a/.claude/skills/project-overview/dependencies.md b/.claude/skills/project-overview/dependencies.md new file mode 100644 index 00000000..726f538b --- /dev/null +++ b/.claude/skills/project-overview/dependencies.md @@ -0,0 +1,222 @@ +# Dependencies & Environment + +## Python Requirements + +### Version Support +- **Minimum**: Python 3.8 +- **Recommended**: Python 3.13 +- **Compatibility**: Code must work on 3.8+ + +## Core Dependencies + +From `requirements.txt`: + +### Functional Dependencies +``` +git-autograder==6.* # Exercise validation framework +PyYAML # YAML parsing +``` + +### Test Dependencies +``` +pytest # Testing framework +repo-smith # Repository state creation +``` + +### Developer Tools +``` +ruff # Linting and formatting +mypy # Static type checking +PyGithub # GitHub API interactions +requests # HTTP library +types-requests # Type stubs for requests +``` + +## External Requirements + +### Required Tools +- **Git CLI**: Version 2.0+ recommended + - Used by all exercises + - Must be in system PATH + +- **GitHub CLI (gh)**: Latest version + - Required for exercises with `__requires_github__ = True` + - Must be authenticated: `gh auth login` + +### Operating System +- **Linux**: Fully supported +- **macOS**: Fully supported +- **Windows**: Supported (use Git Bash or WSL for shell scripts) + +## Environment Setup + +### Quick Setup +```bash +# Run provided script +./setup.sh +``` + +### Manual Setup +```bash +# 1. Create virtual environment +python -m venv venv + +# 2. Activate virtual environment +# Linux/macOS: +source venv/bin/activate + +# Windows: +source venv/Scripts/activate + +# 3. Install dependencies +pip install -r requirements.txt +``` + +### Verify Installation +```bash +# Check Python version +python --version # Should be 3.8+ + +# Check Git +git --version + +# Check GitHub CLI (if needed) +gh --version +gh auth status + +# Run tests to verify setup +./test.sh amateur_detective +``` + +## Package Details + +### git-autograder (v6.*) +**Purpose**: Exercise validation framework + +**Key Components**: +- `GitAutograderExercise`: Exercise context +- `GitAutograderOutput`: Validation results +- `GitAutograderStatus`: Success/failure states +- Validation rules: `NotEmptyRule`, `HasExactValueRule`, etc. + +**Usage**: Required for all `verify.py` implementations + +### pytest +**Purpose**: Testing framework + +**Usage**: +- All exercise tests in `test_verify.py` +- Run via `./test.sh ` +- Supports fixtures, parameterization, markers + +### repo-smith +**Purpose**: Repository state creation for testing + +**Key Components**: +- `RepoSmith`: Repository manipulation +- `Helper`: Base helper class +- Specification-based repo creation + +**Usage**: Used in test setup and `exercise_utils/test.py` + +### ruff +**Purpose**: Fast Python linter and formatter + +**Usage**: +```bash +# Format code +ruff format . + +# Check for issues +ruff check . + +# Auto-fix issues +ruff check --fix . +``` + +**Configuration**: Follows Black defaults (88 char line length) + +### mypy +**Purpose**: Static type checking + +**Usage**: +```bash +# Type check specific module +mypy exercise_utils/ + +# Type check exercise +mypy amateur_detective/ +``` + +**Configuration**: Requires type hints on all functions + +## Dependency Management + +### Updating Dependencies +```bash +# Update all packages +pip install -r requirements.txt --upgrade + +# Update specific package +pip install --upgrade git-autograder +``` + +### Freezing Dependencies +```bash +# Generate locked requirements +pip freeze > requirements-lock.txt +``` + +### Checking for Issues +```bash +# Check for security vulnerabilities +pip audit + +# Check for outdated packages +pip list --outdated +``` + +## Development Environment + +### Recommended Setup +1. Python 3.13 with virtual environment +2. Git 2.40+ +3. GitHub CLI 2.0+ +4. VS Code or PyCharm +5. ruff and mypy installed globally or in venv + +### Optional Tools +- **GitHub Desktop**: For visual Git operations +- **pytest-xdist**: Parallel test execution +- **pre-commit**: Git hooks for quality checks + +## Common Issues + +### Git Not Found +```bash +# Add Git to PATH or install +# Windows: Download from git-scm.com +# Linux: apt install git or yum install git +# macOS: brew install git +``` + +### GitHub CLI Not Authenticated +```bash +# Authenticate +gh auth login + +# Follow prompts to login +``` + +### Wrong Python Version +```bash +# Use specific Python version +python3.13 -m venv venv +source venv/bin/activate +``` + +### Module Import Errors +```bash +# Reinstall dependencies +pip install -r requirements.txt --force-reinstall +``` diff --git a/.claude/skills/project-overview/quick-reference.md b/.claude/skills/project-overview/quick-reference.md new file mode 100644 index 00000000..af303e4f --- /dev/null +++ b/.claude/skills/project-overview/quick-reference.md @@ -0,0 +1,258 @@ +# Quick Reference + +## Essential Commands + +```bash +# Setup +./setup.sh # Create venv and install deps + +# Testing +./test.sh # Test specific exercise +pytest . -s -vv # Test all exercises +pytest /test_verify.py::test_name # Test specific function + +# Quality +ruff format . # Format code +ruff check . # Check linting +ruff check --fix . # Auto-fix issues +mypy / # Type checking + +# Development +./new.sh # Create new exercise +./test-download.sh # Test all downloads +``` + +## Directory Structure Quick Map + +``` +exercises/ +├── amateur_detective/ # Example standard exercise +│ ├── __init__.py +│ ├── download.py # Setup logic +│ ├── verify.py # Validation +│ ├── test_verify.py # Tests +│ ├── README.md # Instructions +│ └── res/ # Resources +│ +├── hands_on/ # Demonstration scripts +│ ├── add_files.py +│ ├── branch_delete.py +│ └── ... +│ +├── exercise_utils/ # Shared utilities +│ ├── git.py # Git wrappers +│ ├── github_cli.py # GitHub wrappers +│ ├── cli.py # CLI helpers +│ ├── gitmastery.py # Start tags +│ ├── file.py # File ops +│ └── test.py # Test helpers +│ +├── .claude/skills/ # AI documentation +├── .github/ # CI/CD +├── setup.sh # Setup script +├── test.sh # Test script +├── new.sh # Scaffolding script +├── requirements.txt # Dependencies +└── CLAUDE.md # AI context entry +``` + +## File Purposes + +| File | Purpose | Required | +|------|---------|----------| +| `__init__.py` | Python package marker | Yes (exercises) | +| `download.py` | Exercise setup | Yes (exercises) | +| `verify.py` | Solution validation | Yes (exercises) | +| `test_verify.py` | Tests for validation | Yes (exercises) | +| `README.md` | Student instructions | Yes (exercises) | +| `res/` | Exercise resources | Optional | + +## Common Import Patterns + +```python +# Git operations +from exercise_utils.git import init, add, commit, checkout, merge + +# GitHub operations +from exercise_utils.github_cli import fork_repo, get_github_username + +# File operations +from exercise_utils.file import create_or_update_file, append_to_file + +# Git-Mastery specific +from exercise_utils.gitmastery import create_start_tag + +# Testing +from exercise_utils.test import GitAutograderTestLoader, assert_output + +# Validation +from git_autograder import GitAutograderExercise, GitAutograderOutput +from git_autograder.answers.rules import NotEmptyRule, HasExactValueRule +``` + +## Required Variables + +### In download.py or hands_on scripts +```python +__requires_git__ = True # Always set this +__requires_github__ = False # True if uses GitHub CLI +``` + +### In test_verify.py +```python +REPOSITORY_NAME = "exercise-name" # Must match directory name exactly +``` + +## Validation Patterns + +### Basic Answer Validation +```python +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + ( + exercise.answers + .add_validation(QUESTION, NotEmptyRule()) + .add_validation(QUESTION, HasExactValueRule("expected")) + .validate() + ) + return exercise.to_output( + ["Success message"], + GitAutograderStatus.SUCCESSFUL + ) +``` + +### Test Pattern +```python +def test_correct(): + with loader.start(mock_answers={QUESTION: "answer"}) as (test, _): + output = test.run() + assert_output(output, GitAutograderStatus.SUCCESSFUL, ["Success"]) +``` + +## Key Conventions + +### Naming +- **Directories**: kebab-case (`branch-forward`, `amateur-detective`) +- **Functions**: snake_case (`verify`, `download`, `create_start_tag`) +- **Constants**: UPPER_SNAKE_CASE (`QUESTION_ONE`, `REPOSITORY_NAME`) +- **Classes**: PascalCase (`GitMasteryHelper`, `ExerciseValidator`) + +### Exercise Setup Pattern +```python +def setup(verbose: bool = False): + # 1. Create files + create_or_update_file("file.txt", "content") + + # 2. Initialize Git + init(verbose) + add(["file.txt"], verbose) + commit("Initial commit", verbose) + + # 3. Make changes for exercise + # ... exercise-specific setup ... + + # 4. Create start tag (ALWAYS LAST) + create_start_tag(verbose) +``` + +### Hands-On Script Pattern +```python +def download(verbose: bool): + # 1. Setup demo environment + os.makedirs("demo-repo") + os.chdir("demo-repo") + + # 2. Demonstrate operation + init(verbose) + # ... demo-specific operations ... + + # 3. Guide user + if verbose: + print("\n✓ Demo complete") + print("Try running: git log") +``` + +## Common Pitfalls + +### ❌ Don't Do +```python +# Raw subprocess calls +import subprocess +subprocess.run(["git", "add", "file.txt"]) + +# Hardcoded values +exercise.answers.add_validation("Which file?", ...) + +# Missing type hints +def verify(exercise): + pass + +# Wrong directory name +REPOSITORY_NAME = "amateur_detective" # Should be "amateur-detective" +``` + +### ✅ Do This +```python +# Use utility wrappers +from exercise_utils.git import add +add(["file.txt"], verbose) + +# Use constants +QUESTION_ONE = "Which file?" +exercise.answers.add_validation(QUESTION_ONE, ...) + +# Include type hints +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + pass + +# Match directory name exactly +REPOSITORY_NAME = "amateur-detective" +``` + +## Debugging Quick Tips + +```bash +# Verbose test output +pytest /test_verify.py -s -vv + +# Run single test +pytest /test_verify.py::test_name -s -vv + +# Python debugger +pytest /test_verify.py --pdb + +# Check imports +python -c "from exercise_utils.git import add; print('OK')" + +# Test download manually +python -c "from .download import download; download(True)" + +# Check Git/GitHub CLI +git --version +gh auth status +``` + +## Status Codes + +```python +from git_autograder import GitAutograderStatus + +GitAutograderStatus.SUCCESSFUL # All validations passed +GitAutograderStatus.UNSUCCESSFUL # Some validations failed +``` + +## Useful Links + +- **Project**: [README.md](../../../README.md) +- **Contributing**: [.github/CONTRIBUTING.md](../../../.github/CONTRIBUTING.md) +- **Developer Docs**: https://git-mastery.github.io/developers +- **Exercise Directory**: https://git-mastery.github.io/exercises + +## Need More Help? + +- **Architecture details**: [architecture.md](architecture.md) +- **Dependencies & setup**: [dependencies.md](dependencies.md) +- **Development workflows**: [workflows.md](workflows.md) +- **Exercise development**: [../exercise-development/SKILL.md](../exercise-development/SKILL.md) +- **Utility reference**: [../exercise-utils/SKILL.md](../exercise-utils/SKILL.md) +- **Coding standards**: [../coding-standards/SKILL.md](../coding-standards/SKILL.md) +- **Testing guide**: [../testing/SKILL.md](../testing/SKILL.md) diff --git a/.claude/skills/project-overview/workflows.md b/.claude/skills/project-overview/workflows.md new file mode 100644 index 00000000..b3db9703 --- /dev/null +++ b/.claude/skills/project-overview/workflows.md @@ -0,0 +1,349 @@ +# Development Workflows + +## Common Tasks + +### Setup Development Environment +```bash +# One-time setup +./setup.sh + +# This creates venv and installs dependencies +# Equivalent to: +python -m venv venv +source venv/bin/activate # or venv/Scripts/activate on Windows +pip install -r requirements.txt +``` + +### Test an Exercise +```bash +# Test specific exercise +./test.sh + +# Example +./test.sh amateur_detective + +# Runs: python -m pytest /test_verify.py -s -vv +``` + +### Create New Exercise +```bash +# Use interactive scaffolding +./new.sh + +# Prompts for: +# 1. Exercise name (kebab-case) +# 2. Tags (space-separated) +# 3. Exercise configuration + +# Generates complete directory structure +``` + +### Run All Tests +```bash +# Test all exercises +pytest . -s -vv + +# Test specific test function +pytest amateur_detective/test_verify.py::test_correct_answers -s -vv + +# Stop at first failure +pytest . -x -s -vv +``` + +### Test Downloads +```bash +# Test all download.py scripts +./test-download.sh + +# Verifies each download script executes without errors +``` + +### Code Quality Checks +```bash +# Format code +ruff format . + +# Check linting +ruff check . + +# Auto-fix issues +ruff check --fix . + +# Type checking +mypy exercise_utils/ +mypy / + +# Run all checks +ruff format . && ruff check . && mypy . +``` + +## Development Scripts + +### `setup.sh` +**Purpose**: Initial environment setup + +**What it does**: +- Creates Python virtual environment +- Activates the venv +- Installs all dependencies from requirements.txt + +**When to use**: First time setup or after clean clone + +### `test.sh ` +**Purpose**: Test specific exercise + +**What it does**: +- Activates virtual environment +- Runs pytest on exercise's test_verify.py +- Shows verbose output (-s -vv flags) + +**Example**: +```bash +./test.sh amateur_detective +./test.sh branch_bender +``` + +### `test-download.sh` +**Purpose**: Validate all download scripts + +**What it does**: +- Tests each exercise's download.py +- Verifies scripts execute without errors +- Doesn't validate correctness, just execution + +**When to use**: Before committing changes to download scripts + +### `new.sh` +**Purpose**: Scaffold new exercise + +**What it does**: +- Interactive prompts for exercise details +- Creates directory structure +- Generates template files: + - `__init__.py` + - `download.py` + - `verify.py` + - `test_verify.py` + - `README.md` + - `res/` directory + +**When to use**: Starting a new exercise implementation + +### `dump.sh` +**Purpose**: Development utility + +**What it does**: Repository-specific utility (check source for details) + +## Exercise Development Workflow + +### Standard Exercise +```bash +# 1. Create exercise discussion issue on GitHub +# 2. Wait for approval +# 3. Request remote repository if needed + +# 4. Generate scaffolding +./new.sh +# Enter: exercise-name, tags, config + +# 5. Implement download.py +# - Setup repository state +# - Create start tag + +# 6. Write README.md +# - Scenario and task +# - Progressive hints + +# 7. Implement verify.py +# - Add validation rules +# - Return appropriate status + +# 8. Write test_verify.py +# - Test all scenarios +# - No answers, wrong answers, correct answers + +# 9. Test locally +./test.sh exercise-name + +# 10. Verify quality +ruff format . +ruff check . +mypy exercise-name/ + +# 11. Commit and push +git add exercise-name/ +git commit -m "Add exercise-name exercise" +git push +``` + +### Hands-On Script +```bash +# 1. Create file +touch hands_on/my_demo.py + +# 2. Implement +# - Add __requires_git__ and __requires_github__ +# - Implement download(verbose: bool) +# - Add helpful print statements + +# 3. Test manually +python hands_on/my_demo.py + +# 4. Verify created state +cd demo-repo # or whatever it creates +git status +git log + +# 5. Commit +git add hands_on/my_demo.py +git commit -m "Add my_demo hands-on script" +git push +``` + +## Testing Workflow + +### Test-Driven Development +```bash +# 1. Write test_verify.py first +# - Define expected behavior +# - Test failure cases + +# 2. Run tests (they fail) +./test.sh exercise-name + +# 3. Implement verify.py +# - Make tests pass one by one + +# 4. Refactor +# - Clean up code +# - Tests still pass +``` + +### Debugging Tests +```bash +# Run single test with full output +pytest exercise/test_verify.py::test_name -s -vv + +# Add print statements in test +def test_something(): + output = test.run() + print(f"Status: {output.status}") + print(f"Messages: {output.messages}") + assert_output(...) + +# Run with Python debugger +pytest exercise/test_verify.py --pdb +``` + +## Git Workflow + +### Branch Strategy +```bash +# Create feature branch +git checkout -b feature/new-exercise + +# Make changes +# ... implement exercise ... + +# Commit frequently +git add exercise-name/ +git commit -m "Add download logic" + +git add exercise-name/ +git commit -m "Add verification rules" + +# Push and create PR +git push origin feature/new-exercise +``` + +### Commit Messages +```bash +# Good commit messages +git commit -m "Add amateur_detective exercise" +git commit -m "Fix validation in branch_bender" +git commit -m "Update exercise_utils git module docs" + +# Bad commit messages +git commit -m "updates" +git commit -m "fix" +git commit -m "wip" +``` + +## CI/CD Workflow + +### What Runs on PR +1. All pytest tests +2. Ruff linting +3. Ruff formatting check +4. Type checking with mypy + +### Pre-Push Checklist +```bash +# Run same checks as CI locally +./test.sh # Tests pass +ruff format . # Code formatted +ruff check . # No lint errors +mypy / # Type hints valid +``` + +## Troubleshooting Workflows + +### Tests Failing Locally +```bash +# 1. Check Python environment +which python +python --version + +# 2. Reinstall dependencies +pip install -r requirements.txt --force-reinstall + +# 3. Run single test with verbose output +pytest exercise/test_verify.py::test_name -s -vv + +# 4. Check for import errors +python -c "from exercise_utils.git import add; print('OK')" +``` + +### Download Script Not Working +```bash +# 1. Test manually with verbose +python -c "from exercise.download import download; download(True)" + +# 2. Check Git/GitHub CLI +git --version +gh auth status + +# 3. Check working directory +pwd +ls -la +``` + +### Can't Run Scripts +```bash +# Make scripts executable (Linux/macOS) +chmod +x setup.sh test.sh new.sh + +# Use sh/bash explicitly +bash setup.sh +bash test.sh amateur_detective +``` + +## Performance Tips + +### Faster Testing +```bash +# Test only specific file +pytest amateur_detective/test_verify.py + +# Run tests in parallel (requires pytest-xdist) +pytest -n auto + +# Skip slow tests (if marked) +pytest -m "not slow" +``` + +### Faster Development +- Use pytest watch mode: `pytest-watch` +- Keep virtual environment activated +- Use IDE integration for testing +- Cache pip packages: `pip install --cache-dir=.cache` diff --git a/.claude/skills/testing/SKILL.md b/.claude/skills/testing/SKILL.md new file mode 100644 index 00000000..6f3104ca --- /dev/null +++ b/.claude/skills/testing/SKILL.md @@ -0,0 +1,151 @@ +--- +name: testing +description: Testing guidelines for exercises. Use when writing tests or debugging test failures. +--- + +# Testing + +## Examples +See [amateur_detective/test_verify.py](../../amateur_detective/test_verify.py) - Answer-based tests. +See [grocery_shopping/test_verify.py](../../grocery_shopping/test_verify.py) - Repository state tests with fixtures. + +## Basic Structure +```python +from exercise_utils.test import GitAutograderTestLoader, assert_output +from git_autograder import GitAutograderStatus +from .verify import verify, QUESTION_ONE, ERROR_MESSAGE + +loader = GitAutograderTestLoader("exercise-name", verify) + +def test_success(): + with loader.start(mock_answers={QUESTION_ONE: "answer"}) as (test, rs): + output = test.run() + assert_output(output, GitAutograderStatus.SUCCESSFUL) +``` + +## Required Test Scenarios + +**Answer-Based:** +1. No answers (all empty) +2. Partial answers (some missing) +3. Wrong answers (test each question) +4. Valid answers (all correct) + +**Repository State:** +1. No changes made +2. Partial completion (e.g., only added, not removed) +3. Wrong approach (e.g., changes not committed) +4. File missing/deleted +5. Valid submission + +## Common Patterns + +### Base Fixture (Repository State) +```python +from contextlib import contextmanager + +@contextmanager +def base_setup(): + with loader.start() as (test, rs): + rs.files.create_or_update("file.txt", "content") + rs.git.add(["file.txt"]) + rs.git.commit(message="Initial") + rs.helper(GitMasteryHelper).create_start_tag() + yield test, rs +``` + +### Mock Answers (Answer-Based) +```python +def test_no_answers(): + with loader.start(mock_answers={}) as (test, _): + output = test.run() + assert_output(output, GitAutograderStatus.UNSUCCESSFUL) + +def test_wrong_answer(): + with loader.start(mock_answers={Q1: "wrong"}) as (test, _): + output = test.run() + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [Q1_ERROR]) +``` + +## RepoSmith (rs) APIs + +**File Operations:** +- `rs.files.create_or_update(path, content)` +- `rs.files.append(path, content)` +- `rs.files.delete(path)` +- `rs.files.delete_lines(path, pattern)` + +**Git Operations:** +- `rs.git.add(files)`, `rs.git.commit(message="msg")` +- `rs.git.checkout(branch, create=True)` +- `rs.git.merge(branch)`, `rs.git.tag(name)` + +**Helper:** +- `rs.helper(GitMasteryHelper).create_start_tag()` + +## Assertions +```python +# Success +assert_output(output, GitAutograderStatus.SUCCESSFUL, ["Congratulations!"]) + +# Failure with specific errors +assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [ERROR_NO_ADD, ERROR_NO_REMOVE] +) +``` + +## Running Tests +```bash +pytest exercise_name/ # Run exercise +pytest exercise/test_verify.py::test_x # Specific test +pytest -s -vv # Verbose with output +pytest -x # Stop on first fail +pytest --lf # Run last failed +pytest -l # Show locals on fail +``` + +## Debug Techniques +```python +import pdb; pdb.set_trace() # Breakpoint +print(f"Files: {os.listdir('.')}") # Check state +print(f"Output: {output.comments}") # Check output +``` + +### Test Independence +Each test should be independent - no shared state + +### Clear Naming +```python +def test_no_answers_provided(): + pass + +def test_all_correct_answers(): + pass +``` + +### Use Constants +```python +# Import from verify module +from .verify import QUESTION_ONE, QUESTION_TWO + +# Don't hardcode +mock_answers = {QUESTION_ONE: "answer"} # ✓ +mock_answers = {"Which file?": "answer"} # ✗ +``` + +## Pre-Test Checklist + +- ✓ All scenarios covered +- ✓ `REPOSITORY_NAME` matches directory +- ✓ Constants imported from verify.py +- ✓ Tests pass: `./test.sh ` +- ✓ Docstrings added +- ✓ No hardcoded values + +## Related Skills + +- **[exercise-development](../exercise-development/SKILL.md)** - Creating testable exercises +- **[exercise-utils](../exercise-utils/SKILL.md)** - Using test utilities +- **[coding-standards](../coding-standards/SKILL.md)** - Test code style From 146fe4b53436ff566bb717ca41350d8630b817ae Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Mon, 26 Jan 2026 21:02:30 +0800 Subject: [PATCH 05/18] Remove unnecessary file --- .claude/skills/coding-standards/SKILL.md | 107 ++---- .../skills/coding-standards/best-practices.md | 220 ----------- .../skills/coding-standards/documentation.md | 223 ----------- .../coding-standards/naming-conventions.md | 16 - .../skills/coding-standards/style-guide.md | 45 --- .claude/skills/coding-standards/type-hints.md | 80 ---- .claude/skills/exercise-development/SKILL.md | 14 - .../exercise-development/common-patterns.md | 355 ------------------ .../exercise-development/testing-exercises.md | 293 --------------- .claude/skills/project-overview/SKILL.md | 179 +++------ .../skills/project-overview/architecture.md | 127 ------- .../skills/project-overview/dependencies.md | 222 ----------- .../project-overview/quick-reference.md | 258 ------------- .claude/skills/project-overview/workflows.md | 349 ----------------- .claude/skills/testing/SKILL.md | 151 -------- AGENT.md | 131 +++---- 16 files changed, 142 insertions(+), 2628 deletions(-) delete mode 100644 .claude/skills/coding-standards/best-practices.md delete mode 100644 .claude/skills/coding-standards/documentation.md delete mode 100644 .claude/skills/coding-standards/naming-conventions.md delete mode 100644 .claude/skills/coding-standards/style-guide.md delete mode 100644 .claude/skills/coding-standards/type-hints.md delete mode 100644 .claude/skills/exercise-development/common-patterns.md delete mode 100644 .claude/skills/exercise-development/testing-exercises.md delete mode 100644 .claude/skills/project-overview/architecture.md delete mode 100644 .claude/skills/project-overview/dependencies.md delete mode 100644 .claude/skills/project-overview/quick-reference.md delete mode 100644 .claude/skills/project-overview/workflows.md delete mode 100644 .claude/skills/testing/SKILL.md diff --git a/.claude/skills/coding-standards/SKILL.md b/.claude/skills/coding-standards/SKILL.md index a13042cc..64840e67 100644 --- a/.claude/skills/coding-standards/SKILL.md +++ b/.claude/skills/coding-standards/SKILL.md @@ -5,93 +5,64 @@ description: Code style and quality guidelines. Use when writing or reviewing co # Coding Standards -## Overview -Follow these standards to ensure consistency, readability, and maintainability. - -## Quick Rules +## Tools +```bash +ruff format . # Format code +ruff check . # Check linting +ruff check --fix . # Auto-fix issues +mypy / # Type checking +``` -### Style -- **Line length**: 88 characters (Black/Ruff default) -- **Indentation**: 4 spaces -- **Formatter**: ruff (`ruff format .`) -- **Linter**: ruff (`ruff check .`) -- **Type checker**: mypy +## Style +- **88 char** line length +- **4 spaces** indentation +- **Double quotes** for strings +- 2 blank lines between functions -### Naming -- **Functions/variables**: `snake_case` -- **Constants**: `UPPER_SNAKE_CASE` +## Naming +- **Functions/Variables**: `snake_case` +- **Constants**: `UPPER_SNAKE_CASE` - **Classes**: `PascalCase` +- **Tests**: `test_description` - **Directories**: `kebab-case` +- Actions: `create_*`, `add_*`, `remove_*` +- Queries: `get_*`, `has_*`, `is_*` -### Type Hints +## Type Hints +Always include on function signatures: ```python -# Always include types -def my_function(param: str, verbose: bool) -> Optional[str]: - pass -``` - -## Detailed Guides - -📄 **[style-guide.md](style-guide.md)** - Formatting, line length, imports, indentation - -📄 **[naming-conventions.md](naming-conventions.md)** - Functions, classes, files, directories - -📄 **[type-hints.md](type-hints.md)** - Using type annotations, common patterns - -📄 **[documentation.md](documentation.md)** - Docstrings, comments, when to document +def func(name: str, count: int, verbose: bool) -> None: + ... -📄 **[best-practices.md](best-practices.md)** - DRY, composition, error handling, patterns - -## Quick Checks - -```bash -# Format code -ruff format . - -# Check linting -ruff check . - -# Auto-fix -ruff check --fix . - -# Type check -mypy / - -# All checks -ruff format . && ruff check . && mypy . +def get_data(path: Optional[str] = None) -> List[str]: + ... ``` -## Common Patterns - -### Import Organization +## Imports +Order: stdlib → third-party → local (blank lines between) ```python -# 1. Standard library import os -from typing import List, Optional +from typing import List -# 2. Third party from git_autograder import GitAutograderExercise -# 3. Local -from exercise_utils.git import add, commit +from exercise_utils.git import commit ``` -### Function Documentation +## Documentation ```python -def merge_with_message( - target_branch: str, ff: bool, message: str, verbose: bool -) -> None: - """Merge target branch with custom message. - - Args: - target_branch: Branch to merge - ff: Allow fast-forward if True - message: Custom commit message - verbose: Print output if True - """ - pass +def tag(tag_name: str, verbose: bool) -> None: + """Tags the latest commit with the given tag_name.""" + ... ``` +## Best Practices +- **DRY**: Extract common logic +- **Early returns**: Check errors first +- **Single responsibility**: Functions < 50 lines, < 5 params +- **Context managers**: Use `with` for resources +- **Constants at top**: Module-level `UPPER_CASE` + ## Anti-Patterns ### ❌ Don't diff --git a/.claude/skills/coding-standards/best-practices.md b/.claude/skills/coding-standards/best-practices.md deleted file mode 100644 index ac42644e..00000000 --- a/.claude/skills/coding-standards/best-practices.md +++ /dev/null @@ -1,220 +0,0 @@ -# best-practices.md - -## DRY -Extract common logic into helpers. - -## Error Handling -Use early returns and specific exceptions. - -```python -def verify(exercise): - if not os.path.exists(file_path): - raise exercise.wrong_answer([FILE_MISSING]) - if not items: - raise exercise.wrong_answer([NO_ITEMS]) - return exercise.to_output(["Success"], GitAutograderStatus.SUCCESSFUL) -``` - -## Function Design -- Single responsibility -- < 50 lines -- < 5 parameters -- Early returns - -## Context Managers -```python -with open(filepath) as f: - content = f.read() - -with loader.start() as (test, rs): - # test setup - ... -``` - with open(shopping_list_path) as f: - content = f.read() -``` - -### Constants at Top - -```python -# Module-level constants -QUESTION_ONE = "Which file was added?" -QUESTION_TWO = "Which file was edited?" -REPOSITORY_NAME = "amateur-detective" - -def verify(exercise): - # Use constants - exercise.answers.add_validation(QUESTION_ONE, NotEmptyRule()) -``` - -## Context Managers - -### Use for Resource Management - -```python -# File handling -with open(filepath, "r") as f: - content = f.read() - -# Test setup/teardown -with loader.start() as (test, rs): - rs.files.create_or_update("file.txt", "content") - output = test.run() - # Automatic cleanup -``` - -### Custom Context Managers - -```python -from contextlib import contextmanager - -@contextmanager -def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]: - with loader.start() as (test, rs): - # Common setup - rs.files.create_or_update("shopping-list.txt", initial_content) - rs.git.add(["shopping-list.txt"]) - rs.git.commit(message="Initial commit") - rs.helper(GitMasteryHelper).create_start_tag() - - yield test, rs - # Automatic cleanup -``` - -## Data Structures - -### Choose Appropriate Types - -```python -# Sets for membership testing -ORIGINAL_SHOPPING_LIST = {"Milk", "Eggs", "Bread"} # O(1) lookup - -# Lists for ordered collections -commits = ["abc123", "def456", "ghi789"] - -# Dicts for key-value lookups -mock_answers = {"question1": "answer1", "question2": "answer2"} -``` - -### Use dataclasses - -```python -from dataclasses import dataclass - -@dataclass -class CommandResult: - result: CompletedProcess[str] - - def is_success(self) -> bool: - return self.result.returncode == 0 - - @property - def stdout(self) -> str: - return self.result.stdout.strip() -``` - -## Testing Best Practices - -### One Assertion Per Test - -**Good:** -```python -def test_no_answers(): - with loader.start(mock_answers={}) as (test, _): - output = test.run() - assert_output(output, GitAutograderStatus.UNSUCCESSFUL) - -def test_wrong_answer(): - with loader.start(mock_answers={QUESTION: "wrong"}) as (test, _): - output = test.run() - assert_output( - output, - GitAutograderStatus.UNSUCCESSFUL, - [HasExactValueRule.NOT_EXACT.format(question=QUESTION)] - ) -``` - -### Descriptive Test Names - -```python -# Good - describes what's being tested -def test_no_answers(): - ... - -def test_incomplete_answer(): - ... - -def test_wrong_question_one(): - ... - -def test_valid_answers(): - ... -``` - -### Use Test Fixtures - -```python -@contextmanager -def base_setup(): - """Common setup for all tests.""" - with loader.start() as (test, rs): - # Setup - yield test, rs - -def test_case_one(): - with base_setup() as (test, rs): - # Test-specific logic - ... -``` - -## Performance Considerations - -### Avoid Premature Optimization - -- Write clear code first -- Optimize only when needed -- Profile before optimizing - -### Use Comprehensions - -```python -# Good -current_items = {line[2:].strip() for line in content.splitlines() if line.startswith("- ")} - -# Instead of -current_items = set() -for line in content.splitlines(): - if line.startswith("- "): - current_items.add(line[2:].strip()) -``` - -## Code Smells to Avoid - -❌ **Long functions** (> 50 lines) -❌ **Deep nesting** (> 3 levels) -❌ **Magic numbers** (use named constants) -❌ **Mutable defaults** (`def func(items=[]):` ❌) -❌ **Global state** -❌ **Commented-out code** (delete it) -❌ **Unclear variable names** (`x`, `tmp`, `data`) - -## Security - -### Command Injection - -```python -# Good - list arguments -subprocess.run(["git", "commit", "-m", user_message], ...) - -# Bad - shell=True with user input -subprocess.run(f"git commit -m '{user_message}'", shell=True) # ❌ -``` - -### Path Traversal - -```python -# Validate paths -file_path = os.path.join(work_dir, filename) -if not file_path.startswith(work_dir): - raise ValueError("Invalid file path") -``` diff --git a/.claude/skills/coding-standards/documentation.md b/.claude/skills/coding-standards/documentation.md deleted file mode 100644 index cc3ca08f..00000000 --- a/.claude/skills/coding-standards/documentation.md +++ /dev/null @@ -1,223 +0,0 @@ -# documentation.md - -## Module Docstrings -```python -"""One-line module summary.""" -``` - -## Function Docstrings -```python -def tag(tag_name: str, verbose: bool) -> None: - """Tags the latest commit with the given tag_name.""" - ... -``` - -## Comments -```python -# Explain why, not what -run_command(["git", "init", "--initial-branch=main"], verbose) # Force main as default - -# TODO(username): Description of future work -``` - -## Function Docstrings - -### Simple Functions -One-line docstring for obvious functions: - -```python -def tag(tag_name: str, verbose: bool) -> None: - """Tags the latest commit with the given tag_name.""" - run_command(["git", "tag", tag_name], verbose) - -def init(verbose: bool) -> None: - """Initializes the current folder as a Git repository.""" - run_command(["git", "init", "--initial-branch=main"], verbose) -``` - -### Complex Functions -Multi-line docstring with details: - -```python -def merge(target_branch: str, ff: bool, verbose: bool) -> None: - """Merges the current branch with the target one. - - Forcefully sets --no-edit to avoid requiring the student to enter the commit - message. - """ - if ff: - run_command(["git", "merge", target_branch, "--no-edit"], verbose) - else: - run_command(["git", "merge", target_branch, "--no-edit", "--no-ff"], verbose) -``` - -**Format:** -1. Summary line (< 80 chars) -2. Blank line -3. Additional details, notes, warnings - -### With Parameters - -For very complex functions, document parameters: - -```python -def run( - command: List[str], - verbose: bool, - env: Dict[str, str] = {}, - exit_on_error: bool = False, -) -> CommandResult: - """Runs the given command, logging the output if verbose is True. - - Args: - command: The command and arguments to run - verbose: Whether to print output to console - env: Additional environment variables - exit_on_error: Exit program on command failure - - Returns: - CommandResult with stdout, returncode, and is_success() - """ -``` - -**Note:** Parameters are already typed in signature, so docstring parameters are optional unless behavior is non-obvious. - -## Class Docstrings - -```python -class GitAutograderTestLoader: - """Test runner factory for exercise validation. - - Provides context manager for setting up isolated test environments - with repo-smith integration. - """ - - def __init__(self, exercise_name: str, grade_func: Callable) -> None: - """Initialize test loader. - - Args: - exercise_name: Name of the exercise being tested - grade_func: Verification function from verify.py - """ - self.exercise_name = exercise_name - self.grade_func = grade_func -``` - -## Comments - -### When to Use - -**Good reasons:** -- Explain **why** code exists -- Document workarounds or non-obvious choices -- Warn about edge cases -- Mark TODOs - -```python -# Force --initial-branch to ensure 'main' is used instead of system default -run_command(["git", "init", "--initial-branch=main"], verbose) - -# TODO(woojiahao): Maybe these should be built from a class like builder -def commit(message: str, verbose: bool) -> None: - ... - -# Avoid interactive prompts in automated exercises -run_command(["git", "merge", target_branch, "--no-edit"], verbose) -``` - -**Bad reasons:** -- Restating what code does -- Obvious statements -- Commented-out code (delete it instead) - -```python -# Bad - obvious from code -# Loop through files -for file in files: - add(file) - -# Bad - redundant -i = i + 1 # Increment i -``` - -### Comment Style - -```python -# Single line comment - space after # - -# Multi-line comments should be split -# across multiple lines, each starting -# with # and a space. - -# For long explanations, consider if a docstring -# or better variable names would be clearer. -``` - -### TODO Comments - -Format: `# TODO(username): Description` - -```python -# TODO(woojiahao): Reconsider if this should be inlined within repo-smith -def create_start_tag() -> None: - ... -``` - -## README Files - -Every exercise should have a README.md: - -```markdown -# Exercise Name - -Brief description of what the exercise teaches. - -## Objectives - -- Learn concept A -- Practice skill B - -## Instructions - -1. Step one -2. Step two - -## Verification - -Describe what will be checked. -``` - -## Constants Documentation - -Document error message constants with context: - -```python -# Error messages -NO_ADD = "There are no new grocery list items added to the shopping list." -NO_REMOVE = "There are no grocery list items removed from the shopping list." -SHOPPING_LIST_FILE_MISSING = "The shopping-list.txt file should not be deleted." -ADD_NOT_COMMITTED = ( - "New grocery list items added to shopping-list.txt are not committed." -) -``` - -## Documentation Best Practices - -1. **Keep it concise** - Users read code more than docs -2. **Update with code** - Outdated docs are worse than no docs -3. **Prefer clarity** - Good names > comments > docstrings -4. **Document the why** - The code shows the "what" and "how" -5. **Test examples** - Code in docstrings should work - -## What NOT to Document - -```python -# Don't document obvious type conversions -name = str(raw_name) # Bad: convert to string - -# Don't document standard library usage -files = os.listdir(path) # Bad: get list of files - -# Don't document clear variable assignments -username = get_github_username() # Bad: get username -``` diff --git a/.claude/skills/coding-standards/naming-conventions.md b/.claude/skills/coding-standards/naming-conventions.md deleted file mode 100644 index 3f242234..00000000 --- a/.claude/skills/coding-standards/naming-conventions.md +++ /dev/null @@ -1,16 +0,0 @@ -# naming-conventions.md - -## Conventions -- **Functions/Variables**: `snake_case` -- **Constants**: `UPPER_SNAKE_CASE` -- **Classes**: `PascalCase` -- **Tests**: `test_description` -- **Private**: `_name` - -## Patterns -- Actions: `create_*`, `add_*`, `remove_*` -- Queries: `get_*`, `has_*`, `is_*` -- Boolean: `is_*`, `has_*`, `can_*` - -## Common Names -- `verbose`, `exercise`, `repo`, `rs`, `test`, `output` diff --git a/.claude/skills/coding-standards/style-guide.md b/.claude/skills/coding-standards/style-guide.md deleted file mode 100644 index 762dc154..00000000 --- a/.claude/skills/coding-standards/style-guide.md +++ /dev/null @@ -1,45 +0,0 @@ -# style-guide.md - -## Formatting -- **88 char line length** -- **4 spaces** indentation (no tabs) -- **Double quotes** for strings -- 2 blank lines between functions, 1 between methods - -## Imports -Order: stdlib → third-party → local (separate with blank lines) - -```python -import os -from typing import List - -from git_autograder import GitAutograderExercise - -from exercise_utils.git import commit -``` - -## Functions -```python -# Short -def commit(message: str, verbose: bool) -> None: - ... - -# Long - one param per line -def fork_repo( - repository_name: str, - fork_name: str, - verbose: bool, -) -> None: - ... -``` - -## Comments -- Explain **why**, not what -- `# TODO(username): Description` -- Minimize - prefer clear code - -## File Structure -1. Module docstring -2. Imports -3. Constants (UPPER_CASE) -4. Functions/Classes diff --git a/.claude/skills/coding-standards/type-hints.md b/.claude/skills/coding-standards/type-hints.md deleted file mode 100644 index 394762f0..00000000 --- a/.claude/skills/coding-standards/type-hints.md +++ /dev/null @@ -1,80 +0,0 @@ -# type-hints.md - -## Policy -- All function signatures need type hints -- Variables when type isn't obvious - -## Common Types -```python -def func(name: str, count: int, verbose: bool) -> None: - ... - -def get_data(path: Optional[str] = None) -> List[str]: - ... - -def process(items: List[str], config: Dict[str, str]) -> Tuple[int, bool]: - ... -``` - -## Forward References - -```python -from __future__ import annotations -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from git import Repo - -class GitMasteryHelper: - def __init__(self, repo: Repo, verbose: bool) -> None: - self.repo = repo -``` - -## Common Imports - -```python -from typing import ( - Any, - Callable, - Dict, - Iterator, - List, - Optional, - Set, - Tuple, - Union, -) -``` - -## When to Skip Type Hints - -### Lambda functions (optional) -```python -# OK without types -files = sorted(files, key=lambda f: f.name) -``` - -### Very short internal helpers -```python -# OK for simple one-liners -def _strip(s): - return s.strip() -``` - -### Tests with obvious types -```python -# Function signature should be typed -def test_no_answers(): - # Local variables in tests can skip types when obvious - expected = "error message" - output = test.run() -``` - -## Type Checking - -Use `mypy` for type checking: - -```bash -mypy exercise_utils/ -mypy amateur_detective/verify.py -``` diff --git a/.claude/skills/exercise-development/SKILL.md b/.claude/skills/exercise-development/SKILL.md index 0b39ace5..8940c581 100644 --- a/.claude/skills/exercise-development/SKILL.md +++ b/.claude/skills/exercise-development/SKILL.md @@ -44,20 +44,6 @@ description: Guidelines for creating and modifying Git-Mastery exercises. Use wh - Common patterns - Examples -### Common Patterns -📄 **[common-patterns.md](common-patterns.md)** - Reusable code patterns for exercises -- Repository setup patterns -- GitHub integration patterns -- Validation patterns -- File operation patterns - -### Testing Exercises -📄 **[testing-exercises.md](testing-exercises.md)** - How to test your exercises -- Running tests -- Manual testing -- Debugging test failures -- Pre-commit checklist - ## Quick Start ### Create Standard Exercise diff --git a/.claude/skills/exercise-development/common-patterns.md b/.claude/skills/exercise-development/common-patterns.md deleted file mode 100644 index 14d5f03e..00000000 --- a/.claude/skills/exercise-development/common-patterns.md +++ /dev/null @@ -1,355 +0,0 @@ -# common-patterns.md - -## Setup Examples -See [grocery_shopping/download.py](../../grocery_shopping/download.py) for local repository setup. - -See [fork_repo/download.py](../../fork_repo/download.py) for GitHub fork/clone pattern. - -## Validation Examples -See [amateur_detective/verify.py](../../amateur_detective/verify.py) for answer-based validation. - -See [grocery_shopping/verify.py](../../grocery_shopping/verify.py) for: -- File existence checks -- File content validation -- Commit validation - -See [branch_compare/verify.py](../../branch_compare/verify.py) for branch validation. - -See [tags_add/verify.py](../../tags_add/verify.py) for tag validation. - -## Key Utilities -- `exercise_utils.git` - Git commands -- `exercise_utils.github_cli` - GitHub operations -- `exercise_utils.file` - File operations -- `exercise_utils.gitmastery` - Start tag creation - -### With Tags - -```python -from exercise_utils.git import tag -from exercise_utils.gitmastery import create_start_tag - -def setup(verbose: bool = False): - init(verbose) - create_or_update_file("file.txt", "content") - add(["file.txt"], verbose) - commit("Initial commit", verbose) - - # Create Git-Mastery start tag - create_start_tag(verbose) - - # Create version tags - tag("v1.0.0", verbose) -``` - -## GitHub Integration Patterns - -### Fork and Clone - -```python -from exercise_utils.github_cli import ( - get_github_username, - has_fork, - fork_repo, - get_fork_name, - clone_repo_with_gh, -) - -def setup(verbose: bool = False): - username = get_github_username(verbose) - owner = "git-mastery" - repo_name = "exercise-base" - - # Check if fork exists - if not has_fork(repo_name, owner, username, verbose): - # Create fork - fork_repo( - f"{owner}/{repo_name}", - f"{username}-{repo_name}", - verbose, - default_branch_only=True - ) - - # Get fork name and clone - fork_name = get_fork_name(repo_name, owner, username, verbose) - clone_repo_with_gh(f"{username}/{fork_name}", verbose, name=repo_name) -``` - -### Create and Clone Repository - -```python -from exercise_utils.github_cli import create_repo, clone_repo_with_gh, delete_repo - -def setup(verbose: bool = False): - username = get_github_username(verbose) - repo_name = "my-exercise" - full_name = f"{username}/{repo_name}" - - # Clean up if exists - if has_repo(full_name, is_fork=False, verbose=verbose): - delete_repo(full_name, verbose) - - # Create new repository - create_repo(repo_name, verbose) - - # Clone it - clone_repo_with_gh(full_name, verbose, name=repo_name) - - # Navigate and setup - os.chdir(repo_name) - create_or_update_file("README.md", "# My Exercise") - add(["README.md"], verbose) - commit("Initial commit", verbose) -``` - -### Push to Remote - -```python -from exercise_utils.git import add_remote, push - -def setup(verbose: bool = False): - username = get_github_username(verbose) - repo_name = "my-repo" - - # Setup local repo - init(verbose) - create_or_update_file("README.md", "# Project") - add(["README.md"], verbose) - commit("Initial commit", verbose) - - # Add remote and push - remote_url = f"https://github.com/{username}/{repo_name}.git" - add_remote("origin", remote_url, verbose) - push("origin", "main", verbose) -``` - -## Validation Patterns - -### Answer-Based Validation - -```python -from git_autograder import GitAutograderExercise, GitAutograderOutput, GitAutograderStatus -from git_autograder.answers.rules import HasExactValueRule, NotEmptyRule - -QUESTION_ONE = "What is the answer?" -QUESTION_TWO = "What is another answer?" - -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - # Validate answers - ( - exercise.answers - .add_validation(QUESTION_ONE, NotEmptyRule()) - .add_validation(QUESTION_ONE, HasExactValueRule("correct_answer")) - .add_validation(QUESTION_TWO, NotEmptyRule()) - .add_validation(QUESTION_TWO, HasExactValueRule("another_answer")) - .validate() - ) - - return exercise.to_output( - ["Congratulations! You got it right!"], - GitAutograderStatus.SUCCESSFUL - ) -``` - -### File Existence Check - -```python -import os - -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - repo_path = exercise.exercise_path - repo_name = exercise.config.exercise_repo.repo_name - work_dir = os.path.join(repo_path, repo_name) - - file_path = os.path.join(work_dir, "required-file.txt") - if not os.path.exists(file_path): - raise exercise.wrong_answer(["The required-file.txt is missing"]) - - # File exists, continue validation - return exercise.to_output(["Success!"], GitAutograderStatus.SUCCESSFUL) -``` - -### File Content Validation - -```python -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - work_dir = os.path.join( - exercise.exercise_path, - exercise.config.exercise_repo.repo_name - ) - - file_path = os.path.join(work_dir, "data.txt") - with open(file_path, "r") as f: - content = f.read() - - # Parse content - items = {line.strip() for line in content.splitlines() if line.strip()} - - # Validate - if "required_item" not in items: - raise exercise.wrong_answer(["Missing required item"]) - - return exercise.to_output(["Success!"], GitAutograderStatus.SUCCESSFUL) -``` - -### Commit Validation - -```python -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - main_branch = exercise.repo.branches.branch("main") - - # Check commits exist - if not main_branch.user_commits: - raise exercise.wrong_answer(["No commits found"]) - - # Check specific file in last commit - latest_commit = main_branch.latest_user_commit.commit - if "file.txt" not in latest_commit.tree: - raise exercise.wrong_answer(["file.txt not in latest commit"]) - - # Read file from commit - file_blob = latest_commit.tree / "file.txt" - content = file_blob.data_stream.read().decode() - - if "expected_content" not in content: - raise exercise.wrong_answer(["File doesn't contain expected content"]) - - return exercise.to_output(["Success!"], GitAutograderStatus.SUCCESSFUL) -``` - -### Branch Validation - -```python -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - # Check branch exists - if not exercise.repo.branches.has_branch("feature"): - raise exercise.wrong_answer(["Branch 'feature' doesn't exist"]) - - feature_branch = exercise.repo.branches.branch("feature") - main_branch = exercise.repo.branches.branch("main") - - # Check commits on branch - if not feature_branch.user_commits: - raise exercise.wrong_answer(["No commits on feature branch"]) - - # Check if merged - feature_commits = {c.commit.hexsha for c in feature_branch.user_commits} - main_commits = {c.commit.hexsha for c in main_branch.user_commits} - - if not feature_commits.issubset(main_commits): - raise exercise.wrong_answer(["Feature branch not merged into main"]) - - return exercise.to_output(["Success!"], GitAutograderStatus.SUCCESSFUL) -``` - -### Tag Validation - -```python -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - # Check tag exists - tags = {tag.name for tag in exercise.repo.repo.tags} - if "v1.0.0" not in tags: - raise exercise.wrong_answer(["Tag 'v1.0.0' not found"]) - - # Get tag - tag = exercise.repo.repo.tags["v1.0.0"] - - # Check tag points to correct commit - expected_commit = exercise.repo.branches.branch("main").latest_user_commit - if tag.commit.hexsha != expected_commit.commit.hexsha: - raise exercise.wrong_answer(["Tag on wrong commit"]) - - return exercise.to_output(["Success!"], GitAutograderStatus.SUCCESSFUL) -``` - -## File Operation Patterns - -### Multi-line Content - -```python -from exercise_utils.file import create_or_update_file - -def setup(verbose: bool = False): - create_or_update_file( - "shopping-list.txt", - """ - - Milk - - Eggs - - Bread - - Apples - """ - ) - # Content is auto-dedented -``` - -### Nested Directories - -```python -def setup(verbose: bool = False): - # Directories created automatically - create_or_update_file("src/utils/helper.py", "def helper(): pass") - create_or_update_file("tests/test_helper.py", "def test(): pass") -``` - -### Append Pattern - -```python -from exercise_utils.file import append_to_file - -def setup(verbose: bool = False): - create_or_update_file("log.txt", "Initial entry\n") - append_to_file("log.txt", "Second entry\n") - append_to_file("log.txt", "Third entry\n") -``` - -## Error Handling Patterns - -### Multiple Error Messages - -```python -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - comments = [] - - if not condition_1: - comments.append("Error 1") - - if not condition_2: - comments.append("Error 2") - - if comments: - raise exercise.wrong_answer(comments) - - return exercise.to_output(["Success!"], GitAutograderStatus.SUCCESSFUL) -``` - -### Early Returns - -```python -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - # Check critical requirement first - if not os.path.exists(required_file): - raise exercise.wrong_answer(["Critical file missing"]) - - # Continue with other checks - if not condition: - raise exercise.wrong_answer(["Condition not met"]) - - return exercise.to_output(["Success!"], GitAutograderStatus.SUCCESSFUL) -``` - -## Constants Pattern - -```python -# Define at module level -QUESTION_ONE = "What is the answer?" -ERROR_FILE_MISSING = "The required file is missing" -ERROR_NOT_COMMITTED = "Changes not committed" -SUCCESS_MESSAGE = "Great work!" - -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - # Use constants - if not file_exists: - raise exercise.wrong_answer([ERROR_FILE_MISSING]) - - return exercise.to_output([SUCCESS_MESSAGE], GitAutograderStatus.SUCCESSFUL) -``` diff --git a/.claude/skills/exercise-development/testing-exercises.md b/.claude/skills/exercise-development/testing-exercises.md deleted file mode 100644 index 3029740b..00000000 --- a/.claude/skills/exercise-development/testing-exercises.md +++ /dev/null @@ -1,293 +0,0 @@ -# testing-exercises.md - -## Examples -See [amateur_detective/test_verify.py](../../amateur_detective/test_verify.py) - Answer-based exercise tests. - -See [grocery_shopping/test_verify.py](../../grocery_shopping/test_verify.py) - Repository state tests with base fixture. - -See [view_commits/test_verify.py](../../view_commits/test_verify.py) - Multiple question exercise. - -## Minimum Tests -1. Success case -2. No action -3. Wrong approach -4. Each error message - -## Running -```bash -pytest exercise_name/ -``` - -## Answer-Based Exercise Tests - -```python -from git_autograder.answers.rules import HasExactValueRule, NotEmptyRule -from .verify import QUESTION_ONE, QUESTION_TWO - - -def test_no_answers(): - with loader.start(mock_answers={QUESTION_ONE: "", QUESTION_TWO: ""}) as (test, _): - output = test.run() - assert_output( - output, - GitAutograderStatus.UNSUCCESSFUL, - [ - NotEmptyRule.EMPTY.format(question=QUESTION_ONE), - NotEmptyRule.EMPTY.format(question=QUESTION_TWO), - ], - ) - - -def test_wrong_answers(): - with loader.start( - mock_answers={QUESTION_ONE: "wrong", QUESTION_TWO: "wrong"} - ) as (test, _): - output = test.run() - assert_output( - output, - GitAutograderStatus.UNSUCCESSFUL, - [ - HasExactValueRule.NOT_EXACT.format(question=QUESTION_ONE), - HasExactValueRule.NOT_EXACT.format(question=QUESTION_TWO), - ], - ) - - -def test_correct_answers(): - with loader.start( - mock_answers={QUESTION_ONE: "correct1", QUESTION_TWO: "correct2"} - ) as (test, _): - output = test.run() - assert_output(output, GitAutograderStatus.SUCCESSFUL) -``` - -## Repository State Exercise Tests - -### With Base Setup Fixture - -```python -from contextlib import contextmanager -from typing import Iterator, Tuple -from exercise_utils.test import GitAutograderTest, GitMasteryHelper -from repo_smith.repo_smith import RepoSmith - - -@contextmanager -def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]: - """Common setup for all tests.""" - with loader.start() as (test, rs): - # Initial repository state - rs.files.create_or_update("file.txt", "initial") - rs.git.add(["file.txt"]) - rs.git.commit(message="Initial commit") - rs.helper(GitMasteryHelper).create_start_tag() - - yield test, rs - - -def test_no_changes(): - with base_setup() as (test, rs): - output = test.run() - assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [NO_CHANGES]) - - -def test_success(): - with base_setup() as (test, rs): - rs.files.append("file.txt", "new content") - rs.git.add(["file.txt"]) - rs.git.commit(message="Add content") - - output = test.run() - assert_output(output, GitAutograderStatus.SUCCESSFUL) -``` - -## Testing File Operations - -```python -def test_file_missing(): - with base_setup() as (test, rs): - rs.files.delete("required-file.txt") - output = test.run() - assert_output( - output, - GitAutograderStatus.UNSUCCESSFUL, - ["File missing error"] - ) - - -def test_file_content_wrong(): - with base_setup() as (test, rs): - rs.files.create_or_update("file.txt", "wrong content") - output = test.run() - assert_output( - output, - GitAutograderStatus.UNSUCCESSFUL, - ["Content error"] - ) -``` - -## Testing Git Operations - -```python -def test_not_committed(): - with base_setup() as (test, rs): - rs.files.append("file.txt", "new content") - # Missing: git add and commit - output = test.run() - assert_output( - output, - GitAutograderStatus.UNSUCCESSFUL, - ["Not committed error"] - ) - - -def test_branch_missing(): - with base_setup() as (test, rs): - # Expected branch doesn't exist - output = test.run() - assert_output( - output, - GitAutograderStatus.UNSUCCESSFUL, - ["Branch missing error"] - ) - - -def test_branch_exists(): - with base_setup() as (test, rs): - rs.git.checkout("feature", create=True) - rs.files.create_or_update("feature.txt", "content") - rs.git.add(["feature.txt"]) - rs.git.commit(message="Add feature") - rs.git.checkout("main") - - output = test.run() - assert_output(output, GitAutograderStatus.SUCCESSFUL) -``` - -## Running Exercise Tests - -```bash -# Run all tests for exercise -pytest my_exercise/ - -# Run specific test -pytest my_exercise/test_verify.py::test_base - -# Verbose output -pytest -vv my_exercise/ - -# Show print statements -pytest -s my_exercise/ -``` - -See [running-tests.md](../../testing/running-tests.md) for detailed testing commands. - -## Test Coverage Requirements - -- **100% coverage** of verify.py -- **All error messages** must have tests -- **All validation paths** must be tested -- **Edge cases** should be covered - -## Common Mistakes - -### ❌ Not Testing All Errors - -```python -# Missing test for ERROR_2 -def test_only_one_error(): - # Tests ERROR_1 but not ERROR_2 - ... -``` - -### ❌ Not Using Base Setup - -```python -# Duplicated setup in every test -def test_1(): - with loader.start() as (test, rs): - rs.files.create_or_update("file.txt", "content") # Repeated - rs.git.add(["file.txt"]) # Repeated - rs.git.commit(message="Initial") # Repeated - ... -``` - -### ❌ Not Creating Start Tag - -```python -# Missing start tag for exercises that need it -def test_without_start_tag(): - with loader.start() as (test, rs): - rs.files.create_or_update("file.txt", "content") - rs.git.add(["file.txt"]) - rs.git.commit(message="Commit") - # Missing: rs.helper(GitMasteryHelper).create_start_tag() - ... -``` - -### ❌ Forgetting to Stage Files - -```python -def test_commit_without_staging(): - with loader.start() as (test, rs): - rs.files.create_or_update("file.txt", "content") - # Missing: rs.git.add(["file.txt"]) - rs.git.commit(message="Commit") # This will fail! -``` - -## Debugging Test Failures - -### Print Repository State - -```python -def test_debug(): - with loader.start() as (test, rs): - rs.files.create_or_update("file.txt", "content") - - # Debug output (run with: pytest -s) - import os - print(f"Files: {os.listdir('.')}") - print(f"Content: {open('file.txt').read()}") - - output = test.run() - print(f"Status: {output.status}") - print(f"Comments: {output.comments}") -``` - -### Use Debugger - -```python -def test_with_debugger(): - with loader.start() as (test, rs): - import pdb; pdb.set_trace() - # Step through test - output = test.run() -``` - -## Test Documentation - -Document test purpose with docstrings: - -```python -def test_no_changes(): - """Test that exercise fails when student makes no changes.""" - with base_setup() as (test, rs): - output = test.run() - assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [NO_CHANGES]) - - -def test_partial_completion(): - """Test that exercise fails when only some requirements are met.""" - with base_setup() as (test, rs): - rs.files.append("file.txt", "partial") - # Missing commit step - output = test.run() - assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [NOT_COMMITTED]) -``` - -## Complete Test File Example - -See existing exercises for complete examples: -- [amateur_detective/test_verify.py](../../amateur_detective/test_verify.py) - Answer-based exercise -- [grocery_shopping/test_verify.py](../../grocery_shopping/test_verify.py) - Repository state exercise with fixture -- [view_commits/test_verify.py](../../view_commits/test_verify.py) - Multiple question exercise diff --git a/.claude/skills/project-overview/SKILL.md b/.claude/skills/project-overview/SKILL.md index 04ef1f86..647e94cb 100644 --- a/.claude/skills/project-overview/SKILL.md +++ b/.claude/skills/project-overview/SKILL.md @@ -1,153 +1,60 @@ --- name: project-overview -description: High-level overview of the Git-Mastery exercises repository. Use when first learning about the project or need quick orientation. +description: Overview of Git-Mastery exercises repository. Use when first learning about the project. user-invocable: false --- -# Git-Mastery Exercises Repository +# Git-Mastery Exercises ## Overview -This repository contains 40+ modular, self-contained Git exercises designed to teach specific Git concepts through hands-on practice with automated validation. +40+ modular Git exercises with automated validation using pytest and git-autograder. -## Repository Purpose -- **Education**: Teach Git concepts through practical, isolated exercises -- **Validation**: Automated testing of student solutions using pytest and git-autograder -- **Modularity**: Each exercise is completely self-contained and independent -- **Consistency**: Shared utilities ensure uniform patterns across all exercises +## Exercise Types -## Core Architecture - -### 1. Exercise Types - -#### Standard Exercises (40+ directories) -**Location**: Root directory (e.g., `amateur_detective/`, `branch_bender/`, `conflict_mediator/`) - -**Purpose**: Guided exercises with specific objectives, instructions, and automated validation. - -**Structure**: +### Standard Exercises (40+ directories) ``` / -├── __init__.py # Python package marker -├── download.py # Setup logic - creates exercise repo state -├── verify.py # Validation logic with verify() function -├── test_verify.py # Pytest tests for verification logic -├── README.md # Student-facing exercise instructions -└── res/ # (Optional) Exercise-specific resources -``` - -**Key Characteristics**: -- Each exercise has a `download.py` that sets up the initial Git repository state -- `verify.py` contains composable validation rules using git-autograder -- `test_verify.py` uses pytest to test the verification logic -- Exercises may require Git only (`__requires_git__`) or both Git and GitHub (`__requires_github__`) -- Start tags are created using `create_start_tag()` from `exercise_utils/gitmastery.py` - -**Example Exercises by Category**: -- **History/Investigation**: `amateur_detective/`, `view_commits/`, `log_and_order/` -- **Branching**: `branch_bender/`, `branch_delete/`, `branch_rename/`, `branch_forward/`, `bonsai_tree/` -- **Working Directory**: `sensors_checkout/`, `sensors_diff/`, `sensors_reset/`, `sensors_revert/` -- **Staging**: `staging_intervention/`, `stage_fright/` -- **Merging**: `conflict_mediator/`, `merge_squash/`, `merge_undo/` -- **Remotes**: `fetch_and_pull/`, `push_over/`, `clone_repo/`, `fork_repo/`, `remote_control/` -- **Tags**: `tags_add/`, `tags_push/`, `tags_update/` - -#### Hands-On Scripts -**Location**: `hands_on/` directory - -**Purpose**: Exploratory learning scripts that demonstrate Git operations without validation. - -**Structure**: +├── download.py # Setup logic +├── verify.py # Validation with git-autograder +├── test_verify.py # Pytest tests +├── README.md # Instructions +└── res/ # Optional resources ``` -hands_on/ -├── add_files.py -├── branch_delete.py -├── create_branch.py -├── remote_branch_pull.py -└── ... (20+ standalone scripts) -``` - -**Key Characteristics**: -- Each file is a standalone Python script demonstrating a specific Git operation -- No `download.py`, `verify.py`, or `test_verify.py` files -- Users run scripts directly to observe Git behavior -- Ideal for experimentation and understanding command effects -- Scripts follow naming pattern: `.py` - -### 2. Shared Utilities - -**Location**: `exercise_utils/` directory - -**Purpose**: Provide consistent, reusable wrappers for common operations across all exercises. - -**Core Modules**: -#### `git.py` -- Wrappers for Git CLI commands -- Functions: `add()`, `commit()`, `empty_commit()`, `checkout()`, `merge()`, `tag()`, `init()`, `push()`, `clone_repo_with_git()`, `add_remote()`, `remove_remote()`, `track_remote_branch()` -- All functions accept `verbose: bool` parameter for output control - -#### `github_cli.py` -- Wrappers for GitHub CLI (gh) commands -- Functions: `fork_repo()`, `clone_repo_with_gh()`, `delete_repo()`, `create_repo()`, `get_github_username()`, `has_repo()` -- Requires GitHub CLI to be installed and authenticated - -#### `cli.py` -- General CLI execution helpers -- Functions: `run()`, `run_command()`, `run_command_no_exit()` -- Returns `CommandResult` dataclass with success status and output - -#### `gitmastery.py` -- Git-Mastery specific utilities -- `create_start_tag(verbose: bool)`: Creates exercise start tag from first commit hash -- Tag format: `git-mastery-start-` - -#### `test.py` -- Test scaffolding for exercise verification -- `GitMasteryHelper`: Helper class extending repo-smith's Helper -- `GitAutograderTestLoader`: Loads and runs exercise tests -- `GitAutograderTest`: Test wrapper for exercise grading -- `assert_output()`: Assertion helper for validation output - -#### `file.py` -- File operation helpers -- Functions for creating, updating, and appending to files consistently - -### 3. Dependencies & Environment - -**Python Version**: Python 3.8+ (primary development: Python 3.13) - -**Core Dependencies** (from `requirements.txt`): -- **git-autograder** (v6.*): Exercise validation framework -- **repo-smith**: Repository state creation and manipulation -- **pytest**: Testing framework -- **PyYAML**: YAML parsing -- **PyGithub**: GitHub API interactions -- **requests**: HTTP library - -**Developer Tools**: -- **ruff**: Python linter and formatter -- **mypy**: Static type checker - -**External Requirements**: -- Git CLI (required for all exercises) -- GitHub CLI (`gh`) - required for exercises with `__requires_github__ = True` -- Bash environment (for shell scripts) - -### 4. Development Workflow - -#### Setup Environment +**Categories**: +- History: `amateur_detective/`, `view_commits/` +- Branching: `branch_bender/`, `branch_delete/`, `bonsai_tree/` +- Working Dir: `sensors_checkout/`, `sensors_diff/`, `sensors_reset/` +- Staging: `staging_intervention/`, `stage_fright/` +- Merging: `conflict_mediator/`, `merge_squash/` +- Remotes: `fetch_and_pull/`, `push_over/`, `fork_repo/` +- Tags: `tags_add/`, `tags_push/` + +### Hands-On Scripts (`hands_on/`) +Single-file demonstrations without validation. Examples: `add_files.py`, `branch_delete.py`. + +## Shared Utilities (`exercise_utils/`) +- **git.py**: Git command wrappers +- **github_cli.py**: GitHub CLI wrappers +- **cli.py**: CLI execution helpers +- **gitmastery.py**: Start tag creation +- **test.py**: Test scaffolding +- **file.py**: File operations + +## Dependencies +- Python 3.8+ +- git-autograder 6.*, repo-smith, pytest +- ruff, mypy (dev tools) +- Git CLI, GitHub CLI (gh) + +## Common Commands ```bash -# Create virtual environment -python -m venv venv - -# Activate (Linux/macOS) -source venv/bin/activate - -# Activate (Windows) -source venv/Scripts/activate - -# Install dependencies -pip install -r requirements.txt +./setup.sh # Setup venv +./test.sh # Test exercise +pytest . -s -vv # Test all +ruff format . && ruff check . # Format & lint +./new.sh # Create exercise +``` ``` Or use the provided script: diff --git a/.claude/skills/project-overview/architecture.md b/.claude/skills/project-overview/architecture.md deleted file mode 100644 index 37a083d4..00000000 --- a/.claude/skills/project-overview/architecture.md +++ /dev/null @@ -1,127 +0,0 @@ -# Repository Architecture - -## Exercise Structure - -### Standard Exercise Anatomy -``` -/ -├── __init__.py # Python package marker -├── download.py # Setup logic - creates exercise repo state -├── verify.py # Validation logic with verify() function -├── test_verify.py # Pytest tests for verification logic -├── README.md # Student-facing exercise instructions -└── res/ # (Optional) Exercise-specific resources -``` - -**Key Files**: - -- **`download.py`**: Sets up initial Git repository state - - Required variables: `__requires_git__`, `__requires_github__` - - Required function: `def download(verbose: bool)` - - Creates start tag using `create_start_tag()` - -- **`verify.py`**: Validates student solutions - - Required function: `def verify(exercise: GitAutograderExercise) -> GitAutograderOutput` - - Uses composable validation rules from git-autograder - - Returns success/failure status with messages - -- **`test_verify.py`**: Tests the verification logic - - Uses pytest and `GitAutograderTestLoader` - - Tests multiple scenarios (no answers, wrong answers, correct answers) - - Required constant: `REPOSITORY_NAME` matching directory name - -- **`README.md`**: Student instructions - - Scenario/context - - Task description - - Progressive hints - -### Hands-On Script Anatomy -``` -hands_on/ -└── .py # Single demonstration script -``` - -**Structure**: -- Required variables: `__requires_git__`, `__requires_github__` -- Required function: `def download(verbose: bool)` -- No validation, tests, or README -- Focus on demonstration and exploration - -## Shared Utilities (`exercise_utils/`) - -### Module Overview -- **`git.py`**: Git CLI command wrappers -- **`github_cli.py`**: GitHub CLI (gh) wrappers -- **`cli.py`**: General CLI execution helpers -- **`gitmastery.py`**: Git-Mastery specific utilities (start tags) -- **`file.py`**: File operation helpers -- **`test.py`**: Test scaffolding and helpers - -### Design Patterns -All utility functions follow consistent patterns: -- Accept `verbose: bool` parameter for output control -- Use type hints for all parameters and returns -- Handle errors consistently (most exit on error) -- Return `CommandResult` or specific types - -## Exercise Categories - -### History & Investigation -- `amateur_detective/` - Uncover file changes with git status -- `view_commits/` - Exploring commit history -- `log_and_order/` - Understanding git log - -### Branching -- `branch_bender/` - Creating and managing branches -- `branch_delete/` - Deleting branches safely -- `branch_rename/` - Renaming branches -- `branch_forward/` - Moving branch pointers -- `bonsai_tree/` - Complex branch structures - -### Working Directory -- `sensors_checkout/` - Checking out commits -- `sensors_diff/` - Viewing differences -- `sensors_reset/` - Resetting changes -- `sensors_revert/` - Reverting commits - -### Staging Area -- `staging_intervention/` - Managing staged changes -- `stage_fright/` - Understanding the staging area - -### Merging -- `conflict_mediator/` - Resolving merge conflicts -- `merge_squash/` - Squash merging -- `merge_undo/` - Undoing merges - -### Remotes & Collaboration -- `fetch_and_pull/` - Fetching and pulling changes -- `push_over/` - Pushing to remotes -- `clone_repo/` - Cloning repositories -- `fork_repo/` - Forking on GitHub -- `remote_control/` - Managing remotes - -### Tags -- `tags_add/` - Creating tags -- `tags_push/` - Pushing tags -- `tags_update/` - Updating tags - -## Integration Points - -### External Tools Required -- **Git CLI**: Required for all exercises -- **GitHub CLI (gh)**: Required when `__requires_github__ = True` -- **Python 3.8+**: Runtime environment -- **Bash**: For shell scripts - -### CI/CD Integration -- GitHub Actions workflows in `.github/workflows/` -- Automated testing on PR and merge -- Linting and formatting checks - -## File Organization Principles - -1. **Self-Contained**: Each exercise has all resources in its directory -2. **No Shared State**: Exercises are completely independent -3. **Standard Naming**: Use kebab-case for directories -4. **Python Packages**: Every exercise has `__init__.py` -5. **Resource Isolation**: Exercise-specific resources in `res/` diff --git a/.claude/skills/project-overview/dependencies.md b/.claude/skills/project-overview/dependencies.md deleted file mode 100644 index 726f538b..00000000 --- a/.claude/skills/project-overview/dependencies.md +++ /dev/null @@ -1,222 +0,0 @@ -# Dependencies & Environment - -## Python Requirements - -### Version Support -- **Minimum**: Python 3.8 -- **Recommended**: Python 3.13 -- **Compatibility**: Code must work on 3.8+ - -## Core Dependencies - -From `requirements.txt`: - -### Functional Dependencies -``` -git-autograder==6.* # Exercise validation framework -PyYAML # YAML parsing -``` - -### Test Dependencies -``` -pytest # Testing framework -repo-smith # Repository state creation -``` - -### Developer Tools -``` -ruff # Linting and formatting -mypy # Static type checking -PyGithub # GitHub API interactions -requests # HTTP library -types-requests # Type stubs for requests -``` - -## External Requirements - -### Required Tools -- **Git CLI**: Version 2.0+ recommended - - Used by all exercises - - Must be in system PATH - -- **GitHub CLI (gh)**: Latest version - - Required for exercises with `__requires_github__ = True` - - Must be authenticated: `gh auth login` - -### Operating System -- **Linux**: Fully supported -- **macOS**: Fully supported -- **Windows**: Supported (use Git Bash or WSL for shell scripts) - -## Environment Setup - -### Quick Setup -```bash -# Run provided script -./setup.sh -``` - -### Manual Setup -```bash -# 1. Create virtual environment -python -m venv venv - -# 2. Activate virtual environment -# Linux/macOS: -source venv/bin/activate - -# Windows: -source venv/Scripts/activate - -# 3. Install dependencies -pip install -r requirements.txt -``` - -### Verify Installation -```bash -# Check Python version -python --version # Should be 3.8+ - -# Check Git -git --version - -# Check GitHub CLI (if needed) -gh --version -gh auth status - -# Run tests to verify setup -./test.sh amateur_detective -``` - -## Package Details - -### git-autograder (v6.*) -**Purpose**: Exercise validation framework - -**Key Components**: -- `GitAutograderExercise`: Exercise context -- `GitAutograderOutput`: Validation results -- `GitAutograderStatus`: Success/failure states -- Validation rules: `NotEmptyRule`, `HasExactValueRule`, etc. - -**Usage**: Required for all `verify.py` implementations - -### pytest -**Purpose**: Testing framework - -**Usage**: -- All exercise tests in `test_verify.py` -- Run via `./test.sh ` -- Supports fixtures, parameterization, markers - -### repo-smith -**Purpose**: Repository state creation for testing - -**Key Components**: -- `RepoSmith`: Repository manipulation -- `Helper`: Base helper class -- Specification-based repo creation - -**Usage**: Used in test setup and `exercise_utils/test.py` - -### ruff -**Purpose**: Fast Python linter and formatter - -**Usage**: -```bash -# Format code -ruff format . - -# Check for issues -ruff check . - -# Auto-fix issues -ruff check --fix . -``` - -**Configuration**: Follows Black defaults (88 char line length) - -### mypy -**Purpose**: Static type checking - -**Usage**: -```bash -# Type check specific module -mypy exercise_utils/ - -# Type check exercise -mypy amateur_detective/ -``` - -**Configuration**: Requires type hints on all functions - -## Dependency Management - -### Updating Dependencies -```bash -# Update all packages -pip install -r requirements.txt --upgrade - -# Update specific package -pip install --upgrade git-autograder -``` - -### Freezing Dependencies -```bash -# Generate locked requirements -pip freeze > requirements-lock.txt -``` - -### Checking for Issues -```bash -# Check for security vulnerabilities -pip audit - -# Check for outdated packages -pip list --outdated -``` - -## Development Environment - -### Recommended Setup -1. Python 3.13 with virtual environment -2. Git 2.40+ -3. GitHub CLI 2.0+ -4. VS Code or PyCharm -5. ruff and mypy installed globally or in venv - -### Optional Tools -- **GitHub Desktop**: For visual Git operations -- **pytest-xdist**: Parallel test execution -- **pre-commit**: Git hooks for quality checks - -## Common Issues - -### Git Not Found -```bash -# Add Git to PATH or install -# Windows: Download from git-scm.com -# Linux: apt install git or yum install git -# macOS: brew install git -``` - -### GitHub CLI Not Authenticated -```bash -# Authenticate -gh auth login - -# Follow prompts to login -``` - -### Wrong Python Version -```bash -# Use specific Python version -python3.13 -m venv venv -source venv/bin/activate -``` - -### Module Import Errors -```bash -# Reinstall dependencies -pip install -r requirements.txt --force-reinstall -``` diff --git a/.claude/skills/project-overview/quick-reference.md b/.claude/skills/project-overview/quick-reference.md deleted file mode 100644 index af303e4f..00000000 --- a/.claude/skills/project-overview/quick-reference.md +++ /dev/null @@ -1,258 +0,0 @@ -# Quick Reference - -## Essential Commands - -```bash -# Setup -./setup.sh # Create venv and install deps - -# Testing -./test.sh # Test specific exercise -pytest . -s -vv # Test all exercises -pytest /test_verify.py::test_name # Test specific function - -# Quality -ruff format . # Format code -ruff check . # Check linting -ruff check --fix . # Auto-fix issues -mypy / # Type checking - -# Development -./new.sh # Create new exercise -./test-download.sh # Test all downloads -``` - -## Directory Structure Quick Map - -``` -exercises/ -├── amateur_detective/ # Example standard exercise -│ ├── __init__.py -│ ├── download.py # Setup logic -│ ├── verify.py # Validation -│ ├── test_verify.py # Tests -│ ├── README.md # Instructions -│ └── res/ # Resources -│ -├── hands_on/ # Demonstration scripts -│ ├── add_files.py -│ ├── branch_delete.py -│ └── ... -│ -├── exercise_utils/ # Shared utilities -│ ├── git.py # Git wrappers -│ ├── github_cli.py # GitHub wrappers -│ ├── cli.py # CLI helpers -│ ├── gitmastery.py # Start tags -│ ├── file.py # File ops -│ └── test.py # Test helpers -│ -├── .claude/skills/ # AI documentation -├── .github/ # CI/CD -├── setup.sh # Setup script -├── test.sh # Test script -├── new.sh # Scaffolding script -├── requirements.txt # Dependencies -└── CLAUDE.md # AI context entry -``` - -## File Purposes - -| File | Purpose | Required | -|------|---------|----------| -| `__init__.py` | Python package marker | Yes (exercises) | -| `download.py` | Exercise setup | Yes (exercises) | -| `verify.py` | Solution validation | Yes (exercises) | -| `test_verify.py` | Tests for validation | Yes (exercises) | -| `README.md` | Student instructions | Yes (exercises) | -| `res/` | Exercise resources | Optional | - -## Common Import Patterns - -```python -# Git operations -from exercise_utils.git import init, add, commit, checkout, merge - -# GitHub operations -from exercise_utils.github_cli import fork_repo, get_github_username - -# File operations -from exercise_utils.file import create_or_update_file, append_to_file - -# Git-Mastery specific -from exercise_utils.gitmastery import create_start_tag - -# Testing -from exercise_utils.test import GitAutograderTestLoader, assert_output - -# Validation -from git_autograder import GitAutograderExercise, GitAutograderOutput -from git_autograder.answers.rules import NotEmptyRule, HasExactValueRule -``` - -## Required Variables - -### In download.py or hands_on scripts -```python -__requires_git__ = True # Always set this -__requires_github__ = False # True if uses GitHub CLI -``` - -### In test_verify.py -```python -REPOSITORY_NAME = "exercise-name" # Must match directory name exactly -``` - -## Validation Patterns - -### Basic Answer Validation -```python -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - ( - exercise.answers - .add_validation(QUESTION, NotEmptyRule()) - .add_validation(QUESTION, HasExactValueRule("expected")) - .validate() - ) - return exercise.to_output( - ["Success message"], - GitAutograderStatus.SUCCESSFUL - ) -``` - -### Test Pattern -```python -def test_correct(): - with loader.start(mock_answers={QUESTION: "answer"}) as (test, _): - output = test.run() - assert_output(output, GitAutograderStatus.SUCCESSFUL, ["Success"]) -``` - -## Key Conventions - -### Naming -- **Directories**: kebab-case (`branch-forward`, `amateur-detective`) -- **Functions**: snake_case (`verify`, `download`, `create_start_tag`) -- **Constants**: UPPER_SNAKE_CASE (`QUESTION_ONE`, `REPOSITORY_NAME`) -- **Classes**: PascalCase (`GitMasteryHelper`, `ExerciseValidator`) - -### Exercise Setup Pattern -```python -def setup(verbose: bool = False): - # 1. Create files - create_or_update_file("file.txt", "content") - - # 2. Initialize Git - init(verbose) - add(["file.txt"], verbose) - commit("Initial commit", verbose) - - # 3. Make changes for exercise - # ... exercise-specific setup ... - - # 4. Create start tag (ALWAYS LAST) - create_start_tag(verbose) -``` - -### Hands-On Script Pattern -```python -def download(verbose: bool): - # 1. Setup demo environment - os.makedirs("demo-repo") - os.chdir("demo-repo") - - # 2. Demonstrate operation - init(verbose) - # ... demo-specific operations ... - - # 3. Guide user - if verbose: - print("\n✓ Demo complete") - print("Try running: git log") -``` - -## Common Pitfalls - -### ❌ Don't Do -```python -# Raw subprocess calls -import subprocess -subprocess.run(["git", "add", "file.txt"]) - -# Hardcoded values -exercise.answers.add_validation("Which file?", ...) - -# Missing type hints -def verify(exercise): - pass - -# Wrong directory name -REPOSITORY_NAME = "amateur_detective" # Should be "amateur-detective" -``` - -### ✅ Do This -```python -# Use utility wrappers -from exercise_utils.git import add -add(["file.txt"], verbose) - -# Use constants -QUESTION_ONE = "Which file?" -exercise.answers.add_validation(QUESTION_ONE, ...) - -# Include type hints -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - pass - -# Match directory name exactly -REPOSITORY_NAME = "amateur-detective" -``` - -## Debugging Quick Tips - -```bash -# Verbose test output -pytest /test_verify.py -s -vv - -# Run single test -pytest /test_verify.py::test_name -s -vv - -# Python debugger -pytest /test_verify.py --pdb - -# Check imports -python -c "from exercise_utils.git import add; print('OK')" - -# Test download manually -python -c "from .download import download; download(True)" - -# Check Git/GitHub CLI -git --version -gh auth status -``` - -## Status Codes - -```python -from git_autograder import GitAutograderStatus - -GitAutograderStatus.SUCCESSFUL # All validations passed -GitAutograderStatus.UNSUCCESSFUL # Some validations failed -``` - -## Useful Links - -- **Project**: [README.md](../../../README.md) -- **Contributing**: [.github/CONTRIBUTING.md](../../../.github/CONTRIBUTING.md) -- **Developer Docs**: https://git-mastery.github.io/developers -- **Exercise Directory**: https://git-mastery.github.io/exercises - -## Need More Help? - -- **Architecture details**: [architecture.md](architecture.md) -- **Dependencies & setup**: [dependencies.md](dependencies.md) -- **Development workflows**: [workflows.md](workflows.md) -- **Exercise development**: [../exercise-development/SKILL.md](../exercise-development/SKILL.md) -- **Utility reference**: [../exercise-utils/SKILL.md](../exercise-utils/SKILL.md) -- **Coding standards**: [../coding-standards/SKILL.md](../coding-standards/SKILL.md) -- **Testing guide**: [../testing/SKILL.md](../testing/SKILL.md) diff --git a/.claude/skills/project-overview/workflows.md b/.claude/skills/project-overview/workflows.md deleted file mode 100644 index b3db9703..00000000 --- a/.claude/skills/project-overview/workflows.md +++ /dev/null @@ -1,349 +0,0 @@ -# Development Workflows - -## Common Tasks - -### Setup Development Environment -```bash -# One-time setup -./setup.sh - -# This creates venv and installs dependencies -# Equivalent to: -python -m venv venv -source venv/bin/activate # or venv/Scripts/activate on Windows -pip install -r requirements.txt -``` - -### Test an Exercise -```bash -# Test specific exercise -./test.sh - -# Example -./test.sh amateur_detective - -# Runs: python -m pytest /test_verify.py -s -vv -``` - -### Create New Exercise -```bash -# Use interactive scaffolding -./new.sh - -# Prompts for: -# 1. Exercise name (kebab-case) -# 2. Tags (space-separated) -# 3. Exercise configuration - -# Generates complete directory structure -``` - -### Run All Tests -```bash -# Test all exercises -pytest . -s -vv - -# Test specific test function -pytest amateur_detective/test_verify.py::test_correct_answers -s -vv - -# Stop at first failure -pytest . -x -s -vv -``` - -### Test Downloads -```bash -# Test all download.py scripts -./test-download.sh - -# Verifies each download script executes without errors -``` - -### Code Quality Checks -```bash -# Format code -ruff format . - -# Check linting -ruff check . - -# Auto-fix issues -ruff check --fix . - -# Type checking -mypy exercise_utils/ -mypy / - -# Run all checks -ruff format . && ruff check . && mypy . -``` - -## Development Scripts - -### `setup.sh` -**Purpose**: Initial environment setup - -**What it does**: -- Creates Python virtual environment -- Activates the venv -- Installs all dependencies from requirements.txt - -**When to use**: First time setup or after clean clone - -### `test.sh ` -**Purpose**: Test specific exercise - -**What it does**: -- Activates virtual environment -- Runs pytest on exercise's test_verify.py -- Shows verbose output (-s -vv flags) - -**Example**: -```bash -./test.sh amateur_detective -./test.sh branch_bender -``` - -### `test-download.sh` -**Purpose**: Validate all download scripts - -**What it does**: -- Tests each exercise's download.py -- Verifies scripts execute without errors -- Doesn't validate correctness, just execution - -**When to use**: Before committing changes to download scripts - -### `new.sh` -**Purpose**: Scaffold new exercise - -**What it does**: -- Interactive prompts for exercise details -- Creates directory structure -- Generates template files: - - `__init__.py` - - `download.py` - - `verify.py` - - `test_verify.py` - - `README.md` - - `res/` directory - -**When to use**: Starting a new exercise implementation - -### `dump.sh` -**Purpose**: Development utility - -**What it does**: Repository-specific utility (check source for details) - -## Exercise Development Workflow - -### Standard Exercise -```bash -# 1. Create exercise discussion issue on GitHub -# 2. Wait for approval -# 3. Request remote repository if needed - -# 4. Generate scaffolding -./new.sh -# Enter: exercise-name, tags, config - -# 5. Implement download.py -# - Setup repository state -# - Create start tag - -# 6. Write README.md -# - Scenario and task -# - Progressive hints - -# 7. Implement verify.py -# - Add validation rules -# - Return appropriate status - -# 8. Write test_verify.py -# - Test all scenarios -# - No answers, wrong answers, correct answers - -# 9. Test locally -./test.sh exercise-name - -# 10. Verify quality -ruff format . -ruff check . -mypy exercise-name/ - -# 11. Commit and push -git add exercise-name/ -git commit -m "Add exercise-name exercise" -git push -``` - -### Hands-On Script -```bash -# 1. Create file -touch hands_on/my_demo.py - -# 2. Implement -# - Add __requires_git__ and __requires_github__ -# - Implement download(verbose: bool) -# - Add helpful print statements - -# 3. Test manually -python hands_on/my_demo.py - -# 4. Verify created state -cd demo-repo # or whatever it creates -git status -git log - -# 5. Commit -git add hands_on/my_demo.py -git commit -m "Add my_demo hands-on script" -git push -``` - -## Testing Workflow - -### Test-Driven Development -```bash -# 1. Write test_verify.py first -# - Define expected behavior -# - Test failure cases - -# 2. Run tests (they fail) -./test.sh exercise-name - -# 3. Implement verify.py -# - Make tests pass one by one - -# 4. Refactor -# - Clean up code -# - Tests still pass -``` - -### Debugging Tests -```bash -# Run single test with full output -pytest exercise/test_verify.py::test_name -s -vv - -# Add print statements in test -def test_something(): - output = test.run() - print(f"Status: {output.status}") - print(f"Messages: {output.messages}") - assert_output(...) - -# Run with Python debugger -pytest exercise/test_verify.py --pdb -``` - -## Git Workflow - -### Branch Strategy -```bash -# Create feature branch -git checkout -b feature/new-exercise - -# Make changes -# ... implement exercise ... - -# Commit frequently -git add exercise-name/ -git commit -m "Add download logic" - -git add exercise-name/ -git commit -m "Add verification rules" - -# Push and create PR -git push origin feature/new-exercise -``` - -### Commit Messages -```bash -# Good commit messages -git commit -m "Add amateur_detective exercise" -git commit -m "Fix validation in branch_bender" -git commit -m "Update exercise_utils git module docs" - -# Bad commit messages -git commit -m "updates" -git commit -m "fix" -git commit -m "wip" -``` - -## CI/CD Workflow - -### What Runs on PR -1. All pytest tests -2. Ruff linting -3. Ruff formatting check -4. Type checking with mypy - -### Pre-Push Checklist -```bash -# Run same checks as CI locally -./test.sh # Tests pass -ruff format . # Code formatted -ruff check . # No lint errors -mypy / # Type hints valid -``` - -## Troubleshooting Workflows - -### Tests Failing Locally -```bash -# 1. Check Python environment -which python -python --version - -# 2. Reinstall dependencies -pip install -r requirements.txt --force-reinstall - -# 3. Run single test with verbose output -pytest exercise/test_verify.py::test_name -s -vv - -# 4. Check for import errors -python -c "from exercise_utils.git import add; print('OK')" -``` - -### Download Script Not Working -```bash -# 1. Test manually with verbose -python -c "from exercise.download import download; download(True)" - -# 2. Check Git/GitHub CLI -git --version -gh auth status - -# 3. Check working directory -pwd -ls -la -``` - -### Can't Run Scripts -```bash -# Make scripts executable (Linux/macOS) -chmod +x setup.sh test.sh new.sh - -# Use sh/bash explicitly -bash setup.sh -bash test.sh amateur_detective -``` - -## Performance Tips - -### Faster Testing -```bash -# Test only specific file -pytest amateur_detective/test_verify.py - -# Run tests in parallel (requires pytest-xdist) -pytest -n auto - -# Skip slow tests (if marked) -pytest -m "not slow" -``` - -### Faster Development -- Use pytest watch mode: `pytest-watch` -- Keep virtual environment activated -- Use IDE integration for testing -- Cache pip packages: `pip install --cache-dir=.cache` diff --git a/.claude/skills/testing/SKILL.md b/.claude/skills/testing/SKILL.md deleted file mode 100644 index 6f3104ca..00000000 --- a/.claude/skills/testing/SKILL.md +++ /dev/null @@ -1,151 +0,0 @@ ---- -name: testing -description: Testing guidelines for exercises. Use when writing tests or debugging test failures. ---- - -# Testing - -## Examples -See [amateur_detective/test_verify.py](../../amateur_detective/test_verify.py) - Answer-based tests. -See [grocery_shopping/test_verify.py](../../grocery_shopping/test_verify.py) - Repository state tests with fixtures. - -## Basic Structure -```python -from exercise_utils.test import GitAutograderTestLoader, assert_output -from git_autograder import GitAutograderStatus -from .verify import verify, QUESTION_ONE, ERROR_MESSAGE - -loader = GitAutograderTestLoader("exercise-name", verify) - -def test_success(): - with loader.start(mock_answers={QUESTION_ONE: "answer"}) as (test, rs): - output = test.run() - assert_output(output, GitAutograderStatus.SUCCESSFUL) -``` - -## Required Test Scenarios - -**Answer-Based:** -1. No answers (all empty) -2. Partial answers (some missing) -3. Wrong answers (test each question) -4. Valid answers (all correct) - -**Repository State:** -1. No changes made -2. Partial completion (e.g., only added, not removed) -3. Wrong approach (e.g., changes not committed) -4. File missing/deleted -5. Valid submission - -## Common Patterns - -### Base Fixture (Repository State) -```python -from contextlib import contextmanager - -@contextmanager -def base_setup(): - with loader.start() as (test, rs): - rs.files.create_or_update("file.txt", "content") - rs.git.add(["file.txt"]) - rs.git.commit(message="Initial") - rs.helper(GitMasteryHelper).create_start_tag() - yield test, rs -``` - -### Mock Answers (Answer-Based) -```python -def test_no_answers(): - with loader.start(mock_answers={}) as (test, _): - output = test.run() - assert_output(output, GitAutograderStatus.UNSUCCESSFUL) - -def test_wrong_answer(): - with loader.start(mock_answers={Q1: "wrong"}) as (test, _): - output = test.run() - assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [Q1_ERROR]) -``` - -## RepoSmith (rs) APIs - -**File Operations:** -- `rs.files.create_or_update(path, content)` -- `rs.files.append(path, content)` -- `rs.files.delete(path)` -- `rs.files.delete_lines(path, pattern)` - -**Git Operations:** -- `rs.git.add(files)`, `rs.git.commit(message="msg")` -- `rs.git.checkout(branch, create=True)` -- `rs.git.merge(branch)`, `rs.git.tag(name)` - -**Helper:** -- `rs.helper(GitMasteryHelper).create_start_tag()` - -## Assertions -```python -# Success -assert_output(output, GitAutograderStatus.SUCCESSFUL, ["Congratulations!"]) - -# Failure with specific errors -assert_output( - output, - GitAutograderStatus.UNSUCCESSFUL, - [ERROR_NO_ADD, ERROR_NO_REMOVE] -) -``` - -## Running Tests -```bash -pytest exercise_name/ # Run exercise -pytest exercise/test_verify.py::test_x # Specific test -pytest -s -vv # Verbose with output -pytest -x # Stop on first fail -pytest --lf # Run last failed -pytest -l # Show locals on fail -``` - -## Debug Techniques -```python -import pdb; pdb.set_trace() # Breakpoint -print(f"Files: {os.listdir('.')}") # Check state -print(f"Output: {output.comments}") # Check output -``` - -### Test Independence -Each test should be independent - no shared state - -### Clear Naming -```python -def test_no_answers_provided(): - pass - -def test_all_correct_answers(): - pass -``` - -### Use Constants -```python -# Import from verify module -from .verify import QUESTION_ONE, QUESTION_TWO - -# Don't hardcode -mock_answers = {QUESTION_ONE: "answer"} # ✓ -mock_answers = {"Which file?": "answer"} # ✗ -``` - -## Pre-Test Checklist - -- ✓ All scenarios covered -- ✓ `REPOSITORY_NAME` matches directory -- ✓ Constants imported from verify.py -- ✓ Tests pass: `./test.sh ` -- ✓ Docstrings added -- ✓ No hardcoded values - -## Related Skills - -- **[exercise-development](../exercise-development/SKILL.md)** - Creating testable exercises -- **[exercise-utils](../exercise-utils/SKILL.md)** - Using test utilities -- **[coding-standards](../coding-standards/SKILL.md)** - Test code style diff --git a/AGENT.md b/AGENT.md index 91ab4ab2..52f358ce 100644 --- a/AGENT.md +++ b/AGENT.md @@ -1,95 +1,84 @@ -# Git-Mastery Exercises - AI Assistant Context +# Git-Mastery Exercises - AI Agent Guide -This repository contains modular Git exercises with automated validation. Each exercise teaches specific Git concepts through hands-on practice. +This repository uses the **Agent Skills** standard for AI documentation. All detailed instructions are in `.claude/skills/`. -## Quick Start for AI Assistants +## For AI Agents -This project uses the **Agent Skills** standard for AI documentation. Detailed instructions are in `.claude/skills/`: +**Start here**: Load `.claude/skills/` to understand the repository. -- **[project-overview](file:.claude/skills/project-overview/SKILL.md)**: Repository structure, architecture, and workflows -- **[exercise-development](file:.claude/skills/exercise-development/SKILL.md)**: Creating and modifying exercises -- **[exercise-utils](file:.claude/skills/exercise-utils/SKILL.md)**: Shared utility modules reference -- **[coding-standards](file:.claude/skills/coding-standards/SKILL.md)**: Code style and quality guidelines -- **[testing](file:.claude/skills/testing/SKILL.md)**: Testing patterns and best practices +Available skills: +- **[project-overview](file:.claude/skills/project-overview/SKILL.md)**: Repository overview, structure, dependencies +- **[exercise-development](file:.claude/skills/exercise-development/SKILL.md)**: Creating exercises (standard vs hands-on) +- **[exercise-utils](file:.claude/skills/exercise-utils/SKILL.md)**: Utility modules API reference +- **[coding-standards](file:.claude/skills/coding-standards/SKILL.md)**: Code style, naming, type hints -## Repository Structure +**What each skill tells you**: +- `project-overview`: 40+ exercises structure, exercise_utils/ modules, dependencies, common commands +- `exercise-development`: How to create standard exercises (download.py, verify.py, test_verify.py, README.md) and hands-on scripts +- `exercise-utils`: API docs for git.py, github_cli.py, cli.py, file.py, gitmastery.py, test.py modules +- `coding-standards`: ruff/mypy usage, naming conventions, type hints, imports, best practices -``` -exercises/ -├── / # 40+ self-contained exercises -│ ├── download.py # Setup logic -│ ├── verify.py # Validation logic -│ ├── test_verify.py # Pytest tests -│ ├── README.md # Student instructions -│ └── res/ # (Optional) Resources -├── hands_on/ # Exploratory scripts (no validation) -├── exercise_utils/ # Shared utilities -│ ├── git.py # Git command wrappers -│ ├── github_cli.py # GitHub CLI wrappers -│ ├── cli.py # General CLI helpers -│ ├── gitmastery.py # Git-Mastery specific helpers -│ ├── file.py # File operations -│ └── test.py # Test scaffolding -├── .claude/skills/ # AI assistant documentation -├── setup.sh # Environment setup -├── test.sh # Test runner -└── requirements.txt # Python dependencies -``` +## For Human Developers -## Key Principles +**When working with AI agents** (GitHub Copilot, Claude, etc.) **on this repository**: -1. **Use Shared Utilities**: Always use functions from `exercise_utils/` instead of raw subprocess calls -2. **Exercise Isolation**: Each exercise is completely independent and self-contained -3. **Composable Validation**: Use git-autograder's rule system for validation -4. **Comprehensive Testing**: Every exercise must have thorough test coverage -5. **Type Safety**: Use type hints for all functions +### First Time Setup +1. Ask AI to "read the skills in .claude/skills/ directory" +2. AI will understand: repo structure, coding standards, testing patterns, utility APIs -## Common Tasks +### Common Workflows -### Create New Exercise -```bash -./new.sh # Interactive scaffolding +**Creating a new exercise:** +``` +"Create a new standard exercise called 'branch-merge-conflict' that teaches handling merge conflicts. +Follow the patterns in .claude/skills/exercise-development/" ``` -### Test Exercise -```bash -./test.sh # Runs pytest with verbose output +**Fixing a test:** +``` +"Fix the failing test in amateur_detective/test_verify.py. +Check .claude/skills/testing/ for test patterns." ``` -### Setup Environment -```bash -./setup.sh # Creates venv and installs dependencies +**Using utility functions:** +``` +"Update grocery_shopping/download.py to use exercise_utils functions. +See .claude/skills/exercise-utils/ for available APIs." ``` -## Tech Stack +**Code review:** +``` +"Review this code against coding standards in .claude/skills/coding-standards/" +``` -- **Python**: 3.8+ (development on 3.13) -- **Testing**: pytest, git-autograder, repo-smith -- **Quality**: ruff (linting/formatting), mypy (type checking) -- **External**: Git CLI, GitHub CLI (gh) +### What AI Knows From Skills -## Development Workflow +After loading skills, AI understands: +- **Structure**: Each exercise has download.py, verify.py, test_verify.py, README.md +- **Patterns**: Use `exercise_utils.*` instead of subprocess, call `create_start_tag()` last +- **Testing**: Required scenarios (no answers, wrong answers, success), `loader.start()` pattern +- **Standards**: 88-char lines, snake_case, type hints, import order +- **APIs**: All functions in exercise_utils/ (git.py, github_cli.py, etc.) -1. Understand exercise requirements from issue/discussion -2. Use `./new.sh` to scaffold exercise structure -3. Implement `download.py` (setup), `verify.py` (validation), `README.md` (instructions) -4. Write comprehensive tests in `test_verify.py` -5. Run `./test.sh ` to verify -6. Ensure code passes: `ruff format . && ruff check . && mypy .` +### Tips for Best Results -## Documentation +1. **Be specific about which skill**: "Follow exercise-development skill for creating download.py" +2. **Reference example files**: Skills already point to examples like [amateur_detective/test_verify.py](amateur_detective/test_verify.py) +3. **Check standards before committing**: "Does this follow coding-standards skill?" +4. **Use for learning**: "Explain the testing patterns from .claude/skills/testing/" -- **Developer Guide**: https://git-mastery.github.io/developers -- **Exercise Directory**: https://git-mastery.github.io/exercises -- **Contributing**: [.github/CONTRIBUTING.md](.github/CONTRIBUTING.md) +## Quick Reference -## For More Details +```bash +./setup.sh # Setup environment +./new.sh # Create exercise (interactive) +./test.sh # Test one exercise +pytest . -s -vv # Test all +ruff format . && ruff check . # Format & lint +``` -Load the skills in `.claude/skills/` for comprehensive documentation on: -- Repository architecture and patterns -- Exercise development lifecycle -- Utility module API reference -- Coding standards and conventions -- Testing strategies and patterns +## Tech Stack -Each skill file follows the Agent Skills standard and provides detailed, actionable guidance for working with this repository. +- Python 3.8+, pytest, git-autograder, repo-smith +- ruff (lint/format), mypy (types) +- Git CLI, GitHub CLI (gh) From 5d862c099747f2ad4709c905ba6fc6267ded50af Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Mon, 26 Jan 2026 21:17:58 +0800 Subject: [PATCH 06/18] Update AGENT.md --- AGENT.md | 47 +++++++++++++++++------------------------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/AGENT.md b/AGENT.md index 52f358ce..c090ea3d 100644 --- a/AGENT.md +++ b/AGENT.md @@ -4,50 +4,37 @@ This repository uses the **Agent Skills** standard for AI documentation. All det ## For AI Agents -**Start here**: Load `.claude/skills/` to understand the repository. +**Workflow**: +1. Read this AGENT.md to understand available skills +2. Based on your task, load relevant skills from `.claude/skills/` +3. Reference actual code files linked in skills -Available skills: +**Available skills**: - **[project-overview](file:.claude/skills/project-overview/SKILL.md)**: Repository overview, structure, dependencies - **[exercise-development](file:.claude/skills/exercise-development/SKILL.md)**: Creating exercises (standard vs hands-on) - **[exercise-utils](file:.claude/skills/exercise-utils/SKILL.md)**: Utility modules API reference - **[coding-standards](file:.claude/skills/coding-standards/SKILL.md)**: Code style, naming, type hints -**What each skill tells you**: -- `project-overview`: 40+ exercises structure, exercise_utils/ modules, dependencies, common commands -- `exercise-development`: How to create standard exercises (download.py, verify.py, test_verify.py, README.md) and hands-on scripts -- `exercise-utils`: API docs for git.py, github_cli.py, cli.py, file.py, gitmastery.py, test.py modules -- `coding-standards`: ruff/mypy usage, naming conventions, type hints, imports, best practices +**When to load each skill**: +- Creating/modifying exercises → `exercise-development` +- Using utility functions → `exercise-utils` +- Code review/formatting → `coding-standards` +- Understanding repo structure → `project-overview` ## For Human Developers -**When working with AI agents** (GitHub Copilot, Claude, etc.) **on this repository**: +**Working with AI agents**: Simply state your task. AI will read AGENT.md, identify needed skills, and load them automatically. -### First Time Setup -1. Ask AI to "read the skills in .claude/skills/ directory" -2. AI will understand: repo structure, coding standards, testing patterns, utility APIs - -### Common Workflows - -**Creating a new exercise:** -``` -"Create a new standard exercise called 'branch-merge-conflict' that teaches handling merge conflicts. -Follow the patterns in .claude/skills/exercise-development/" +**Example tasks**: ``` +"Create a new exercise about merge conflicts" -**Fixing a test:** -``` -"Fix the failing test in amateur_detective/test_verify.py. -Check .claude/skills/testing/ for test patterns." -``` +"Fix the test in amateur_detective/test_verify.py" -**Using utility functions:** -``` -"Update grocery_shopping/download.py to use exercise_utils functions. -See .claude/skills/exercise-utils/ for available APIs." -``` +"Update grocery_shopping/download.py to use exercise_utils" + +"Review this code against our standards" -**Code review:** -``` "Review this code against coding standards in .claude/skills/coding-standards/" ``` From 43ef4e516d0c0689fbe0208c7095e2953797e09a Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Mon, 26 Jan 2026 21:32:08 +0800 Subject: [PATCH 07/18] Address review --- .claude/skills/project-overview/SKILL.md | 1 - AGENT.md | 1 - 2 files changed, 2 deletions(-) diff --git a/.claude/skills/project-overview/SKILL.md b/.claude/skills/project-overview/SKILL.md index 647e94cb..572394d8 100644 --- a/.claude/skills/project-overview/SKILL.md +++ b/.claude/skills/project-overview/SKILL.md @@ -55,7 +55,6 @@ pytest . -s -vv # Test all ruff format . && ruff check . # Format & lint ./new.sh # Create exercise ``` -``` Or use the provided script: ```bash diff --git a/AGENT.md b/AGENT.md index c090ea3d..4b6d63ff 100644 --- a/AGENT.md +++ b/AGENT.md @@ -52,7 +52,6 @@ After loading skills, AI understands: 1. **Be specific about which skill**: "Follow exercise-development skill for creating download.py" 2. **Reference example files**: Skills already point to examples like [amateur_detective/test_verify.py](amateur_detective/test_verify.py) 3. **Check standards before committing**: "Does this follow coding-standards skill?" -4. **Use for learning**: "Explain the testing patterns from .claude/skills/testing/" ## Quick Reference From 0a358c75ca8ae7097ad5cc6c4cd4d09511cc1147 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Tue, 27 Jan 2026 13:07:20 +0800 Subject: [PATCH 08/18] Address copilot comment --- .claude/skills/coding-standards/SKILL.md | 1 - .claude/skills/exercise-development/SKILL.md | 4 +-- .claude/skills/exercise-utils/SKILL.md | 1 - .claude/skills/exercise-utils/cli-module.md | 6 ++-- .claude/skills/exercise-utils/file-module.md | 2 +- .claude/skills/exercise-utils/git-module.md | 2 +- .../skills/exercise-utils/github-module.md | 13 ++++----- .../exercise-utils/gitmastery-module.md | 2 +- .claude/skills/exercise-utils/test-module.md | 8 ++--- .claude/skills/project-overview/SKILL.md | 29 +++++++++++++------ 10 files changed, 37 insertions(+), 31 deletions(-) diff --git a/.claude/skills/coding-standards/SKILL.md b/.claude/skills/coding-standards/SKILL.md index 64840e67..60c7217d 100644 --- a/.claude/skills/coding-standards/SKILL.md +++ b/.claude/skills/coding-standards/SKILL.md @@ -109,4 +109,3 @@ if count > MAX_RETRIES: - **[exercise-development](../exercise-development/SKILL.md)** - Applying standards - **[exercise-utils](../exercise-utils/SKILL.md)** - Using utilities correctly -- **[testing](../testing/SKILL.md)** - Test code standards diff --git a/.claude/skills/exercise-development/SKILL.md b/.claude/skills/exercise-development/SKILL.md index 8940c581..86920c91 100644 --- a/.claude/skills/exercise-development/SKILL.md +++ b/.claude/skills/exercise-development/SKILL.md @@ -86,7 +86,8 @@ python hands_on/my_demo.py ### Required Elements (Standard Exercises) - `__init__.py` - Package marker -- `download.py` - With `__requires_git__`, `__requires_github__`, `download()` +- `gitmastery-exercise.json` - Configuration +- `download.py` - `setup(verbose: bool = False)` - `verify.py` - With `verify()` function - `test_verify.py` - With `REPOSITORY_NAME` and test functions - `README.md` - With scenario, task, hints @@ -112,5 +113,4 @@ python hands_on/my_demo.py - **[exercise-utils](../exercise-utils/SKILL.md)** - Utility functions reference - **[coding-standards](../coding-standards/SKILL.md)** - Code style guidelines -- **[testing](../testing/SKILL.md)** - Testing strategies - **[project-overview](../project-overview/SKILL.md)** - Repository structure diff --git a/.claude/skills/exercise-utils/SKILL.md b/.claude/skills/exercise-utils/SKILL.md index 623b437c..465d3b8b 100644 --- a/.claude/skills/exercise-utils/SKILL.md +++ b/.claude/skills/exercise-utils/SKILL.md @@ -105,4 +105,3 @@ cli.py # No dependencies (base module) - **[exercise-development](../exercise-development/SKILL.md)** - Using utilities in exercises - **[coding-standards](../coding-standards/SKILL.md)** - Code conventions -- **[testing](../testing/SKILL.md)** - Using test utilities diff --git a/.claude/skills/exercise-utils/cli-module.md b/.claude/skills/exercise-utils/cli-module.md index b4a935d9..4af3d710 100644 --- a/.claude/skills/exercise-utils/cli-module.md +++ b/.claude/skills/exercise-utils/cli-module.md @@ -3,7 +3,7 @@ ## Overview Utilities for running CLI commands with error handling. -See [exercise_utils/cli.py](../../exercise_utils/cli.py) for implementation. +See [exercise_utils/cli.py](../../../exercise_utils/cli.py) for implementation. ## Key Functions - `run(command, verbose, env={}, exit_on_error=False)` → `CommandResult` @@ -15,5 +15,5 @@ See [exercise_utils/cli.py](../../exercise_utils/cli.py) for implementation. ## Usage Examples See usages in: -- [exercise_utils/git.py](../../exercise_utils/git.py) -- [exercise_utils/github_cli.py](../../exercise_utils/github_cli.py) +- [exercise_utils/git.py](../../../exercise_utils/git.py) +- [exercise_utils/github_cli.py](../../../exercise_utils/github_cli.py) diff --git a/.claude/skills/exercise-utils/file-module.md b/.claude/skills/exercise-utils/file-module.md index bb04366d..bef82988 100644 --- a/.claude/skills/exercise-utils/file-module.md +++ b/.claude/skills/exercise-utils/file-module.md @@ -3,7 +3,7 @@ ## Overview File creation and modification utilities. -See [exercise_utils/file.py](../../exercise_utils/file.py) for implementation. +See [exercise_utils/file.py](../../../exercise_utils/file.py) for implementation. ## Functions - `create_or_update_file(filepath, contents=None)` - Create/overwrite file, auto-creates directories diff --git a/.claude/skills/exercise-utils/git-module.md b/.claude/skills/exercise-utils/git-module.md index 0de6615b..dc3bcb25 100644 --- a/.claude/skills/exercise-utils/git-module.md +++ b/.claude/skills/exercise-utils/git-module.md @@ -3,7 +3,7 @@ ## Overview Wrapper functions for Git CLI commands. -See [exercise_utils/git.py](../../exercise_utils/git.py) for all functions. +See [exercise_utils/git.py](../../../exercise_utils/git.py) for all functions. ## Functions - `init(verbose)`, `add(files, verbose)`, `commit(message, verbose)`, `empty_commit(message, verbose)` diff --git a/.claude/skills/exercise-utils/github-module.md b/.claude/skills/exercise-utils/github-module.md index 85306a70..fe5574aa 100644 --- a/.claude/skills/exercise-utils/github-module.md +++ b/.claude/skills/exercise-utils/github-module.md @@ -3,16 +3,13 @@ ## Overview Wrapper functions for GitHub CLI (gh) commands. -See [exercise_utils/github_cli.py](../../exercise_utils/github_cli.py) for all functions. +See [exercise_utils/github_cli.py](../../../exercise_utils/github_cli.py) for all functions. ## Functions - `get_github_username(verbose)` → `str` -- `create_repo(name, verbose)`, `delete_repo(name, verbose)`, `clone_repo_with_gh(name, verbose, name=None)` -- `fork_repo(repo_name, fork_name, verbose, default_branch_only=True)` -- `has_repo(name, is_fork, verbose)` → `bool`, `has_fork(repo, owner, username, verbose)` → `bool` -- `get_fork_name(repo, owner, username, verbose)` → `str` +- `create_repo(repository_name, verbose)`, `delete_repo(repository_name, verbose)`, `clone_repo_with_gh(repository_name, verbose, name=None)` +- `fork_repo(repository_name, fork_name, verbose, default_branch_only=True)` +- `has_repo(repo_name, is_fork, verbose)` → `bool`, `has_fork(repository_name, owner, username, verbose)` → `bool` +- `get_fork_name(repository_name, owner, username, verbose)` → `str` **Prerequisites:** GitHub CLI (`gh`) installed and authenticated. - -## Usage Examples -See [fork_repo/download.py](../../fork_repo/download.py) for fork/clone pattern. diff --git a/.claude/skills/exercise-utils/gitmastery-module.md b/.claude/skills/exercise-utils/gitmastery-module.md index 12241637..33d22394 100644 --- a/.claude/skills/exercise-utils/gitmastery-module.md +++ b/.claude/skills/exercise-utils/gitmastery-module.md @@ -3,7 +3,7 @@ ## Overview Git-Mastery specific utility for creating start tags. -See [exercise_utils/gitmastery.py](../../exercise_utils/gitmastery.py) for implementation. +See [exercise_utils/gitmastery.py](../../../exercise_utils/gitmastery.py) for implementation. ## Function `create_start_tag(verbose)` - Creates tag `git-mastery-start-` on first commit. diff --git a/.claude/skills/exercise-utils/test-module.md b/.claude/skills/exercise-utils/test-module.md index 0fd4cc50..c530853e 100644 --- a/.claude/skills/exercise-utils/test-module.md +++ b/.claude/skills/exercise-utils/test-module.md @@ -3,7 +3,7 @@ ## Overview Testing utilities for exercise validation. -See [exercise_utils/test.py](../../exercise_utils/test.py) for implementation. +See [exercise_utils/test.py](../../../exercise_utils/test.py) for implementation. ## Core Classes - `GitAutograderTestLoader(exercise_name, grade_func)` - Test runner factory @@ -12,8 +12,8 @@ See [exercise_utils/test.py](../../exercise_utils/test.py) for implementation. ## Usage Examples See test files: -- [amateur_detective/test_verify.py](../../amateur_detective/test_verify.py) -- [grocery_shopping/test_verify.py](../../grocery_shopping/test_verify.py) +- [amateur_detective/test_verify.py](../../../amateur_detective/test_verify.py) +- [grocery_shopping/test_verify.py](../../../grocery_shopping/test_verify.py) ## Loader Pattern ```python @@ -21,5 +21,5 @@ loader = GitAutograderTestLoader("exercise-name", verify) with loader.start() as (test, rs): # setup with rs (RepoSmith) output = test.run() - assert_output(output, GitAutograderStatus.SUCCESS) + assert_output(output, GitAutograderStatus.SUCCESSFUL) ``` diff --git a/.claude/skills/project-overview/SKILL.md b/.claude/skills/project-overview/SKILL.md index 572394d8..29cd5b91 100644 --- a/.claude/skills/project-overview/SKILL.md +++ b/.claude/skills/project-overview/SKILL.md @@ -93,11 +93,25 @@ The script generates the complete exercise structure with all required files. ### 5. Exercise Configuration -Each exercise has a `.gitmastery-exercise.json` file (not present in current structure but referenced in docs): +Each exercise has a `.gitmastery-exercise.json` file ```json { - "exercise_name": "kebab-case-name", - "tags": ["branch", "merge", "intermediate"] + "exercise_name": "amateur-detective", + "tags": [ + "git-status" + ], + "requires_git": true, + "requires_github": false, + "base_files": { + "answers.txt": "answers.txt" + }, + "exercise_repo": { + "repo_type": "local", + "repo_name": "crime-scene", + "repo_title": null, + "create_fork": null, + "init": true + } } ``` @@ -168,9 +182,7 @@ def test_something(): ### Documentation - **README.md**: Main project documentation (currently minimal) -- **summary.md**: Comprehensive repository structure documentation - **.github/CONTRIBUTING.md**: Contributor guidelines with detailed setup instructions -- **.github/copilot-instructions.md**: AI assistant guidelines (for GitHub Copilot) ### CI/CD - GitHub Actions workflows in `.github/workflows/` @@ -213,7 +225,6 @@ When modifying this repository: ## Additional Resources For detailed information, see: -- [exercise-development](./exercise-development/SKILL.md) - Creating and modifying exercises -- [exercise-utils](./exercise-utils/SKILL.md) - Using shared utility modules -- [coding-standards](./coding-standards/SKILL.md) - Code style and quality guidelines -- [testing](./testing/SKILL.md) - Testing strategies and patterns +- [exercise-development](../exercise-development/SKILL.md) - Creating and modifying exercises +- [exercise-utils](../exercise-utils/SKILL.md) - Using shared utility modules +- [coding-standards](../coding-standards/SKILL.md) - Code style and quality guidelines From e27032bd3ff28d6c6ae1786bf6eeae129f028d13 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Tue, 27 Jan 2026 13:08:25 +0800 Subject: [PATCH 09/18] Rename AGENTS.md --- AGENTS.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..4b6d63ff --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,70 @@ +# Git-Mastery Exercises - AI Agent Guide + +This repository uses the **Agent Skills** standard for AI documentation. All detailed instructions are in `.claude/skills/`. + +## For AI Agents + +**Workflow**: +1. Read this AGENT.md to understand available skills +2. Based on your task, load relevant skills from `.claude/skills/` +3. Reference actual code files linked in skills + +**Available skills**: +- **[project-overview](file:.claude/skills/project-overview/SKILL.md)**: Repository overview, structure, dependencies +- **[exercise-development](file:.claude/skills/exercise-development/SKILL.md)**: Creating exercises (standard vs hands-on) +- **[exercise-utils](file:.claude/skills/exercise-utils/SKILL.md)**: Utility modules API reference +- **[coding-standards](file:.claude/skills/coding-standards/SKILL.md)**: Code style, naming, type hints + +**When to load each skill**: +- Creating/modifying exercises → `exercise-development` +- Using utility functions → `exercise-utils` +- Code review/formatting → `coding-standards` +- Understanding repo structure → `project-overview` + +## For Human Developers + +**Working with AI agents**: Simply state your task. AI will read AGENT.md, identify needed skills, and load them automatically. + +**Example tasks**: +``` +"Create a new exercise about merge conflicts" + +"Fix the test in amateur_detective/test_verify.py" + +"Update grocery_shopping/download.py to use exercise_utils" + +"Review this code against our standards" + +"Review this code against coding standards in .claude/skills/coding-standards/" +``` + +### What AI Knows From Skills + +After loading skills, AI understands: +- **Structure**: Each exercise has download.py, verify.py, test_verify.py, README.md +- **Patterns**: Use `exercise_utils.*` instead of subprocess, call `create_start_tag()` last +- **Testing**: Required scenarios (no answers, wrong answers, success), `loader.start()` pattern +- **Standards**: 88-char lines, snake_case, type hints, import order +- **APIs**: All functions in exercise_utils/ (git.py, github_cli.py, etc.) + +### Tips for Best Results + +1. **Be specific about which skill**: "Follow exercise-development skill for creating download.py" +2. **Reference example files**: Skills already point to examples like [amateur_detective/test_verify.py](amateur_detective/test_verify.py) +3. **Check standards before committing**: "Does this follow coding-standards skill?" + +## Quick Reference + +```bash +./setup.sh # Setup environment +./new.sh # Create exercise (interactive) +./test.sh # Test one exercise +pytest . -s -vv # Test all +ruff format . && ruff check . # Format & lint +``` + +## Tech Stack + +- Python 3.8+, pytest, git-autograder, repo-smith +- ruff (lint/format), mypy (types) +- Git CLI, GitHub CLI (gh) From 055e2d96f6eb9b757c49091848adf13eb754c576 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Tue, 27 Jan 2026 13:14:05 +0800 Subject: [PATCH 10/18] Remove AGENT.md --- AGENT.md | 70 -------------------------------------------------------- 1 file changed, 70 deletions(-) delete mode 100644 AGENT.md diff --git a/AGENT.md b/AGENT.md deleted file mode 100644 index 4b6d63ff..00000000 --- a/AGENT.md +++ /dev/null @@ -1,70 +0,0 @@ -# Git-Mastery Exercises - AI Agent Guide - -This repository uses the **Agent Skills** standard for AI documentation. All detailed instructions are in `.claude/skills/`. - -## For AI Agents - -**Workflow**: -1. Read this AGENT.md to understand available skills -2. Based on your task, load relevant skills from `.claude/skills/` -3. Reference actual code files linked in skills - -**Available skills**: -- **[project-overview](file:.claude/skills/project-overview/SKILL.md)**: Repository overview, structure, dependencies -- **[exercise-development](file:.claude/skills/exercise-development/SKILL.md)**: Creating exercises (standard vs hands-on) -- **[exercise-utils](file:.claude/skills/exercise-utils/SKILL.md)**: Utility modules API reference -- **[coding-standards](file:.claude/skills/coding-standards/SKILL.md)**: Code style, naming, type hints - -**When to load each skill**: -- Creating/modifying exercises → `exercise-development` -- Using utility functions → `exercise-utils` -- Code review/formatting → `coding-standards` -- Understanding repo structure → `project-overview` - -## For Human Developers - -**Working with AI agents**: Simply state your task. AI will read AGENT.md, identify needed skills, and load them automatically. - -**Example tasks**: -``` -"Create a new exercise about merge conflicts" - -"Fix the test in amateur_detective/test_verify.py" - -"Update grocery_shopping/download.py to use exercise_utils" - -"Review this code against our standards" - -"Review this code against coding standards in .claude/skills/coding-standards/" -``` - -### What AI Knows From Skills - -After loading skills, AI understands: -- **Structure**: Each exercise has download.py, verify.py, test_verify.py, README.md -- **Patterns**: Use `exercise_utils.*` instead of subprocess, call `create_start_tag()` last -- **Testing**: Required scenarios (no answers, wrong answers, success), `loader.start()` pattern -- **Standards**: 88-char lines, snake_case, type hints, import order -- **APIs**: All functions in exercise_utils/ (git.py, github_cli.py, etc.) - -### Tips for Best Results - -1. **Be specific about which skill**: "Follow exercise-development skill for creating download.py" -2. **Reference example files**: Skills already point to examples like [amateur_detective/test_verify.py](amateur_detective/test_verify.py) -3. **Check standards before committing**: "Does this follow coding-standards skill?" - -## Quick Reference - -```bash -./setup.sh # Setup environment -./new.sh # Create exercise (interactive) -./test.sh # Test one exercise -pytest . -s -vv # Test all -ruff format . && ruff check . # Format & lint -``` - -## Tech Stack - -- Python 3.8+, pytest, git-autograder, repo-smith -- ruff (lint/format), mypy (types) -- Git CLI, GitHub CLI (gh) From bf5d5b297757565bfb6d8bf250381b5972f2d856 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Tue, 27 Jan 2026 13:29:15 +0800 Subject: [PATCH 11/18] Address Copilot Comment --- .claude/skills/exercise-development/SKILL.md | 4 ++-- .claude/skills/exercise-development/standard-exercises.md | 6 ------ AGENTS.md | 4 ++-- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.claude/skills/exercise-development/SKILL.md b/.claude/skills/exercise-development/SKILL.md index 86920c91..edad81e8 100644 --- a/.claude/skills/exercise-development/SKILL.md +++ b/.claude/skills/exercise-development/SKILL.md @@ -86,7 +86,7 @@ python hands_on/my_demo.py ### Required Elements (Standard Exercises) - `__init__.py` - Package marker -- `gitmastery-exercise.json` - Configuration +- `.gitmastery-exercise.json` - Configuration - `download.py` - `setup(verbose: bool = False)` - `verify.py` - With `verify()` function - `test_verify.py` - With `REPOSITORY_NAME` and test functions @@ -101,7 +101,7 @@ python hands_on/my_demo.py ### Standard Exercise Examples - **Simple**: `amateur_detective/` - Answer validation - **Branching**: `branch_bender/` - Branch operations -- **Remote**: `remote_branch_pull/` - GitHub integration +- **Remote**: `remote_control/` - GitHub integration - **Complex**: `conflict_mediator/` - Merge conflicts ### Hands-On Examples diff --git a/.claude/skills/exercise-development/standard-exercises.md b/.claude/skills/exercise-development/standard-exercises.md index 3fa11f66..21e2ad5c 100644 --- a/.claude/skills/exercise-development/standard-exercises.md +++ b/.claude/skills/exercise-development/standard-exercises.md @@ -47,12 +47,6 @@ Use the `new.sh` script to generate exercise structure: **Purpose**: Set up the initial Git repository state for the exercise. -### Required Variables -```python -__requires_git__ = True # Always required -__requires_github__ = False # Set True if exercise uses GitHub -``` - ### Required Function ```python def download(verbose: bool): diff --git a/AGENTS.md b/AGENTS.md index 4b6d63ff..e2f0603b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ This repository uses the **Agent Skills** standard for AI documentation. All det ## For AI Agents **Workflow**: -1. Read this AGENT.md to understand available skills +1. Read this AGENTS.md to understand available skills 2. Based on your task, load relevant skills from `.claude/skills/` 3. Reference actual code files linked in skills @@ -23,7 +23,7 @@ This repository uses the **Agent Skills** standard for AI documentation. All det ## For Human Developers -**Working with AI agents**: Simply state your task. AI will read AGENT.md, identify needed skills, and load them automatically. +**Working with AI agents**: Simply state your task. AI will read AGENTS.md, identify needed skills, and load them automatically. **Example tasks**: ``` From 933985eb665d85bb3db46ed68414ff28558df451 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Tue, 27 Jan 2026 13:40:17 +0800 Subject: [PATCH 12/18] Change python version --- .claude/skills/exercise-development/standard-exercises.md | 2 +- .claude/skills/project-overview/SKILL.md | 2 +- AGENTS.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude/skills/exercise-development/standard-exercises.md b/.claude/skills/exercise-development/standard-exercises.md index 21e2ad5c..bcddee16 100644 --- a/.claude/skills/exercise-development/standard-exercises.md +++ b/.claude/skills/exercise-development/standard-exercises.md @@ -49,7 +49,7 @@ Use the `new.sh` script to generate exercise structure: ### Required Function ```python -def download(verbose: bool): +def setup(verbose: bool = False): """Setup the exercise repository.""" # Implementation here ``` diff --git a/.claude/skills/project-overview/SKILL.md b/.claude/skills/project-overview/SKILL.md index 29cd5b91..9713902f 100644 --- a/.claude/skills/project-overview/SKILL.md +++ b/.claude/skills/project-overview/SKILL.md @@ -42,7 +42,7 @@ Single-file demonstrations without validation. Examples: `add_files.py`, `branch - **file.py**: File operations ## Dependencies -- Python 3.8+ +- Python 3.13+ - git-autograder 6.*, repo-smith, pytest - ruff, mypy (dev tools) - Git CLI, GitHub CLI (gh) diff --git a/AGENTS.md b/AGENTS.md index e2f0603b..b859539f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -65,6 +65,6 @@ ruff format . && ruff check . # Format & lint ## Tech Stack -- Python 3.8+, pytest, git-autograder, repo-smith +- Python 3.13+, pytest, git-autograder, repo-smith - ruff (lint/format), mypy (types) - Git CLI, GitHub CLI (gh) From 98f0a25c9c7192d251efeccd7c75ab752d658c42 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Tue, 27 Jan 2026 13:55:35 +0800 Subject: [PATCH 13/18] Address copilot comment --- .../skills/exercise-development/standard-exercises.md | 9 ++------- .claude/skills/exercise-utils/git-module.md | 2 -- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.claude/skills/exercise-development/standard-exercises.md b/.claude/skills/exercise-development/standard-exercises.md index bcddee16..96daa5b7 100644 --- a/.claude/skills/exercise-development/standard-exercises.md +++ b/.claude/skills/exercise-development/standard-exercises.md @@ -87,14 +87,11 @@ from exercise_utils.git import clone_repo_with_git, checkout, add, commit, push from exercise_utils.github_cli import get_github_username, fork_repo, delete_repo, has_repo from exercise_utils.file import append_to_file -__requires_git__ = True -__requires_github__ = True - TARGET_REPO = "git-mastery/sample-repo" FORK_NAME = "gitmastery-sample-repo" LOCAL_DIR = "sample-repo" -def download(verbose: bool): +def setup(verbose: bool = False): username = get_github_username(verbose) full_repo_name = f"{username}/{FORK_NAME}" @@ -118,11 +115,9 @@ def download(verbose: bool): ``` ### Best Practices -- Use utility functions from `exercise_utils/` - never raw subprocess calls -- Always call `create_start_tag()` as the final step +- Use utility functions from `exercise_utils/` - never raw subprocess calls] - Keep setup simple and focused on learning objectives - Use verbose parameter for all utility calls -- Set appropriate `__requires_*__` flags ## Step 3: Write README.md diff --git a/.claude/skills/exercise-utils/git-module.md b/.claude/skills/exercise-utils/git-module.md index dc3bcb25..fa8bf460 100644 --- a/.claude/skills/exercise-utils/git-module.md +++ b/.claude/skills/exercise-utils/git-module.md @@ -11,5 +11,3 @@ See [exercise_utils/git.py](../../../exercise_utils/git.py) for all functions. - `tag(tag_name, verbose)`, `tag_with_options(tag_name, options, verbose)` - `add_remote(remote, url, verbose)`, `remove_remote(remote, verbose)`, `push(remote, branch, verbose)` - `track_remote_branch(remote, branch, verbose)`, `clone_repo_with_git(url, verbose, name=None)` - -**Note:** All functions exit on failure, use `verbose=True` to see output. From 23422bc622b614080cdaa07302406c6554388613 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Tue, 27 Jan 2026 14:55:21 +0800 Subject: [PATCH 14/18] Address copilot comment --- .claude/skills/exercise-development/standard-exercises.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.claude/skills/exercise-development/standard-exercises.md b/.claude/skills/exercise-development/standard-exercises.md index 96daa5b7..78f563f0 100644 --- a/.claude/skills/exercise-development/standard-exercises.md +++ b/.claude/skills/exercise-development/standard-exercises.md @@ -57,13 +57,10 @@ def setup(verbose: bool = False): ### Pattern: Local Repository Only ```python import os -from exercise_utils.git import init, add, commit +from exercise_utils.git import add, commit from exercise_utils.gitmastery import create_start_tag from exercise_utils.file import create_or_update_file -__requires_git__ = True -__requires_github__ = False - def setup(verbose: bool = False): # Create initial files create_or_update_file("file1.txt", "Initial content") @@ -115,7 +112,7 @@ def setup(verbose: bool = False): ``` ### Best Practices -- Use utility functions from `exercise_utils/` - never raw subprocess calls] +- Use utility functions from `exercise_utils/` - never raw subprocess calls - Keep setup simple and focused on learning objectives - Use verbose parameter for all utility calls From 5d8b42cdc11546e4b919e3b7d617f49af72724fd Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Wed, 28 Jan 2026 20:48:37 +0800 Subject: [PATCH 15/18] Replace example with link --- .claude/skills/coding-standards/SKILL.md | 62 +--- .../exercise-development/hands-on-scripts.md | 315 +----------------- .../standard-exercises.md | 313 ++--------------- .claude/skills/exercise-utils/SKILL.md | 63 +--- .claude/skills/exercise-utils/test-module.md | 8 +- 5 files changed, 69 insertions(+), 692 deletions(-) diff --git a/.claude/skills/coding-standards/SKILL.md b/.claude/skills/coding-standards/SKILL.md index 60c7217d..8013f890 100644 --- a/.claude/skills/coding-standards/SKILL.md +++ b/.claude/skills/coding-standards/SKILL.md @@ -29,32 +29,13 @@ mypy / # Type checking - Queries: `get_*`, `has_*`, `is_*` ## Type Hints -Always include on function signatures: -```python -def func(name: str, count: int, verbose: bool) -> None: - ... - -def get_data(path: Optional[str] = None) -> List[str]: - ... -``` +Always include on function signatures. See [exercise_utils/git.py](../../../exercise_utils/git.py) for examples. ## Imports -Order: stdlib → third-party → local (blank lines between) -```python -import os -from typing import List - -from git_autograder import GitAutograderExercise - -from exercise_utils.git import commit -``` +Order: stdlib → third-party → local (blank lines between). See any exercise file for examples like [grocery_shopping/verify.py](../../../grocery_shopping/verify.py). ## Documentation -```python -def tag(tag_name: str, verbose: bool) -> None: - """Tags the latest commit with the given tag_name.""" - ... -``` +One-line docstrings for simple functions. See [exercise_utils/git.py](../../../exercise_utils/git.py) for examples. ## Best Practices - **DRY**: Extract common logic @@ -65,36 +46,17 @@ def tag(tag_name: str, verbose: bool) -> None: ## Anti-Patterns -### ❌ Don't -```python -# Raw subprocess -import subprocess -subprocess.run(["git", "add", "file.txt"]) +**❌ Don't**: +- Raw subprocess calls +- Missing type hints +- Magic values/hardcoded numbers -# No type hints -def process(data): - return data.strip() +**✅ Do**: +- Use exercise_utils wrappers (see [exercise_utils/git.py](../../../exercise_utils/git.py)) +- Add type hints on all functions +- Use named constants -# Magic values -if count > 5: - pass -``` - -### ✅ Do -```python -# Use wrappers -from exercise_utils.git import add -add(["file.txt"], verbose) - -# Type hints -def process(data: str) -> str: - return data.strip() - -# Named constants -MAX_RETRIES = 5 -if count > MAX_RETRIES: - pass -``` +**Examples**: See [grocery_shopping/download.py](../../../grocery_shopping/download.py) for proper patterns. ## Pre-Commit Checklist diff --git a/.claude/skills/exercise-development/hands-on-scripts.md b/.claude/skills/exercise-development/hands-on-scripts.md index d4896ba6..4d1e6629 100644 --- a/.claude/skills/exercise-development/hands-on-scripts.md +++ b/.claude/skills/exercise-development/hands-on-scripts.md @@ -24,46 +24,13 @@ touch hands_on/my_demo.py ### 2. Implement Required Variables -```python -# At top of file -__requires_git__ = True # Set True if uses Git commands -__requires_github__ = False # Set True if uses GitHub CLI -``` +`__requires_git__` and `__requires_github__` flags at top of file. ### 3. Implement download() Function -This is the only required function - it performs the demonstration: - -```python -import os -from exercise_utils.git import init, add, commit, checkout -from exercise_utils.file import create_or_update_file - -__requires_git__ = True -__requires_github__ = False +This is the only required function - it performs the demonstration. -def download(verbose: bool): - """Demonstrate creating and switching branches.""" - # Setup initial repository - os.makedirs("demo-repo") - os.chdir("demo-repo") - - init(verbose) - create_or_update_file("README.md", "# Demo Project\n") - add(["README.md"], verbose) - commit("Initial commit", verbose) - - # Demonstrate the concept - checkout("feature-branch", create_branch=True, verbose=verbose) - create_or_update_file("feature.txt", "New feature\n") - add(["feature.txt"], verbose) - commit("Add feature", verbose) - - # Show the result - if verbose: - print("\n✓ Created feature-branch with new commit") - print("Run 'git log --oneline --all --graph' to see the result") -``` +**Examples**: See [hands_on/branch_delete.py](../../../hands_on/branch_delete.py) or [hands_on/add_files.py](../../../hands_on/add_files.py). ### 4. Focus on Demonstration @@ -75,195 +42,17 @@ Key principles: ## Common Patterns -### Pattern: Simple Git Operation Demo - -```python -import os -from exercise_utils.git import init, checkout -from exercise_utils.file import create_or_update_file - -__requires_git__ = True -__requires_github__ = False - -def download(verbose: bool): - """Show how to rename a branch.""" - os.makedirs("rename-demo") - os.chdir("rename-demo") - - init(verbose) - - if verbose: - print("\n✓ Repository initialized") - print("Try running: git branch -m main trunk") - print("Then: git branch") -``` - -### Pattern: Branch Operations Demo - -```python -import os -from exercise_utils.git import init, checkout, add, commit -from exercise_utils.file import create_or_update_file - -__requires_git__ = True -__requires_github__ = False - -def download(verbose: bool): - """Demonstrate branch creation and switching.""" - os.makedirs("branch-demo") - os.chdir("branch-demo") - - # Setup - init(verbose) - create_or_update_file("main.txt", "Main branch file\n") - add(["main.txt"], verbose) - commit("Initial on main", verbose) - - # Create feature branch - checkout("feature", create_branch=True, verbose=verbose) - create_or_update_file("feature.txt", "Feature file\n") - add(["feature.txt"], verbose) - commit("Add feature", verbose) - - # Back to main - checkout("main", create_branch=False, verbose=verbose) - - if verbose: - print("\n✓ Two branches created: main and feature") - print("Current branch: main") - print("Try: git log --oneline --all --graph") -``` - -### Pattern: GitHub Operations Demo - -```python -import os -from exercise_utils.git import clone_repo_with_git, checkout, add, commit -from exercise_utils.github_cli import get_github_username, fork_repo, has_repo, delete_repo -from exercise_utils.file import append_to_file - -__requires_git__ = True -__requires_github__ = True - -def download(verbose: bool): - """Demonstrate forking and cloning a repository.""" - TARGET_REPO = "git-mastery/sample-repo" - FORK_NAME = "my-forked-repo" - - username = get_github_username(verbose) - full_repo_name = f"{username}/{FORK_NAME}" - - # Clean up if exists - if has_repo(full_repo_name, True, verbose): - if verbose: - print(f"Deleting existing fork: {full_repo_name}") - delete_repo(full_repo_name, verbose) - - # Fork repository - if verbose: - print(f"Forking {TARGET_REPO}...") - fork_repo(TARGET_REPO, FORK_NAME, verbose, True) - - # Clone locally - if verbose: - print(f"Cloning to local-fork directory...") - clone_repo_with_git( - f"https://github.com/{username}/{FORK_NAME}", - verbose, - "local-fork" - ) - - os.chdir("local-fork") - - # Make a change - checkout("demo-branch", create_branch=True, verbose=verbose) - append_to_file("README.md", "\n## Demo Addition\n") - add(["README.md"], verbose) - commit("Add demo section", verbose) - - if verbose: - print(f"\n✓ Forked {TARGET_REPO} to {username}/{FORK_NAME}") - print("✓ Cloned to local-fork directory") - print("✓ Created demo-branch with changes") - print("\nExplore the repository:") - print(" git log --oneline") - print(" git remote -v") -``` - -### Pattern: Merge Operations Demo - -```python -import os -from exercise_utils.git import init, checkout, add, commit, merge -from exercise_utils.file import create_or_update_file - -__requires_git__ = True -__requires_github__ = False - -def download(verbose: bool): - """Demonstrate merging branches.""" - os.makedirs("merge-demo") - os.chdir("merge-demo") - - # Setup main branch - init(verbose) - create_or_update_file("file.txt", "Line 1\n") - add(["file.txt"], verbose) - commit("Initial commit", verbose) - - # Create and work on feature branch - checkout("feature", create_branch=True, verbose=verbose) - create_or_update_file("file.txt", "Line 1\nLine 2 from feature\n") - add(["file.txt"], verbose) - commit("Add line 2", verbose) - - # Back to main and merge - checkout("main", create_branch=False, verbose=verbose) - merge("feature", ff=False, verbose=verbose) - - if verbose: - print("\n✓ Merged feature branch into main") - print("View the merge: git log --oneline --graph") -``` +See examples in [hands_on/](../../../hands_on/) directory: +- Simple Git operations: [branch_delete.py](../../../hands_on/branch_delete.py) +- Branch operations: [create_branch.py](../../../hands_on/create_branch.py) +- GitHub operations: [remote_branch_pull.py](../../../hands_on/remote_branch_pull.py) +- Merge operations: Any merge-related script in hands_on/ ## Best Practices -### Use Helpful Print Statements - -```python -if verbose: - print("\n=== Demo Complete ===") - print("✓ Created repository with 2 branches") - print("\nNext steps:") - print(" 1. cd demo-repo") - print(" 2. git log --oneline --all --graph") - print(" 3. git branch -v") -``` - -### Leave Repository in Explorable State - -```python -# Good - leaves interesting state -checkout("feature", create_branch=True, verbose=verbose) -commit("Feature work", verbose) -checkout("main", create_branch=False, verbose=verbose) -# Now user can explore both branches - -# Less useful - ends in empty state -init(verbose) -# Not much to explore -``` - -### Guide Without Prescribing - -```python -if verbose: - print("\nTry these commands to explore:") - print(" git status") - print(" git log") - print(" git branch") - # Suggest, don't require -``` +- Use helpful print statements with verbose flag +- Leave repository in explorable state (multiple branches, commits) +- Guide without prescribing (suggest commands, don't require) ## Hands-On vs Standard Exercise @@ -282,88 +71,10 @@ if verbose: ## Testing Hands-On Scripts -No formal tests required, but manually verify: - -```bash -# 1. Run the script -python hands_on/my_demo.py - -# 2. Check created state -cd demo-repo # or whatever directory it creates -git status -git log --oneline --all --graph -git branch - -# 3. Verify it demonstrates the concept clearly -``` - -## Examples - -### Minimal Example -```python -import os -from exercise_utils.git import init - -__requires_git__ = True -__requires_github__ = False - -def download(verbose: bool): - """Show git init.""" - os.makedirs("init-demo") - os.chdir("init-demo") - init(verbose) - - if verbose: - print("\n✓ Git repository initialized") - print("Run 'git status' to see") -``` - -### Complete Example with GitHub -```python -import os -from exercise_utils.git import clone_repo_with_git, checkout, add, commit, push -from exercise_utils.github_cli import get_github_username, fork_repo -from exercise_utils.file import append_to_file - -__requires_git__ = True -__requires_github__ = True - -TARGET_REPO = "git-mastery/sample-repo" -FORK_NAME = "demo-fork" - -def download(verbose: bool): - """Complete GitHub workflow demonstration.""" - username = get_github_username(verbose) - - # Fork and clone - fork_repo(TARGET_REPO, FORK_NAME, verbose, True) - clone_repo_with_git( - f"https://github.com/{username}/{FORK_NAME}", - verbose, - "demo-repo" - ) - os.chdir("demo-repo") - - # Make changes - checkout("demo-feature", create_branch=True, verbose=verbose) - append_to_file("README.md", "\n## Demo Feature\n") - add(["README.md"], verbose) - commit("Add demo feature", verbose) - push("origin", "demo-feature", verbose) - - if verbose: - print("\n=== Demo Complete ===") - print(f"✓ Forked to {username}/{FORK_NAME}") - print("✓ Cloned to demo-repo") - print("✓ Created demo-feature branch") - print("✓ Pushed changes to GitHub") - print("\nView on GitHub:") - print(f" https://github.com/{username}/{FORK_NAME}") -``` +No formal tests required, but manually verify by running the script and checking the created state. -## Quick Checklist +## Pre-Commit Checklist -Before committing a hands-on script: - ✓ Has `__requires_git__` and `__requires_github__` - ✓ Has `download(verbose: bool)` function - ✓ Uses utility functions (not raw subprocess) diff --git a/.claude/skills/exercise-development/standard-exercises.md b/.claude/skills/exercise-development/standard-exercises.md index 78f563f0..4ec8f288 100644 --- a/.claude/skills/exercise-development/standard-exercises.md +++ b/.claude/skills/exercise-development/standard-exercises.md @@ -47,72 +47,15 @@ Use the `new.sh` script to generate exercise structure: **Purpose**: Set up the initial Git repository state for the exercise. -### Required Function -```python -def setup(verbose: bool = False): - """Setup the exercise repository.""" - # Implementation here -``` - -### Pattern: Local Repository Only -```python -import os -from exercise_utils.git import add, commit -from exercise_utils.gitmastery import create_start_tag -from exercise_utils.file import create_or_update_file - -def setup(verbose: bool = False): - # Create initial files - create_or_update_file("file1.txt", "Initial content") - create_or_update_file("file2.txt", "More content") - - # Setup Git repository - add(["file1.txt", "file2.txt"], verbose) - commit("Initial commit", verbose) - - # Make changes for exercise - create_or_update_file("file3.txt", "New file") - - # Create start tag (always last step) - create_start_tag(verbose) -``` +**Examples**: +- Local repo: See [grocery_shopping/download.py](../../../grocery_shopping/download.py) +- GitHub integration: See [fork_repo/download.py](../../../fork_repo/download.py) -### Pattern: With GitHub Integration -```python -import os -from exercise_utils.git import clone_repo_with_git, checkout, add, commit, push -from exercise_utils.github_cli import get_github_username, fork_repo, delete_repo, has_repo -from exercise_utils.file import append_to_file - -TARGET_REPO = "git-mastery/sample-repo" -FORK_NAME = "gitmastery-sample-repo" -LOCAL_DIR = "sample-repo" - -def setup(verbose: bool = False): - username = get_github_username(verbose) - full_repo_name = f"{username}/{FORK_NAME}" - - # Clean up existing fork if present - if has_repo(full_repo_name, True, verbose): - delete_repo(full_repo_name, verbose) - - # Create fork - fork_repo(TARGET_REPO, FORK_NAME, verbose, False) - - # Clone locally - clone_repo_with_git(f"https://github.com/{full_repo_name}", verbose, LOCAL_DIR) - os.chdir(LOCAL_DIR) - - # Make changes - checkout("feature-branch", True, verbose) - append_to_file("README.md", "\nNew content") - add(["README.md"], verbose) - commit("Update README", verbose) - push("origin", "feature-branch", verbose) -``` +**Required function**: `def setup(verbose: bool = False)` -### Best Practices +**Key points**: - Use utility functions from `exercise_utils/` - never raw subprocess calls +- Always call `create_start_tag()` as the final step - Keep setup simple and focused on learning objectives - Use verbose parameter for all utility calls @@ -120,58 +63,17 @@ def setup(verbose: bool = False): **Purpose**: Student-facing instructions for the exercise. -### Required Sections +**Examples**: See any exercise README like [amateur_detective/README.md](../../../amateur_detective/README.md) +**Required sections**: 1. **Title**: Exercise name (h1) 2. **Scenario/Context**: Engaging story that motivates the exercise 3. **Task**: Clear, actionable objectives -4. **Hints**: Progressive disclosure of help - -### Template -```markdown -# exercise-name - -## Scenario -[Engaging story or context that motivates the exercise. -Make it relatable and interesting.] - -## Task -[Clear description of what students need to accomplish] - -Use `git ` to [specific action]. - -[Additional requirements or constraints] - -Update your answers in `answers.txt`. - -## Hints - -
-Hint 1 +4. **Hints**: Progressive disclosure of help (3-5 hints in collapsible details) -[First level of help - general guidance] - -
- -
-Hint 2 - -[Second level - more specific direction] - -
- -
-Hint 3 - -[Third level - nearly direct answer] - -
-``` - -### Best Practices +**Best practices**: - Use engaging scenarios that make Git concepts relatable - Be specific about expected outcomes -- Provide 3-5 progressive hints - Mention specific Git commands when appropriate - Keep instructions concise and scannable @@ -179,178 +81,45 @@ Update your answers in `answers.txt`. **Purpose**: Validate student's solution using composable rules. -### Required Imports -```python -from git_autograder import ( - GitAutograderExercise, - GitAutograderOutput, - GitAutograderStatus, -) -from git_autograder.answers.rules import HasExactValueRule, NotEmptyRule -# Import other rules as needed -``` +**Examples**: +- Answer-based: See [amateur_detective/verify.py](../../../amateur_detective/verify.py) +- Repository state: See [grocery_shopping/verify.py](../../../grocery_shopping/verify.py) +- Branch validation: See [branch_compare/verify.py](../../../branch_compare/verify.py) -### Required Function -```python -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - """Verify the student's solution.""" - # Validation logic here -``` +**Required function**: `def verify(exercise: GitAutograderExercise) -> GitAutograderOutput` -### Pattern: Answer-Based Validation -For exercises where students provide answers in `answers.txt`: - -```python -QUESTION_ONE = "Which file was modified?" -QUESTION_TWO = "Which commit was the change made in?" - -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - ( - exercise.answers.add_validation(QUESTION_ONE, NotEmptyRule()) - .add_validation(QUESTION_ONE, HasExactValueRule("expected_file.txt")) - .add_validation(QUESTION_TWO, NotEmptyRule()) - .add_validation(QUESTION_TWO, HasExactValueRule("abc123")) - .validate() - ) - - return exercise.to_output( - ["Congratulations! You solved the exercise!"], - GitAutograderStatus.SUCCESSFUL, - ) -``` - -### Pattern: Repository State Validation -For exercises checking Git repository state: - -```python -from git_autograder.repo.rules import HasBranchRule, HasCommitWithMessageRule - -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - ( - exercise.repo.add_validation(HasBranchRule("feature-branch")) - .add_validation(HasCommitWithMessageRule("Add new feature")) - .validate() - ) - - return exercise.to_output( - ["Great work! The repository is in the correct state."], - GitAutograderStatus.SUCCESSFUL, - ) -``` - -### Validation Rule Categories +**Validation rule categories**: - **Answer rules**: `NotEmptyRule`, `HasExactValueRule`, `MatchesPatternRule` - **Repository rules**: `HasBranchRule`, `HasCommitRule`, `HasRemoteRule` - **Commit rules**: `HasCommitWithMessageRule`, `CommitCountRule` - **File rules**: `FileExistsRule`, `FileContentsRule` -### Best Practices +**Best practices**: - Chain validations using fluent API - Provide clear, actionable success messages - Use status codes appropriately (SUCCESSFUL, UNSUCCESSFUL) -- Test edge cases in your validation logic - Keep validation focused on learning objectives ## Step 5: Write test_verify.py **Purpose**: Test the verification logic with various scenarios. -### Required Imports -```python -from exercise_utils.test import GitAutograderTestLoader, assert_output -from git_autograder import GitAutograderStatus -from git_autograder.answers.rules import HasExactValueRule, NotEmptyRule - -from .verify import QUESTION_ONE, QUESTION_TWO, verify -``` - -### Required Setup -```python -REPOSITORY_NAME = "exercise-name" # Must match exercise directory name - -loader = GitAutograderTestLoader(REPOSITORY_NAME, verify) -``` - -### Required Test Scenarios - -#### 1. No Answers -```python -def test_no_answers(): - """Test when student provides no answers.""" - with loader.start(mock_answers={QUESTION_ONE: "", QUESTION_TWO: ""}) as (test, _): - output = test.run() - assert_output( - output, - GitAutograderStatus.UNSUCCESSFUL, - [ - NotEmptyRule.EMPTY.format(question=QUESTION_ONE), - NotEmptyRule.EMPTY.format(question=QUESTION_TWO), - ], - ) -``` - -#### 2. Partial Answers -```python -def test_partial_answers(): - """Test when some answers missing.""" - with loader.start( - mock_answers={QUESTION_ONE: "correct", QUESTION_TWO: ""} - ) as (test, _): - output = test.run() - assert_output( - output, - GitAutograderStatus.UNSUCCESSFUL, - [NotEmptyRule.EMPTY.format(question=QUESTION_TWO)], - ) -``` +**Examples**: +- Answer-based: See [amateur_detective/test_verify.py](../../../amateur_detective/test_verify.py) +- Repository state: See [grocery_shopping/test_verify.py](../../../grocery_shopping/test_verify.py) -#### 3. Wrong Answers -```python -def test_wrong_answers(): - """Test when answers are incorrect.""" - with loader.start( - mock_answers={QUESTION_ONE: "wrong", QUESTION_TWO: "also_wrong"} - ) as (test, _): - output = test.run() - assert_output( - output, - GitAutograderStatus.UNSUCCESSFUL, - [ - HasExactValueRule.NOT_EXACT.format(question=QUESTION_ONE), - HasExactValueRule.NOT_EXACT.format(question=QUESTION_TWO), - ], - ) -``` +**Required setup**: +- `REPOSITORY_NAME = "exercise-name"` (must match directory) +- `loader = GitAutograderTestLoader(REPOSITORY_NAME, verify)` -#### 4. Mixed Answers -```python -def test_mixed_answers(): - """Test mix of correct and incorrect answers.""" - with loader.start( - mock_answers={QUESTION_ONE: "correct", QUESTION_TWO: "wrong"} - ) as (test, _): - output = test.run() - assert_output( - output, - GitAutograderStatus.UNSUCCESSFUL, - [HasExactValueRule.NOT_EXACT.format(question=QUESTION_TWO)], - ) -``` +**Required test scenarios**: +1. No answers/changes +2. Partial answers/completion +3. Wrong answers/approach +4. Mixed (some correct, some wrong) +5. All correct -#### 5. Correct Answers -```python -def test_correct_answers(): - """Test successful completion with all correct answers.""" - with loader.start( - mock_answers={QUESTION_ONE: "correct", QUESTION_TWO: "also_correct"} - ) as (test, _): - output = test.run() - assert_output( - output, - GitAutograderStatus.SUCCESSFUL, - ["Congratulations! You solved the exercise!"], - ) -``` +**Pattern**: Use `loader.start(mock_answers={...})` context manager, then `test.run()` and `assert_output()` ## Step 6: Add Resources (Optional) @@ -362,30 +131,14 @@ def test_correct_answers(): - Scripts that students interact with - Images or diagrams for README -**Accessing resources**: -```python -import os -from pathlib import Path - -# In download.py -resource_dir = Path(__file__).parent / "res" -sample_file = resource_dir / "sample.txt" - -# Copy to exercise directory -import shutil -shutil.copy(sample_file, ".") -``` +**Accessing**: Use `Path(__file__).parent / "res"` to get resource directory ## Testing Your Exercise ### Run Tests ```bash ./test.sh -``` - -### Run Specific Test -```bash -pytest /test_verify.py::test_correct_answers -s -vv +pytest /test_verify.py::test_name -s -vv ``` ### Manual Testing @@ -405,7 +158,7 @@ pytest /test_verify.py::test_correct_answers -s -vv ### Download Script Errors 1. Check `__requires_git__` and `__requires_github__` flags 2. Verify Git/GitHub CLI is available -3. Test with verbose mode: `download(verbose=True)` +3. Test with verbose mode: `setup(verbose=True)` 4. Check file paths are relative to exercise directory ### Validation Not Working diff --git a/.claude/skills/exercise-utils/SKILL.md b/.claude/skills/exercise-utils/SKILL.md index 465d3b8b..89869b92 100644 --- a/.claude/skills/exercise-utils/SKILL.md +++ b/.claude/skills/exercise-utils/SKILL.md @@ -25,63 +25,20 @@ The `exercise_utils/` package provides reusable wrappers for common operations. ## Quick Reference -### Most Common Functions - -```python -# Git operations -from exercise_utils.git import init, add, commit, checkout -init(verbose) -add(["file.txt"], verbose) -commit("Initial commit", verbose) -checkout("branch-name", create_branch=True, verbose=verbose) - -# GitHub operations -from exercise_utils.github_cli import fork_repo, get_github_username -username = get_github_username(verbose) -fork_repo("owner/repo", "fork-name", verbose) - -# File operations -from exercise_utils.file import create_or_update_file, append_to_file -create_or_update_file("file.txt", "content") -append_to_file("log.txt", "new entry\n") - -# Start tag (always last in download.py) -from exercise_utils.gitmastery import create_start_tag -create_start_tag(verbose) - -# Testing -from exercise_utils.test import GitAutograderTestLoader, assert_output -loader = GitAutograderTestLoader(REPO_NAME, verify) -with loader.start(mock_answers={}) as (test, _): - output = test.run() - assert_output(output, status, messages) -``` +**Most common functions**: +- Git: `init()`, `add()`, `commit()`, `checkout()`, `merge()`, `push()` +- GitHub: `fork_repo()`, `get_github_username()`, `clone_repo_with_gh()` +- File: `create_or_update_file()`, `append_to_file()` +- Start tag: `create_start_tag()` (always last in download.py) +- Testing: `GitAutograderTestLoader`, `assert_output()` -## Common Patterns +**Examples**: See [grocery_shopping/download.py](../../../grocery_shopping/download.py) and [amateur_detective/test_verify.py](../../../amateur_detective/test_verify.py). -### Complete Exercise Setup -```python -from exercise_utils.git import init, add, commit -from exercise_utils.file import create_or_update_file -from exercise_utils.gitmastery import create_start_tag - -def download(verbose: bool): - create_or_update_file("README.md", "# Project\n") - init(verbose) - add(["README.md"], verbose) - commit("Initial commit", verbose) - create_start_tag(verbose) # Always last -``` +## Common Patterns -### GitHub Fork Pattern -```python -from exercise_utils.github_cli import get_github_username, fork_repo -from exercise_utils.git import clone_repo_with_git +**Complete exercise setup**: See [grocery_shopping/download.py](../../../grocery_shopping/download.py) -username = get_github_username(verbose) -fork_repo("git-mastery/repo", "my-fork", verbose) -clone_repo_with_git(f"https://github.com/{username}/my-fork", verbose) -``` +**GitHub fork pattern**: See [fork_repo/download.py](../../../fork_repo/download.py) ## Key Principles diff --git a/.claude/skills/exercise-utils/test-module.md b/.claude/skills/exercise-utils/test-module.md index c530853e..b87bdbeb 100644 --- a/.claude/skills/exercise-utils/test-module.md +++ b/.claude/skills/exercise-utils/test-module.md @@ -16,10 +16,4 @@ See test files: - [grocery_shopping/test_verify.py](../../../grocery_shopping/test_verify.py) ## Loader Pattern -```python -loader = GitAutograderTestLoader("exercise-name", verify) -with loader.start() as (test, rs): - # setup with rs (RepoSmith) - output = test.run() - assert_output(output, GitAutograderStatus.SUCCESSFUL) -``` +See [amateur_detective/test_verify.py](../../../amateur_detective/test_verify.py) and [grocery_shopping/test_verify.py](../../../grocery_shopping/test_verify.py) for complete examples. From 84fdb1045040fc049471628598ba4efea43429ac Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Wed, 28 Jan 2026 21:26:43 +0800 Subject: [PATCH 16/18] Improve consistency --- .claude/skills/coding-standards/SKILL.md | 27 ++++++++++++++----- .claude/skills/exercise-development/SKILL.md | 9 +++++++ .../standard-exercises.md | 11 ++++++++ .claude/skills/exercise-utils/SKILL.md | 7 +++++ .claude/skills/exercise-utils/cli-module.md | 11 +++----- .claude/skills/exercise-utils/file-module.md | 7 +++-- .claude/skills/exercise-utils/git-module.md | 10 +++---- .../skills/exercise-utils/github-module.md | 10 +++---- .../exercise-utils/gitmastery-module.md | 6 ++--- .claude/skills/exercise-utils/test-module.md | 8 +++--- .claude/skills/project-overview/SKILL.md | 5 ++++ AGENTS.md | 21 +++++++++++---- 12 files changed, 91 insertions(+), 41 deletions(-) diff --git a/.claude/skills/coding-standards/SKILL.md b/.claude/skills/coding-standards/SKILL.md index 8013f890..9c245b9d 100644 --- a/.claude/skills/coding-standards/SKILL.md +++ b/.claude/skills/coding-standards/SKILL.md @@ -5,6 +5,13 @@ description: Code style and quality guidelines. Use when writing or reviewing co # Coding Standards +## Prerequisites + +**This skill is standalone** - you can use it directly for code review and style checks. + +**Referenced by**: +- **[exercise-development](../exercise-development/SKILL.md)** - Uses these standards during exercise creation + ## Tools ```bash ruff format . # Format code @@ -44,6 +51,13 @@ One-line docstrings for simple functions. See [exercise_utils/git.py](../../../e - **Context managers**: Use `with` for resources - **Constants at top**: Module-level `UPPER_CASE` +## Common Mistakes to Avoid +- ❌ Calling `git` directly instead of using exercise_utils/git.py +- ❌ Forgetting to call `create_start_tag()` at end of download.py +- ❌ Not using `verbose` parameter in utility functions +- ❌ Hardcoding paths instead of using `Path(__file__).parent` +- ❌ Creating test without `test_` prefix + ## Anti-Patterns **❌ Don't**: @@ -60,12 +74,13 @@ One-line docstrings for simple functions. See [exercise_utils/git.py](../../../e ## Pre-Commit Checklist -- ✓ `ruff format .` - Code formatted -- ✓ `ruff check .` - No lint errors -- ✓ `mypy .` - Type hints valid -- ✓ Docstrings on public functions -- ✓ No hardcoded values -- ✓ Imports organized +- [ ] Ran `ruff format .` +- [ ] Ran `ruff check .` +- [ ] Ran `mypy /` +- [ ] All tests pass: `./test.sh ` +- [ ] Docstrings on public functions +- [ ] No hardcoded values +- [ ] Imports organized ## Related Skills diff --git a/.claude/skills/exercise-development/SKILL.md b/.claude/skills/exercise-development/SKILL.md index edad81e8..1d66d037 100644 --- a/.claude/skills/exercise-development/SKILL.md +++ b/.claude/skills/exercise-development/SKILL.md @@ -5,6 +5,15 @@ description: Guidelines for creating and modifying Git-Mastery exercises. Use wh # Exercise Development +## Prerequisites + +**Recommended to read first**: +- **[project-overview](../project-overview/SKILL.md)** - Understand repository structure and conventions + +**Referenced by this skill**: +- **[exercise-utils](../exercise-utils/SKILL.md)** - Utility functions you'll use in download.py +- **[coding-standards](../coding-standards/SKILL.md)** - Code style for quality checks + ## Quick Guide ### Two Types of Content diff --git a/.claude/skills/exercise-development/standard-exercises.md b/.claude/skills/exercise-development/standard-exercises.md index 4ec8f288..aba63843 100644 --- a/.claude/skills/exercise-development/standard-exercises.md +++ b/.claude/skills/exercise-development/standard-exercises.md @@ -154,6 +154,11 @@ pytest /test_verify.py::test_name -s -vv 2. Check mock answers match validation rules 3. Verify `REPOSITORY_NAME` matches directory name 4. Ensure imports are correct +5. **Common patterns**: + - Forgot to call `loader.start()` before `test.run()` + - Mock answers dictionary keys don't match question text exactly + - Test function missing `test_` prefix + - Wrong exercise name in `GitAutograderTestLoader` ### Download Script Errors 1. Check `__requires_git__` and `__requires_github__` flags @@ -167,6 +172,12 @@ pytest /test_verify.py::test_name -s -vv 3. Ensure correct status returned 4. Test with actual exercise setup +### Style/Quality Issues +See [coding-standards skill](../coding-standards/SKILL.md) for: +- Formatting with ruff +- Type checking with mypy +- Code style guidelines + ## Pre-Submission Checklist - ✓ Exercise discussion approved diff --git a/.claude/skills/exercise-utils/SKILL.md b/.claude/skills/exercise-utils/SKILL.md index 89869b92..2a83c258 100644 --- a/.claude/skills/exercise-utils/SKILL.md +++ b/.claude/skills/exercise-utils/SKILL.md @@ -5,6 +5,13 @@ description: Reference for shared utility modules. Use when working with Git com # Exercise Utils Reference +## Prerequisites + +**Context** (optional): +- **[project-overview](../project-overview/SKILL.md)** - Helpful for understanding how utilities fit into repository structure + +**This skill is standalone** - you can use it directly when working with utility functions. + ## Overview The `exercise_utils/` package provides reusable wrappers for common operations. **Always use these instead of raw subprocess calls.** diff --git a/.claude/skills/exercise-utils/cli-module.md b/.claude/skills/exercise-utils/cli-module.md index 4af3d710..14a1fc98 100644 --- a/.claude/skills/exercise-utils/cli-module.md +++ b/.claude/skills/exercise-utils/cli-module.md @@ -3,15 +3,12 @@ ## Overview Utilities for running CLI commands with error handling. -See [exercise_utils/cli.py](../../../exercise_utils/cli.py) for implementation. +**When to use**: Low-level CLI execution - typically you'll use git.py or github_cli.py wrappers instead. ## Key Functions -- `run(command, verbose, env={}, exit_on_error=False)` → `CommandResult` - - Returns result with `.stdout`, `.returncode`, `.is_success()` -- `run_command(command, verbose)` → `str | exits` - - Simple runner, exits on failure -- `run_command_no_exit(command, verbose)` → `str | None` - - Returns None on failure +**Core operations**: run commands with error handling, capture output, control exit behavior + +Full API: [exercise_utils/cli.py](../../../exercise_utils/cli.py) ## Usage Examples See usages in: diff --git a/.claude/skills/exercise-utils/file-module.md b/.claude/skills/exercise-utils/file-module.md index bef82988..bc6be260 100644 --- a/.claude/skills/exercise-utils/file-module.md +++ b/.claude/skills/exercise-utils/file-module.md @@ -3,13 +3,12 @@ ## Overview File creation and modification utilities. -See [exercise_utils/file.py](../../../exercise_utils/file.py) for implementation. +**When to use**: Creating files in download.py scripts - handles auto-dedenting and directory creation. ## Functions -- `create_or_update_file(filepath, contents=None)` - Create/overwrite file, auto-creates directories -- `append_to_file(filepath, contents)` - Append content to file +**Core operations**: create/update files, append content, auto-dedent with textwrap -**Features:** Auto-dedenting with `textwrap.dedent()`, automatic directory creation. +Full API: [exercise_utils/file.py](../../../exercise_utils/file.py) ## Usage Examples See usages in download.py files across exercises. diff --git a/.claude/skills/exercise-utils/git-module.md b/.claude/skills/exercise-utils/git-module.md index fa8bf460..f5606ed8 100644 --- a/.claude/skills/exercise-utils/git-module.md +++ b/.claude/skills/exercise-utils/git-module.md @@ -3,11 +3,9 @@ ## Overview Wrapper functions for Git CLI commands. -See [exercise_utils/git.py](../../../exercise_utils/git.py) for all functions. +**When to use**: For all Git operations - never call `git` directly via subprocess or shell commands. ## Functions -- `init(verbose)`, `add(files, verbose)`, `commit(message, verbose)`, `empty_commit(message, verbose)` -- `checkout(branch, create_branch, verbose)`, `merge(target_branch, ff, verbose)` -- `tag(tag_name, verbose)`, `tag_with_options(tag_name, options, verbose)` -- `add_remote(remote, url, verbose)`, `remove_remote(remote, verbose)`, `push(remote, branch, verbose)` -- `track_remote_branch(remote, branch, verbose)`, `clone_repo_with_git(url, verbose, name=None)` +**Core operations**: init, add, commit, checkout, merge, push, tag, remote management + +Full API: [exercise_utils/git.py](../../../exercise_utils/git.py) diff --git a/.claude/skills/exercise-utils/github-module.md b/.claude/skills/exercise-utils/github-module.md index fe5574aa..c29cc0b9 100644 --- a/.claude/skills/exercise-utils/github-module.md +++ b/.claude/skills/exercise-utils/github-module.md @@ -3,13 +3,11 @@ ## Overview Wrapper functions for GitHub CLI (gh) commands. -See [exercise_utils/github_cli.py](../../../exercise_utils/github_cli.py) for all functions. +**When to use**: For GitHub operations requiring authentication (create/delete repos, forks, etc.). ## Functions -- `get_github_username(verbose)` → `str` -- `create_repo(repository_name, verbose)`, `delete_repo(repository_name, verbose)`, `clone_repo_with_gh(repository_name, verbose, name=None)` -- `fork_repo(repository_name, fork_name, verbose, default_branch_only=True)` -- `has_repo(repo_name, is_fork, verbose)` → `bool`, `has_fork(repository_name, owner, username, verbose)` → `bool` -- `get_fork_name(repository_name, owner, username, verbose)` → `str` +**Core operations**: repo management, forking, cloning, user queries + +Full API: [exercise_utils/github_cli.py](../../../exercise_utils/github_cli.py) **Prerequisites:** GitHub CLI (`gh`) installed and authenticated. diff --git a/.claude/skills/exercise-utils/gitmastery-module.md b/.claude/skills/exercise-utils/gitmastery-module.md index 33d22394..fb97fa76 100644 --- a/.claude/skills/exercise-utils/gitmastery-module.md +++ b/.claude/skills/exercise-utils/gitmastery-module.md @@ -3,11 +3,11 @@ ## Overview Git-Mastery specific utility for creating start tags. -See [exercise_utils/gitmastery.py](../../../exercise_utils/gitmastery.py) for implementation. +**When to use**: After creating initial repository state, before student modifications. ## Function -`create_start_tag(verbose)` - Creates tag `git-mastery-start-` on first commit. +**Core operation**: create_start_tag() - tags first commit for exercise reset -**When to use:** After creating initial repository state, before student modifications. +Full API: [exercise_utils/gitmastery.py](../../../exercise_utils/gitmastery.py) **Testing alternative:** Use `GitMasteryHelper` in tests (see test-module.md). diff --git a/.claude/skills/exercise-utils/test-module.md b/.claude/skills/exercise-utils/test-module.md index b87bdbeb..5a9b9e40 100644 --- a/.claude/skills/exercise-utils/test-module.md +++ b/.claude/skills/exercise-utils/test-module.md @@ -3,12 +3,12 @@ ## Overview Testing utilities for exercise validation. -See [exercise_utils/test.py](../../../exercise_utils/test.py) for implementation. +**When to use**: Writing test_verify.py files - provides loader pattern and assertion helpers. 5 test scenarios are required to ensure comprehensive coverage: no answers, partial, wrong, mixed, and all correct. ## Core Classes -- `GitAutograderTestLoader(exercise_name, grade_func)` - Test runner factory -- `GitMasteryHelper` - Repo-smith helper with `create_start_tag()` method -- `assert_output(output, expected_status, expected_comments=[])` - Assertion helper +**Core operations**: test loader factory, Git-Mastery helper, output assertions + +Full API: [exercise_utils/test.py](../../../exercise_utils/test.py) ## Usage Examples See test files: diff --git a/.claude/skills/project-overview/SKILL.md b/.claude/skills/project-overview/SKILL.md index 9713902f..74db3541 100644 --- a/.claude/skills/project-overview/SKILL.md +++ b/.claude/skills/project-overview/SKILL.md @@ -9,6 +9,11 @@ user-invocable: false ## Overview 40+ modular Git exercises with automated validation using pytest and git-autograder. +## Prerequisites for Contributing +- **GitHub Issue Templates**: Use "exercise discussion" template for proposals, "request exercise repository" for remote repos +- **Pull Requests**: Must reference an approved issue from exercise discussion +- **Approval Required**: Wait for maintainer approval before implementing exercises + ## Exercise Types ### Standard Exercises (40+ directories) diff --git a/AGENTS.md b/AGENTS.md index b859539f..579d5c3d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,10 +10,10 @@ This repository uses the **Agent Skills** standard for AI documentation. All det 3. Reference actual code files linked in skills **Available skills**: -- **[project-overview](file:.claude/skills/project-overview/SKILL.md)**: Repository overview, structure, dependencies -- **[exercise-development](file:.claude/skills/exercise-development/SKILL.md)**: Creating exercises (standard vs hands-on) -- **[exercise-utils](file:.claude/skills/exercise-utils/SKILL.md)**: Utility modules API reference -- **[coding-standards](file:.claude/skills/coding-standards/SKILL.md)**: Code style, naming, type hints +- **[project-overview](.claude/skills/project-overview/SKILL.md)**: Repository overview, structure, dependencies +- **[exercise-development](.claude/skills/exercise-development/SKILL.md)**: Creating exercises (standard vs hands-on) +- **[exercise-utils](.claude/skills/exercise-utils/SKILL.md)**: Utility modules API reference +- **[coding-standards](.claude/skills/coding-standards/SKILL.md)**: Code style, naming, type hints **When to load each skill**: - Creating/modifying exercises → `exercise-development` @@ -21,6 +21,17 @@ This repository uses the **Agent Skills** standard for AI documentation. All det - Code review/formatting → `coding-standards` - Understanding repo structure → `project-overview` +**Recommended workflow**: +- **Load only skills needed** for current task (don't load all 4 unnecessarily) +- **Read supporting docs** (*.md files) only when main SKILL.md references them +- **Verify example links** before using code patterns to ensure they match current implementation + +**Skill dependencies**: +- **First time?** → Start with `project-overview` +- **Creating exercise?** → `exercise-development` (references `exercise-utils`, `coding-standards`) +- **Using utilities?** → `exercise-utils` (standalone) +- **Code review?** → `coding-standards` (standalone) + ## For Human Developers **Working with AI agents**: Simply state your task. AI will read AGENTS.md, identify needed skills, and load them automatically. @@ -35,7 +46,7 @@ This repository uses the **Agent Skills** standard for AI documentation. All det "Review this code against our standards" -"Review this code against coding standards in .claude/skills/coding-standards/" +"Review this code against coding-standards skill" ``` ### What AI Knows From Skills From 8b95325215a977ec28644b88b68ba6cfbc2d23ae Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Wed, 28 Jan 2026 21:40:43 +0800 Subject: [PATCH 17/18] Change to snake_case --- .claude/skills/coding-standards/SKILL.md | 2 +- .claude/skills/project-overview/SKILL.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude/skills/coding-standards/SKILL.md b/.claude/skills/coding-standards/SKILL.md index 9c245b9d..41072815 100644 --- a/.claude/skills/coding-standards/SKILL.md +++ b/.claude/skills/coding-standards/SKILL.md @@ -31,7 +31,7 @@ mypy / # Type checking - **Constants**: `UPPER_SNAKE_CASE` - **Classes**: `PascalCase` - **Tests**: `test_description` -- **Directories**: `kebab-case` +- **Directories**: `snake-case` - Actions: `create_*`, `add_*`, `remove_*` - Queries: `get_*`, `has_*`, `is_*` diff --git a/.claude/skills/project-overview/SKILL.md b/.claude/skills/project-overview/SKILL.md index 74db3541..026dc693 100644 --- a/.claude/skills/project-overview/SKILL.md +++ b/.claude/skills/project-overview/SKILL.md @@ -163,7 +163,7 @@ def test_something(): ### File Organization - **One exercise per directory**: No shared state between exercises - **Self-contained resources**: All exercise resources in `res/` subdirectory -- **Standard naming**: Use kebab-case for exercise directories +- **Standard naming**: Use snake-case for exercise directories - **Python packages**: Every exercise directory has `__init__.py` ### Git Operations From eef3db2b0793e343cab509f2e5d6ca518cc62f81 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Sun, 1 Feb 2026 18:03:10 +0800 Subject: [PATCH 18/18] Remove coding and overview skill --- .claude/skills/coding-standards/SKILL.md | 88 ------- .claude/skills/exercise-development/SKILL.md | 36 +-- .../exercise-development/hands-on-scripts.md | 26 +- .../standard-exercises.md | 10 +- .claude/skills/exercise-utils/SKILL.md | 21 +- .claude/skills/project-overview/SKILL.md | 235 ------------------ AGENTS.md | 100 ++++---- 7 files changed, 85 insertions(+), 431 deletions(-) delete mode 100644 .claude/skills/coding-standards/SKILL.md delete mode 100644 .claude/skills/project-overview/SKILL.md diff --git a/.claude/skills/coding-standards/SKILL.md b/.claude/skills/coding-standards/SKILL.md deleted file mode 100644 index 41072815..00000000 --- a/.claude/skills/coding-standards/SKILL.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -name: coding-standards -description: Code style and quality guidelines. Use when writing or reviewing code to ensure consistency. ---- - -# Coding Standards - -## Prerequisites - -**This skill is standalone** - you can use it directly for code review and style checks. - -**Referenced by**: -- **[exercise-development](../exercise-development/SKILL.md)** - Uses these standards during exercise creation - -## Tools -```bash -ruff format . # Format code -ruff check . # Check linting -ruff check --fix . # Auto-fix issues -mypy / # Type checking -``` - -## Style -- **88 char** line length -- **4 spaces** indentation -- **Double quotes** for strings -- 2 blank lines between functions - -## Naming -- **Functions/Variables**: `snake_case` -- **Constants**: `UPPER_SNAKE_CASE` -- **Classes**: `PascalCase` -- **Tests**: `test_description` -- **Directories**: `snake-case` -- Actions: `create_*`, `add_*`, `remove_*` -- Queries: `get_*`, `has_*`, `is_*` - -## Type Hints -Always include on function signatures. See [exercise_utils/git.py](../../../exercise_utils/git.py) for examples. - -## Imports -Order: stdlib → third-party → local (blank lines between). See any exercise file for examples like [grocery_shopping/verify.py](../../../grocery_shopping/verify.py). - -## Documentation -One-line docstrings for simple functions. See [exercise_utils/git.py](../../../exercise_utils/git.py) for examples. - -## Best Practices -- **DRY**: Extract common logic -- **Early returns**: Check errors first -- **Single responsibility**: Functions < 50 lines, < 5 params -- **Context managers**: Use `with` for resources -- **Constants at top**: Module-level `UPPER_CASE` - -## Common Mistakes to Avoid -- ❌ Calling `git` directly instead of using exercise_utils/git.py -- ❌ Forgetting to call `create_start_tag()` at end of download.py -- ❌ Not using `verbose` parameter in utility functions -- ❌ Hardcoding paths instead of using `Path(__file__).parent` -- ❌ Creating test without `test_` prefix - -## Anti-Patterns - -**❌ Don't**: -- Raw subprocess calls -- Missing type hints -- Magic values/hardcoded numbers - -**✅ Do**: -- Use exercise_utils wrappers (see [exercise_utils/git.py](../../../exercise_utils/git.py)) -- Add type hints on all functions -- Use named constants - -**Examples**: See [grocery_shopping/download.py](../../../grocery_shopping/download.py) for proper patterns. - -## Pre-Commit Checklist - -- [ ] Ran `ruff format .` -- [ ] Ran `ruff check .` -- [ ] Ran `mypy /` -- [ ] All tests pass: `./test.sh ` -- [ ] Docstrings on public functions -- [ ] No hardcoded values -- [ ] Imports organized - -## Related Skills - -- **[exercise-development](../exercise-development/SKILL.md)** - Applying standards -- **[exercise-utils](../exercise-utils/SKILL.md)** - Using utilities correctly diff --git a/.claude/skills/exercise-development/SKILL.md b/.claude/skills/exercise-development/SKILL.md index 1d66d037..83102073 100644 --- a/.claude/skills/exercise-development/SKILL.md +++ b/.claude/skills/exercise-development/SKILL.md @@ -5,15 +5,6 @@ description: Guidelines for creating and modifying Git-Mastery exercises. Use wh # Exercise Development -## Prerequisites - -**Recommended to read first**: -- **[project-overview](../project-overview/SKILL.md)** - Understand repository structure and conventions - -**Referenced by this skill**: -- **[exercise-utils](../exercise-utils/SKILL.md)** - Utility functions you'll use in download.py -- **[coding-standards](../coding-standards/SKILL.md)** - Code style for quality checks - ## Quick Guide ### Two Types of Content @@ -32,22 +23,22 @@ description: Guidelines for creating and modifying Git-Mastery exercises. Use wh | Use Standard Exercise When... | Use Hands-On Script When... | |------------------------------|----------------------------| -| ✓ Need to assess understanding | ✓ Just demonstrating a concept | -| ✓ Have specific success criteria | ✓ Exploring open-ended scenarios | -| ✓ Want automated grading | ✓ Showing command effects | -| ✓ Building structured curriculum | ✓ Quick experimentation | +| Need to assess understanding | Just demonstrating a concept | +| Have specific success criteria | Exploring open-ended scenarios | +| Want automated grading | Showing command effects | +| Building structured curriculum | Quick experimentation | ## Detailed Guides ### Standard Exercises -📄 **[standard-exercises.md](standard-exercises.md)** - Complete guide to creating exercises with validation +**[standard-exercises.md](standard-exercises.md)** - Complete guide to creating exercises with validation - Proposal and approval process - Scaffolding with `./new.sh` - Implementing download.py, verify.py, test_verify.py, README.md - Testing and troubleshooting ### Hands-On Scripts -📄 **[hands-on-scripts.md](hands-on-scripts.md)** - Quick guide to creating demonstration scripts +**[hands-on-scripts.md](hands-on-scripts.md)** - Quick guide to creating demonstration scripts - When and why to create - Implementation steps - Common patterns @@ -76,14 +67,13 @@ ruff format . && ruff check . && mypy / ### Create Hands-On Script ```bash -# 1. Create file -touch hands_on/my_demo.py - -# 2. Implement download(verbose: bool) -# Add __requires_git__ and __requires_github__ +# Use scaffolding +./new.sh +# Choose "hands-on" -# 3. Test manually -python hands_on/my_demo.py +# Implement download(verbose: bool) function +# Test manually +python hands_on/.py ``` ## Exercise Conventions @@ -121,5 +111,3 @@ python hands_on/my_demo.py ## Related Skills - **[exercise-utils](../exercise-utils/SKILL.md)** - Utility functions reference -- **[coding-standards](../coding-standards/SKILL.md)** - Code style guidelines -- **[project-overview](../project-overview/SKILL.md)** - Repository structure diff --git a/.claude/skills/exercise-development/hands-on-scripts.md b/.claude/skills/exercise-development/hands-on-scripts.md index 4d1e6629..f89ed9ac 100644 --- a/.claude/skills/exercise-development/hands-on-scripts.md +++ b/.claude/skills/exercise-development/hands-on-scripts.md @@ -5,20 +5,32 @@ Hands-on scripts are simple demonstration scripts that show Git operations witho ## When to Create Hands-On Scripts Use hands-on scripts when: -- **Demonstrating** how a Git command works -- **Showing effects** of operations (e.g., what happens when you delete a branch) -- **Exploratory learning** without right/wrong answers -- **Quick demonstrations** that don't need validation -- **Teaching through observation** rather than assessment +- Demonstrating how a Git command works +- Showing effects of operations (e.g., what happens when you delete a branch) +- Exploratory learning without right/wrong answers +- Quick demonstrations that don't need validation +- Teaching through observation rather than assessment ## Implementation Steps ### 1. Create Script File -Simply create a new `.py` file in the `hands_on/` directory: +Use the scaffolding script: ```bash -# No scaffolding needed - just create the file +./new.sh +# Choose "hands-on" or "h" +``` + +**Prompts:** +1. **Hands-on name**: Enter name after "hp-" prefix (e.g., entering "branch-demo" creates "hp-branch-demo") +2. **Requires Git?**: Default yes (if script uses Git commands) +3. **Requires GitHub?**: Default yes (set to no if script doesn't need GitHub CLI) + +**Generated file**: `hands_on/.py` with `__requires_git__`, `__requires_github__`, and `download(verbose: bool)` function + +**Manual creation** (if needed): +```bash touch hands_on/my_demo.py ``` diff --git a/.claude/skills/exercise-development/standard-exercises.md b/.claude/skills/exercise-development/standard-exercises.md index aba63843..e915353a 100644 --- a/.claude/skills/exercise-development/standard-exercises.md +++ b/.claude/skills/exercise-development/standard-exercises.md @@ -173,10 +173,12 @@ pytest /test_verify.py::test_name -s -vv 4. Test with actual exercise setup ### Style/Quality Issues -See [coding-standards skill](../coding-standards/SKILL.md) for: -- Formatting with ruff -- Type checking with mypy -- Code style guidelines +Run quality checks: +- Format: `ruff format .` +- Lint: `ruff check .` +- Type check: `mypy /` + +See [AGENTS.md](../../../AGENTS.md) for coding standards. ## Pre-Submission Checklist diff --git a/.claude/skills/exercise-utils/SKILL.md b/.claude/skills/exercise-utils/SKILL.md index 2a83c258..c82a89ff 100644 --- a/.claude/skills/exercise-utils/SKILL.md +++ b/.claude/skills/exercise-utils/SKILL.md @@ -5,30 +5,22 @@ description: Reference for shared utility modules. Use when working with Git com # Exercise Utils Reference -## Prerequisites - -**Context** (optional): -- **[project-overview](../project-overview/SKILL.md)** - Helpful for understanding how utilities fit into repository structure - -**This skill is standalone** - you can use it directly when working with utility functions. - -## Overview The `exercise_utils/` package provides reusable wrappers for common operations. **Always use these instead of raw subprocess calls.** ## Quick Navigation ### Module References -📄 **[git-module.md](git-module.md)** - Git command wrappers (`init`, `add`, `commit`, `checkout`, `merge`, `push`, etc.) +**[git-module.md](git-module.md)** - Git command wrappers (`init`, `add`, `commit`, `checkout`, `merge`, `push`, etc.) -📄 **[github-module.md](github-module.md)** - GitHub CLI wrappers (`fork_repo`, `create_repo`, `delete_repo`, etc.) +**[github-module.md](github-module.md)** - GitHub CLI wrappers (`fork_repo`, `create_repo`, `delete_repo`, etc.) -📄 **[cli-module.md](cli-module.md)** - General CLI execution (`run`, `run_command`, `CommandResult`) +**[cli-module.md](cli-module.md)** - General CLI execution (`run`, `run_command`, `CommandResult`) -📄 **[file-module.md](file-module.md)** - File operations (`create_or_update_file`, `append_to_file`) +**[file-module.md](file-module.md)** - File operations (`create_or_update_file`, `append_to_file`) -📄 **[gitmastery-module.md](gitmastery-module.md)** - Git-Mastery specific (`create_start_tag`) +**[gitmastery-module.md](gitmastery-module.md)** - Git-Mastery specific (`create_start_tag`) -📄 **[test-module.md](test-module.md)** - Test scaffolding (`GitAutograderTestLoader`, `assert_output`) +**[test-module.md](test-module.md)** - Test scaffolding (`GitAutograderTestLoader`, `assert_output`) ## Quick Reference @@ -68,4 +60,3 @@ cli.py # No dependencies (base module) ## Related Skills - **[exercise-development](../exercise-development/SKILL.md)** - Using utilities in exercises -- **[coding-standards](../coding-standards/SKILL.md)** - Code conventions diff --git a/.claude/skills/project-overview/SKILL.md b/.claude/skills/project-overview/SKILL.md deleted file mode 100644 index 026dc693..00000000 --- a/.claude/skills/project-overview/SKILL.md +++ /dev/null @@ -1,235 +0,0 @@ ---- -name: project-overview -description: Overview of Git-Mastery exercises repository. Use when first learning about the project. -user-invocable: false ---- - -# Git-Mastery Exercises - -## Overview -40+ modular Git exercises with automated validation using pytest and git-autograder. - -## Prerequisites for Contributing -- **GitHub Issue Templates**: Use "exercise discussion" template for proposals, "request exercise repository" for remote repos -- **Pull Requests**: Must reference an approved issue from exercise discussion -- **Approval Required**: Wait for maintainer approval before implementing exercises - -## Exercise Types - -### Standard Exercises (40+ directories) -``` -/ -├── download.py # Setup logic -├── verify.py # Validation with git-autograder -├── test_verify.py # Pytest tests -├── README.md # Instructions -└── res/ # Optional resources -``` - -**Categories**: -- History: `amateur_detective/`, `view_commits/` -- Branching: `branch_bender/`, `branch_delete/`, `bonsai_tree/` -- Working Dir: `sensors_checkout/`, `sensors_diff/`, `sensors_reset/` -- Staging: `staging_intervention/`, `stage_fright/` -- Merging: `conflict_mediator/`, `merge_squash/` -- Remotes: `fetch_and_pull/`, `push_over/`, `fork_repo/` -- Tags: `tags_add/`, `tags_push/` - -### Hands-On Scripts (`hands_on/`) -Single-file demonstrations without validation. Examples: `add_files.py`, `branch_delete.py`. - -## Shared Utilities (`exercise_utils/`) -- **git.py**: Git command wrappers -- **github_cli.py**: GitHub CLI wrappers -- **cli.py**: CLI execution helpers -- **gitmastery.py**: Start tag creation -- **test.py**: Test scaffolding -- **file.py**: File operations - -## Dependencies -- Python 3.13+ -- git-autograder 6.*, repo-smith, pytest -- ruff, mypy (dev tools) -- Git CLI, GitHub CLI (gh) - -## Common Commands -```bash -./setup.sh # Setup venv -./test.sh # Test exercise -pytest . -s -vv # Test all -ruff format . && ruff check . # Format & lint -./new.sh # Create exercise -``` - -Or use the provided script: -```bash -./setup.sh -``` - -#### Testing Exercises -```bash -# Test specific exercise -./test.sh - -# Example -./test.sh amateur_detective - -# This runs: python -m pytest /test_verify.py -s -vv -``` - -#### Creating New Exercises -```bash -# Use scaffolding script -./new.sh - -# You'll be prompted for: -# 1. Exercise name (kebab-case recommended) -# 2. Tags (space-separated) -# 3. Exercise configuration -``` - -The script generates the complete exercise structure with all required files. - -#### Testing Downloads -```bash -# Test all exercise download scripts -./test-download.sh -``` - -### 5. Exercise Configuration - -Each exercise has a `.gitmastery-exercise.json` file -```json -{ - "exercise_name": "amateur-detective", - "tags": [ - "git-status" - ], - "requires_git": true, - "requires_github": false, - "base_files": { - "answers.txt": "answers.txt" - }, - "exercise_repo": { - "repo_type": "local", - "repo_name": "crime-scene", - "repo_title": null, - "create_fork": null, - "init": true - } -} -``` - -### 6. Validation Patterns - -Exercises use composable validation with git-autograder: - -```python -from git_autograder import GitAutograderExercise, GitAutograderOutput -from git_autograder.answers.rules import HasExactValueRule, NotEmptyRule - -def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - exercise.answers.add_validation(QUESTION, NotEmptyRule()) - exercise.answers.add_validation(QUESTION, HasExactValueRule("expected")) - exercise.answers.validate() - - return exercise.to_output( - ["Success message"], - GitAutograderStatus.SUCCESSFUL - ) -``` - -### 7. Test Patterns - -Tests use `GitAutograderTestLoader` for standardized testing: - -```python -from exercise_utils.test import GitAutograderTestLoader, assert_output - -loader = GitAutograderTestLoader(REPOSITORY_NAME, verify) - -def test_something(): - with loader.start(mock_answers={QUESTION: "answer"}) as (test, _): - output = test.run() - assert_output( - output, - GitAutograderStatus.SUCCESSFUL, - ["Expected message"] - ) -``` - -## Important Conventions - -### File Organization -- **One exercise per directory**: No shared state between exercises -- **Self-contained resources**: All exercise resources in `res/` subdirectory -- **Standard naming**: Use snake-case for exercise directories -- **Python packages**: Every exercise directory has `__init__.py` - -### Git Operations -- **Use wrappers**: Always use `exercise_utils/git.py` functions, not direct subprocess calls -- **Start tags**: Create using `create_start_tag()` after repository setup -- **Verbose mode**: All utility functions support verbose parameter for debugging - -### Testing -- **Test verification logic**: Every `verify.py` must have corresponding `test_verify.py` -- **Use test loader**: Leverage `GitAutograderTestLoader` for consistent test structure -- **Mock answers**: Test various scenarios with different mock answer combinations -- **Assertions**: Use `assert_output()` for standardized validation checking - -### Code Quality -- **Type hints**: Use type annotations for all function parameters and returns -- **Docstrings**: Document all utility functions with clear descriptions -- **Error handling**: Wrap CLI operations with proper error handling -- **Consistency**: Follow patterns from existing exercises - -## Project Integration - -### Documentation -- **README.md**: Main project documentation (currently minimal) -- **.github/CONTRIBUTING.md**: Contributor guidelines with detailed setup instructions - -### CI/CD -- GitHub Actions workflows in `.github/workflows/` -- Automated testing via `ci.yml` - -### Version Control -- **Don't modify**: `.git/`, `.github/`, top-level `README.md`, or scripts unless updating global logic -- **Exercise isolation**: Exercise logic stays in exercise directory -- **Shared improvements**: Common utilities go in `exercise_utils/` - -## Quick Reference - -### Common Tasks -- **Add new exercise**: `./new.sh` -- **Test exercise**: `./test.sh ` -- **Setup environment**: `./setup.sh` -- **Run single test**: `pytest /test_verify.py::test_name -s -vv` - -### Key Directories -- `exercise_utils/`: Shared utilities (git, github, cli, test helpers) -- `hands_on/`: Standalone demonstration scripts -- `.github/`: GitHub configuration and templates -- `test-downloads/`: Temporary directory for download testing - -### External Documentation -- Developer docs: https://git-mastery.github.io/developers -- Exercise directory: https://git-mastery.github.io/exercises -- Git-Mastery app: https://git-mastery.github.io/app - -## Working with This Repository - -When modifying this repository: - -1. **Understand exercise isolation**: Each exercise is completely independent -2. **Use shared utilities**: Leverage `exercise_utils/` for all Git/GitHub operations -3. **Test thoroughly**: Run `./test.sh ` after changes -4. **Follow patterns**: Match structure and style of existing exercises -5. **Document changes**: Update relevant documentation when adding features - -## Additional Resources - -For detailed information, see: -- [exercise-development](../exercise-development/SKILL.md) - Creating and modifying exercises -- [exercise-utils](../exercise-utils/SKILL.md) - Using shared utility modules -- [coding-standards](../coding-standards/SKILL.md) - Code style and quality guidelines diff --git a/AGENTS.md b/AGENTS.md index 579d5c3d..12a2674c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,81 +1,65 @@ # Git-Mastery Exercises - AI Agent Guide -This repository uses the **Agent Skills** standard for AI documentation. All detailed instructions are in `.claude/skills/`. +## Repository Structure -## For AI Agents +40+ modular Git exercises with automated validation using pytest and git-autograder. -**Workflow**: -1. Read this AGENTS.md to understand available skills -2. Based on your task, load relevant skills from `.claude/skills/` -3. Reference actual code files linked in skills +**Exercise types:** +- **Standard exercises** (40+ directories): Complete with download.py, verify.py, test_verify.py, README.md, res/ +- **Hands-on scripts** (hands_on/): Single-file demonstrations without validation -**Available skills**: -- **[project-overview](.claude/skills/project-overview/SKILL.md)**: Repository overview, structure, dependencies -- **[exercise-development](.claude/skills/exercise-development/SKILL.md)**: Creating exercises (standard vs hands-on) -- **[exercise-utils](.claude/skills/exercise-utils/SKILL.md)**: Utility modules API reference -- **[coding-standards](.claude/skills/coding-standards/SKILL.md)**: Code style, naming, type hints +**Shared utilities** (exercise_utils/): git.py, github_cli.py, cli.py, file.py, gitmastery.py, test.py -**When to load each skill**: -- Creating/modifying exercises → `exercise-development` -- Using utility functions → `exercise-utils` -- Code review/formatting → `coding-standards` -- Understanding repo structure → `project-overview` +## Available Skills -**Recommended workflow**: -- **Load only skills needed** for current task (don't load all 4 unnecessarily) -- **Read supporting docs** (*.md files) only when main SKILL.md references them -- **Verify example links** before using code patterns to ensure they match current implementation +**[exercise-development](.claude/skills/exercise-development/SKILL.md)**: Creating exercises (standard vs hands-on) +- References: exercise-utils, coding standards +- Use for: implementing download.py, verify.py, test_verify.py, README.md -**Skill dependencies**: -- **First time?** → Start with `project-overview` -- **Creating exercise?** → `exercise-development` (references `exercise-utils`, `coding-standards`) -- **Using utilities?** → `exercise-utils` (standalone) -- **Code review?** → `coding-standards` (standalone) +**[exercise-utils](.claude/skills/exercise-utils/SKILL.md)**: Utility modules API reference +- Standalone: git, github_cli, cli, file, gitmastery, test utilities +- Never use raw subprocess calls -## For Human Developers +## Quick Commands -**Working with AI agents**: Simply state your task. AI will read AGENTS.md, identify needed skills, and load them automatically. - -**Example tasks**: +```bash +./new.sh # Create exercise/hands-on (interactive) +./test.sh # Test one exercise +pytest . -s -vv # Test all +ruff format . && ruff check . # Format & lint ``` -"Create a new exercise about merge conflicts" - -"Fix the test in amateur_detective/test_verify.py" -"Update grocery_shopping/download.py to use exercise_utils" +## Coding Standards -"Review this code against our standards" +**Style**: 88 char lines, 4 spaces, double quotes, 2 blank lines between functions -"Review this code against coding-standards skill" -``` +**Naming**: +- Functions/Variables: snake_case +- Constants: UPPER_SNAKE_CASE +- Classes: PascalCase +- Directories: kebab-case -### What AI Knows From Skills +**Type hints**: Required on all function signatures -After loading skills, AI understands: -- **Structure**: Each exercise has download.py, verify.py, test_verify.py, README.md -- **Patterns**: Use `exercise_utils.*` instead of subprocess, call `create_start_tag()` last -- **Testing**: Required scenarios (no answers, wrong answers, success), `loader.start()` pattern -- **Standards**: 88-char lines, snake_case, type hints, import order -- **APIs**: All functions in exercise_utils/ (git.py, github_cli.py, etc.) +**Imports**: Order: stdlib, third-party, local (blank lines between) -### Tips for Best Results +**Common mistakes**: +- Don't call git directly, use exercise_utils/git.py +- Don't forget create_start_tag() at end of download.py +- Don't hardcode paths, use Path(__file__).parent +- Don't create tests without test_ prefix -1. **Be specific about which skill**: "Follow exercise-development skill for creating download.py" -2. **Reference example files**: Skills already point to examples like [amateur_detective/test_verify.py](amateur_detective/test_verify.py) -3. **Check standards before committing**: "Does this follow coding-standards skill?" +**Pre-commit**: Run ruff format, ruff check, mypy, and ./test.sh -## Quick Reference +## Contributing Workflow -```bash -./setup.sh # Setup environment -./new.sh # Create exercise (interactive) -./test.sh # Test one exercise -pytest . -s -vv # Test all -ruff format . && ruff check . # Format & lint -``` +1. Create "exercise discussion" GitHub issue +2. Wait for approval +3. Use ./new.sh to scaffold +4. Implement files +5. Test thoroughly +6. Submit PR referencing approved issue ## Tech Stack -- Python 3.13+, pytest, git-autograder, repo-smith -- ruff (lint/format), mypy (types) -- Git CLI, GitHub CLI (gh) +Python 3.13+, pytest, git-autograder 6.*, repo-smith, ruff, mypy, Git CLI, GitHub CLI (gh)