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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ coverage.xml
.agentready/
*.log
*.tmp
.plans/

# Build artifacts
*.whl
Expand Down
17 changes: 17 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,23 @@ ruff check src/ tests/
black src/ tests/ && isort src/ tests/ && ruff check src/ tests/
```

### Cold-Start Prompts Pattern

**Gitignored Planning Directory**: `.plans/`

When creating cold-start prompts for features or assessors:
- Store in `.plans/` directory (gitignored, never committed)
- Each prompt is self-contained for implementation handoff
- Prompts include: requirements, implementation approach, code patterns, test guidance
- Use for creating GitHub issues with full context
- Allows LLM agents to pick up work without conversation history

**Example workflow**:
1. Generate cold-start prompt → `.plans/assessor-name.md`
2. Create GitHub issue with prompt content as body
3. Future agent reads issue → implements feature
4. Prompt stays in `.plans/` for local reference only

### Adding New Assessors

1. **Expand a stub assessor** in `src/agentready/assessors/stub_assessors.py`
Expand Down
201 changes: 201 additions & 0 deletions src/agentready/assessors/structure.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Structure assessors for project layout and separation of concerns."""

import re

from ..models.attribute import Attribute
from ..models.finding import Citation, Finding, Remediation
from ..models.repository import Repository
Expand Down Expand Up @@ -106,3 +108,202 @@ def _create_remediation(self) -> Remediation:
)
],
)


class OneCommandSetupAssessor(BaseAssessor):
"""Assesses single-command development environment setup.

Tier 2 Critical (3% weight) - One-command setup enables AI to quickly
reproduce environments and reduces onboarding friction.
"""

@property
def attribute_id(self) -> str:
return "one_command_setup"

@property
def tier(self) -> int:
return 2 # Critical

@property
def attribute(self) -> Attribute:
return Attribute(
id=self.attribute_id,
name="One-Command Build/Setup",
category="Build & Development",
tier=self.tier,
description="Single command to set up development environment from fresh clone",
criteria="Single command (make setup, npm install, etc.) documented prominently",
default_weight=0.03,
)

def assess(self, repository: Repository) -> Finding:
"""Check for single-command setup documentation and tooling.

Scoring:
- README has setup command (40%)
- Setup script/Makefile exists (30%)
- Setup in prominent location (30%)
"""
# Check if README exists
readme_path = repository.path / "README.md"
if not readme_path.exists():
return Finding.not_applicable(
self.attribute,
reason="No README found, cannot assess setup documentation",
)

score = 0
evidence = []

# Read README
try:
readme_content = readme_path.read_text()
except Exception as e:
return Finding(
attribute=self.attribute,
status="error",
score=0.0,
measured_value="error reading README",
threshold="single command documented",
evidence=[f"Error reading README: {e}"],
remediation=None,
error_message=str(e),
)

# Check 1: README has setup command (40%)
setup_command = self._find_setup_command(readme_content, repository.languages)
if setup_command:
score += 40
evidence.append(f"Setup command found in README: '{setup_command}'")
else:
evidence.append("No clear setup command found in README")

# Check 2: Setup script/Makefile exists (30%)
setup_files = self._check_setup_files(repository)
if setup_files:
score += 30
evidence.append(f"Setup automation found: {', '.join(setup_files)}")
else:
evidence.append("No Makefile or setup script found")

# Check 3: Setup in prominent location (30%)
if self._is_setup_prominent(readme_content):
score += 30
evidence.append("Setup instructions in prominent location")
else:
evidence.append("Setup instructions not in first 3 sections")

status = "pass" if score >= 75 else "fail"

return Finding(
attribute=self.attribute,
status=status,
score=score,
measured_value=setup_command or "multi-step setup",
threshold="single command",
evidence=evidence,
remediation=self._create_remediation() if status == "fail" else None,
error_message=None,
)

def _find_setup_command(self, readme_content: str, languages: dict) -> str:
"""Find setup command in README based on language.

Returns the setup command if found, empty string otherwise.
"""
# Common setup patterns by language
patterns = [
r"(?:^|\n)(?:```(?:bash|sh|shell)?\n)?([a-z\-_]+\s+(?:install|setup))",
r"(?:^|\n)(?:```(?:bash|sh|shell)?\n)?((?:make|npm|yarn|pnpm|pip|poetry|uv|cargo|go)\s+[a-z\-_]+)",
]

for pattern in patterns:
match = re.search(pattern, readme_content, re.IGNORECASE | re.MULTILINE)
if match:
return match.group(1).strip()

return ""

def _check_setup_files(self, repository: Repository) -> list:
"""Check for setup automation files."""
setup_files = []

# Check for common setup files
files_to_check = {
"Makefile": "Makefile",
"setup.sh": "shell script",
"bootstrap.sh": "bootstrap script",
"package.json": "npm/yarn",
"pyproject.toml": "Python project",
"setup.py": "Python setup",
}

for filename, description in files_to_check.items():
if (repository.path / filename).exists():
setup_files.append(filename)

return setup_files

def _is_setup_prominent(self, readme_content: str) -> bool:
"""Check if setup instructions are in first 3 sections of README."""
# Split by markdown headers (## or ###)
sections = re.split(r"\n##\s+", readme_content)

# Check first 3 sections (plus preamble)
first_sections = "\n".join(sections[:4])

setup_keywords = [
"install",
"setup",
"quick start",
"getting started",
"installation",
]

return any(keyword in first_sections.lower() for keyword in setup_keywords)

def _create_remediation(self) -> Remediation:
"""Create remediation guidance for one-command setup."""
return Remediation(
summary="Create single-command setup for development environment",
steps=[
"Choose setup automation tool (Makefile, setup script, or package manager)",
"Create setup command that handles all dependencies",
"Document setup command prominently in README (Quick Start section)",
"Ensure setup is idempotent (safe to run multiple times)",
"Test setup on fresh clone to verify it works",
],
tools=["make", "npm", "pip", "poetry"],
commands=[
"# Example Makefile",
"cat > Makefile << 'EOF'",
".PHONY: setup",
"setup:",
"\tpython -m venv venv",
"\t. venv/bin/activate && pip install -r requirements.txt",
"\tpre-commit install",
"\tcp .env.example .env",
"\t@echo 'Setup complete! Run make test to verify.'",
"EOF",
],
examples=[
"""# Quick Start section in README

## Quick Start

```bash
make setup # One command to set up development environment
make test # Run tests to verify setup
```
""",
],
citations=[
Citation(
source="freeCodeCamp",
title="Using make for project automation",
url="https://www.freecodecamp.org/news/want-to-know-the-easiest-way-to-save-time-use-make/",
relevance="Guide to using Makefiles for one-command setup",
),
],
)
5 changes: 3 additions & 2 deletions src/agentready/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

# Import all assessors
from ..assessors.documentation import CLAUDEmdAssessor, READMEAssessor
from ..assessors.structure import StandardLayoutAssessor
from ..assessors.structure import OneCommandSetupAssessor, StandardLayoutAssessor
from ..assessors.stub_assessors import (
ConventionalCommitsAssessor,
GitignoreAssessor,
Expand Down Expand Up @@ -63,11 +63,12 @@ def create_all_assessors():
TypeAnnotationsAssessor(),
StandardLayoutAssessor(),
LockFilesAssessor(),
# Tier 2 Critical (10 assessors - 3 implemented, 7 stubs)
# Tier 2 Critical (10 assessors - 4 implemented, 6 stubs)
TestCoverageAssessor(),
PreCommitHooksAssessor(),
ConventionalCommitsAssessor(),
GitignoreAssessor(),
OneCommandSetupAssessor(),
CyclomaticComplexityAssessor(), # Actually Tier 3, but including here
]

Expand Down
Loading