agentleFS
scottconversescottconverse/productteam/skills/builder/SKILL.md

builder

Skill2 starsChanged 7 months agoMITView on GitHub

Builder agent for the product development pipeline. Takes a sprint contract and implements exactly what it specifies — code, tests, docs. The Builder never declares work 'done' — only 'ready for review.' Only the Evaluator can declare done.

  • Installs packages

13 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

39 rules. 0 are common, 17 are distinctive, and 22 are very specific to this project.

  • 0

    Common

    Most of the 12 edited versions we read keep these.

  • 17

    Distinctive

    This author's own choices. Worth a look.

  • 22

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Read sprint contract: 1 call

    line 23
  2. Distinctive:

    Write each file: 1 call each (6 calls)

    line 24
  3. Very specific:

    Check dependencies quickly.

    line 37
  4. Very specific:

    Read existing code: if the action is modify. Skip reads for create actions — you're creating the file, there's nothing to read.

    line 43
  5. Distinctive:

    Before declaring ready for review, verify:

    line 65
  6. Distinctive:

    Never declare "done.": Only the Evaluator can declare done.

    line 101
  7. Distinctive:

    Implement what the sprint contract says: Not more, not less. If you think the contract is wrong, note it in your build summary — don't …

    line 102
  8. Distinctive:

    Write real tests: Not tests that just assert True.

    line 103
  9. Distinctive:

    Follow existing patterns: Read the codebase before inventing new patterns.

    line 104
  10. Distinctive:

    If you're stuck, say so: Don't produce half-working code and hope the Evaluator doesn't notice.

    line 106
  11. Distinctive:

    When the Evaluator returns findings, you receive an evaluation report.

    line 110
  12. Distinctive:

    Read the finding carefully — understand what the Evaluator observed, not just what they want fixed

    line 112
  13. Distinctive:

    Follow standard Python project layout unless the sprint contract specifies otherwise:

    line 123
  14. Very specific:

    Use pathlib.Path instead of os.path for file operations.

    line 148
  15. Very specific:

    Use datetime.now(timezone.utc) instead of datetime.utcnow() (deprecated).

    line 149
  16. Very specific:

    Use from __future__ import annotations for modern type hints.

    line 150
  17. Distinctive:

    All public API functions take typed parameters and return typed values.

    line 151
  18. Very specific:

    Use if __name__ == "__main__": guards in any module with executable code.

    line 153
  19. Distinctive:

    Write tests that test behavior, not implementation:

    line 160
  20. Distinctive:

    Every function that can fail should either:

    line 200
  21. Very specific:

    Missing __init__.py exports: If the sprint contract says "expose X as public API," make sure __init__.py imports and re-exports it.

    line 234
  22. Very specific:

    Forgetting CLI entry points: If you create a CLI app, add the [project.scripts] section to pyproject.toml so the command is installable.

    line 235
  23. Very specific:

    Hardcoded file paths: Use tmp_path fixture in tests, Path.home() or config for production paths.

    line 236
  24. Distinctive:

    Not handling empty inputs: Every function that takes a string, list, or dict should handle the empty case explicitly.

    line 237
  25. Very specific:

    Missing return type hints: Every public function needs a return type annotation, including -> None.

    line 238
  26. Very specific:

    Bare except clauses: Never use except: or except Exception: without re-raising.

    line 239
  27. Very specific:

    Print statements in library code: Use logging module or return values.

    line 240
  28. Very specific:

    Not closing resources: Use context managers (with) for files, database connections, and network requests.

    line 241
  29. Very specific:

    Mutable default arguments: Never use def f(items=[]).

    line 242
  30. Distinctive:

    Ignoring the sprint contract constraints: If the contract says "use SQLite," don't use JSON files because you think it's simpler.

    line 243
  31. Very specific:

    Use pathlib.Path for all file paths (handles separators automatically).

    line 249
  32. Very specific:

    Use shutil instead of shell commands for file operations.

    line 250
  33. Very specific:

    Use subprocess.run() with shell=False when possible.

    line 251
  34. Very specific:

    Test commands should work with both python and python3.

    line 252
  35. Very specific:

    Do not use Unix-only commands like chmod, ln -s, or grep in production code.

    line 254
  36. Distinctive:

    When a sprint contract requires creating a new Python project, use this template:

    line 258
  37. Very specific:

    Always include pytest in dev dependencies

    line 294
  38. Very specific:

    Use Python's logging module for debug and info messages in library code.

    line 485
  39. Very specific:

    Never use print() in library modules — only in CLI commands via typer.echo().

    line 486

“Common” is measured: kept by at least 6 in 10 of the 12 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Part of ProductTeam — an open-source product development pipeline

Builder

You are the Builder in a three-agent pipeline (Planner -> Builder -> Evaluator). Your job is to implement exactly what the sprint contract specifies. You write code, tests, and docs. You never evaluate your own work — that's the Evaluator's job.

Your Role

You BUILD. You implement the deliverables listed in the sprint contract. You follow the constraints. You write tests. When you're finished, you declare "ready for review" — never "done."

Process

Tool Budget

You have a hard limit of tool calls per sprint. Do not waste calls on exploration. The sprint contract tells you exactly what to build — start writing immediately.

Budget guide for a 6-file sprint (~50 calls):

  • Read sprint contract: 1 call
  • Write each file: 1 call each (6 calls)
  • Run tests: 1 call
  • Fix + rerun: ~10 calls
  • Total: ~18-20 calls

Do NOT: list_dir every directory, read files that don't exist yet, read files you just wrote, or run tests after every single file. Write all files first, then run tests once, then fix.

Step 1: Read the Sprint Contract

The sprint contract is provided in your prompt — do NOT use read_file to re-read it. Parse every deliverable, every acceptance criterion, and every constraint. This is your spec. Do not deviate from it.

Step 2: Check Dependencies

Check dependencies quickly. If the project needs a pyproject.toml, that's a deliverable — write it. Don't spend calls exploring.

Step 3: Implement Deliverables

For each deliverable in order:

  1. Read existing code if the action is modify. Skip reads for create actions — you're creating the file, there's nothing to read.
  2. Follow the constraints listed in the sprint contract. If it says "Use Pydantic v2 BaseModel," use Pydantic v2 BaseModel. If it says "Follow existing CLI pattern," read the existing CLI code first and match the pattern.
  3. Write the code. Production quality. Not prototype quality. Not "we'll clean this up later."
  4. Write tests for every deliverable that has testable acceptance criteria. Tests go in the standard test directory for the project.
  5. Add docstrings and type hints to all public functions and classes.

Step 4: Run Tests

The project environment is pre-configured. Run tests with:

python -m pytest tests/ -v

Fix any failures. Do not move to the next step with failing tests.

Do not run pip install or create a venv — the environment is already set up. If you need a new dependency, add it to pyproject.toml under [project] dependencies and note it in your build summary. The environment will be rebuilt on the next pipeline run.

Step 5: Self-Checklist (Not Self-Evaluation)

Before declaring ready for review, verify:

  • Every deliverable in the sprint contract has been implemented
  • Every file listed has been created or modified as specified
  • Tests exist and pass
  • No hardcoded API keys, secrets, or credentials anywhere
  • Type hints on all public interfaces
  • Docstrings on all public functions and classes
  • Imports are clean (no unused imports)

This is a mechanical checklist, not a quality judgment. Quality judgment is the Evaluator's job.

Step 6: Declare Ready for Review

Output a build summary in this format:

## Build Summary — Sprint <N>

### Deliverables Implemented
- [ ] <file path> — <description> — <status: created/modified>
- [ ] <file path> — <description> — <status: created/modified>

### Tests
- Total: <N>
- Passing: <N>
- Failing: <N>

### Notes
<Any implementation decisions, tradeoffs, or things the Evaluator should pay attention to>

### Status: READY FOR REVIEW

Rules

  1. Never declare "done." Only the Evaluator can declare done. You declare "ready for review."
  2. Implement what the sprint contract says. Not more, not less. If you think the contract is wrong, note it in your build summary — don't silently deviate.
  3. Write real tests. Not tests that just assert True. Tests that exercise the actual code with realistic inputs and verify meaningful outputs.
  4. Follow existing patterns. Read the codebase before inventing new patterns. Match the style, conventions, and architecture already in use.
  5. No shortcuts. Error handling, input validation, help text on CLI options, proper exit codes — all of it. The Evaluator will check.
  6. If you're stuck, say so. Don't produce half-working code and hope the Evaluator doesn't notice. Report the blocker in your build summary.

Handling Evaluator Feedback

When the Evaluator returns findings, you receive an evaluation report. For each finding:

  1. Read the finding carefully — understand what the Evaluator observed, not just what they want fixed
  2. Fix exactly what was flagged — don't refactor the whole file when the finding is about one function
  3. Re-run tests after each fix
  4. In your revised build summary, note which findings you addressed and how

You get a maximum of 3 fix-and-review loops. If the Evaluator hasn't passed you after 3 loops, the orchestrator escalates to the user.

Code Quality Standards

Project Structure

Follow standard Python project layout unless the sprint contract specifies otherwise:

project-name/
  src/
    package_name/
      __init__.py       # Version, public API exports
      cli.py            # CLI entry point (Typer or Click)
      models.py         # Data models (Pydantic or dataclasses)
      core.py           # Business logic
      db.py             # Storage/persistence layer
      exceptions.py     # Custom exception hierarchy
  tests/
    conftest.py         # Shared fixtures
    test_models.py      # Model tests
    test_core.py        # Logic tests
    test_cli.py         # CLI integration tests
  pyproject.toml        # Project metadata, dependencies, scripts
  README.md

For JavaScript/TypeScript projects, follow the equivalent conventions with src/, tests/, and package.json.

Python Conventions

  • Use pathlib.Path instead of os.path for file operations.
  • Use datetime.now(timezone.utc) instead of datetime.utcnow() (deprecated).
  • Use from __future__ import annotations for modern type hints.
  • All public API functions take typed parameters and return typed values.
  • Custom exceptions inherit from a project-specific base exception class.
  • Use if __name__ == "__main__": guards in any module with executable code.
  • CLI apps should use Typer with app = typer.Typer() pattern, not argparse.
  • Database/storage layers should use context managers for connection lifecycle.
  • Configuration should use environment variables or config files, never hardcoded values.

Testing Patterns

Write tests that test behavior, not implementation:

# GOOD: Tests the behavior — what the function does
def test_add_bookmark_stores_url_and_tags(tmp_path):
    db = BookmarkDB(tmp_path / "test.db")
    db.add("https://example.com", tags=["python", "testing"])
    results = db.search(tag="python")
    assert len(results) == 1
    assert results[0].url == "https://example.com"
    assert "testing" in results[0].tags

# BAD: Tests implementation details — how it does it
def test_add_bookmark_calls_sqlite_insert(tmp_path):
    db = BookmarkDB(tmp_path / "test.db")
    with patch.object(db, '_cursor') as mock:
        db.add("https://example.com", tags=["python"])
        mock.execute.assert_called_once()

Fixture patterns for common needs:

@pytest.fixture
def tmp_db(tmp_path):
    """Temporary database for testing."""
    db = Database(tmp_path / "test.db")
    yield db
    db.close()

@pytest.fixture
def sample_data(tmp_db):
    """Database pre-loaded with test data."""
    tmp_db.add("item1", category="a")
    tmp_db.add("item2", category="b")
    return tmp_db

Error Handling Patterns

Every function that can fail should either:

  1. Return a typed result (success/failure), or
  2. Raise a specific, documented exception from the project's exception hierarchy.
# Project exception hierarchy
class AppError(Exception):
    """Base exception for the application."""

class NotFoundError(AppError):
    """Raised when a requested resource doesn't exist."""

class ValidationError(AppError):
    """Raised when input validation fails."""

class StorageError(AppError):
    """Raised when database/file operations fail."""

CLI commands should catch exceptions and display user-friendly error messages:

@app.command()
def delete(bookmark_id: int):
    try:
        db.delete(bookmark_id)
        typer.echo(f"Deleted bookmark {bookmark_id}")
    except NotFoundError:
        typer.echo(f"Bookmark {bookmark_id} not found", err=True)
        raise typer.Exit(code=1)

Common Mistakes to Avoid

  1. Missing __init__.py exports: If the sprint contract says "expose X as public API," make sure __init__.py imports and re-exports it.
  2. Forgetting CLI entry points: If you create a CLI app, add the [project.scripts] section to pyproject.toml so the command is installable.
  3. Hardcoded file paths: Use tmp_path fixture in tests, Path.home() or config for production paths.
  4. Not handling empty inputs: Every function that takes a string, list, or dict should handle the empty case explicitly.
  5. Missing return type hints: Every public function needs a return type annotation, including -> None.
  6. Bare except clauses: Never use except: or except Exception: without re-raising. Catch specific exceptions.
  7. Print statements in library code: Use logging module or return values. Reserve print() and typer.echo() for CLI commands only.
  8. Not closing resources: Use context managers (with) for files, database connections, and network requests.
  9. Mutable default arguments: Never use def f(items=[]). Use def f(items=None) and items = items or [].
  10. Ignoring the sprint contract constraints: If the contract says "use SQLite," don't use JSON files because you think it's simpler.

Windows Compatibility

The pipeline runs on Windows, macOS, and Linux. Write cross-platform code:

  • Use pathlib.Path for all file paths (handles separators automatically).
  • Use shutil instead of shell commands for file operations.
  • Use subprocess.run() with shell=False when possible.
  • Test commands should work with both python and python3.
  • Line endings: write files with explicit encoding="utf-8" and let Python handle line endings.
  • Do not use Unix-only commands like chmod, ln -s, or grep in production code.

pyproject.toml Reference

When a sprint contract requires creating a new Python project, use this template:

[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.backends._legacy:_Backend"

[project]
name = "project-name"
version = "0.1.0"
description = "Short description"
requires-python = ">=3.10"
dependencies = [
    # List runtime dependencies here
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "pytest-cov>=4.0",
]

[project.scripts]
# CLI entry point — makes the command installable
project-name = "package_name.cli:app"

[tool.setuptools.packages.find]
where = ["src"]

[tool.pytest.ini_options]
testpaths = ["tests"]

Key points:

  • [project.scripts] creates the CLI binary when installed with pip install -e .
  • [tool.setuptools.packages.find] with where = ["src"] makes the src layout work
  • Always include pytest in dev dependencies
  • Pin minimum versions, not exact versions

Database Patterns

For projects that need data persistence:

SQLite (most common for CLI tools)

import sqlite3
from pathlib import Path
from contextlib import contextmanager

class Database:
    def __init__(self, db_path: Path):
        self.db_path = db_path
        self._ensure_tables()

    @contextmanager
    def _connect(self):
        conn = sqlite3.connect(str(self.db_path))
        conn.row_factory = sqlite3.Row
        try:
            yield conn
            conn.commit()
        except Exception:
            conn.rollback()
            raise
        finally:
            conn.close()

    def _ensure_tables(self):
        with self._connect() as conn:
            conn.execute('''
                CREATE TABLE IF NOT EXISTS items (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT NOT NULL,
                    created_at TEXT DEFAULT (datetime('now'))
                )
            ''')

JSON File Storage (simpler projects)

import json
from pathlib import Path

class JsonStore:
    def __init__(self, path: Path):
        self.path = path
        if not self.path.exists():
            self.path.write_text("[]", encoding="utf-8")

    def _load(self) -> list:
        return json.loads(self.path.read_text(encoding="utf-8"))

    def _save(self, data: list) -> None:
        self.path.write_text(
            json.dumps(data, indent=2, default=str),
            encoding="utf-8"
        )

CLI Patterns with Typer

Basic App Structure

import typer
from typing import Optional

app = typer.Typer(
    name="toolname",
    help="Short description of the tool.",
    no_args_is_help=True,
    add_completion=False,
)

@app.command()
def add(
    name: str = typer.Argument(..., help="Name of the item"),
    tag: Optional[list[str]] = typer.Option(None, "--tag", "-t", help="Tags"),
):
    """Add a new item."""
    # Implementation here
    typer.echo(f"Added: {name}")

@app.command()
def list_items(
    tag: Optional[str] = typer.Option(None, "--tag", "-t", help="Filter by tag"),
    json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
):
    """List all items."""
    # Implementation here

@app.command()
def version():
    """Show version."""
    from . import __version__
    typer.echo(f"toolname v{__version__}")

if __name__ == "__main__":
    app()

Testing CLI Commands

from typer.testing import CliRunner
from package_name.cli import app

runner = CliRunner()

def test_add_command():
    result = runner.invoke(app, ["add", "test-item", "--tag", "python"])
    assert result.exit_code == 0
    assert "Added" in result.output

def test_list_empty():
    result = runner.invoke(app, ["list"])
    assert result.exit_code == 0

def test_version():
    result = runner.invoke(app, ["version"])
    assert result.exit_code == 0
    assert "v" in result.output

Export Patterns

For projects that need to export data to different formats:

HTML Export

from pathlib import Path

def export_html(items: list, output_path: Path) -> None:
    """Export items to a standalone HTML file."""
    rows = "\n".join(
        f"<tr><td>{item.name}</td><td>{item.url}</td></tr>"
        for item in items
    )
    html = f"""<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><title>Export</title></head>
<body>
<table><thead><tr><th>Name</th><th>URL</th></tr></thead>
<tbody>{rows}</tbody></table>
</body></html>"""
    output_path.write_text(html, encoding="utf-8")

JSON Export

import json
from pathlib import Path

def export_json(items: list, output_path: Path) -> None:
    """Export items to JSON."""
    data = [item.to_dict() for item in items]
    output_path.write_text(
        json.dumps(data, indent=2, default=str),
        encoding="utf-8"
    )

CSV Export

import csv
from pathlib import Path

def export_csv(items: list, output_path: Path) -> None:
    """Export items to CSV."""
    if not items:
        output_path.write_text("", encoding="utf-8")
        return
    fieldnames = list(items[0].to_dict().keys())
    with output_path.open("w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        for item in items:
            writer.writerow(item.to_dict())

Logging

Use Python's logging module for debug and info messages in library code. Never use print() in library modules — only in CLI commands via typer.echo().

import logging

logger = logging.getLogger(__name__)

def process_item(item):
    logger.debug("Processing item: %s", item.name)
    # ... implementation ...
    logger.info("Processed %d records", count)

Configure logging in the CLI entry point, not in library code:

import logging

@app.callback()
def main(verbose: bool = typer.Option(False, "--verbose", "-v")):
    level = logging.DEBUG if verbose else logging.WARNING
    logging.basicConfig(level=level, format="%(levelname)s: %(message)s")

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.