Skip to content
Open
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 CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ src/agentready/
├── reporters/ # Report generation (HTML, Markdown, JSON)
│ ├── html.py # Interactive HTML with Jinja2
│ └── markdown.py # GitHub-Flavored Markdown
├── prompts/ # LLM prompt .md templates; load_prompt(name) from loader.py
├── templates/ # Jinja2 templates
│ └── report.html.j2 # Self-contained HTML report (73KB)
└── cli/ # Click-based CLI
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ where = ["src"]
agentready = [
"data/*.md",
"data/*.yaml",
"prompts/*.md",
"templates/*.j2",
"templates/**/*.j2",
"templates/**/*.yml.j2",
Expand Down
23 changes: 17 additions & 6 deletions src/agentready/fixers/documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ..models.finding import Finding
from ..models.fix import CommandFix, Fix, MultiStepFix
from ..models.repository import Repository
from ..prompts import load_prompt
from .base import BaseFixer

# Env var required for Claude CLI (used by CLAUDEmdFixer)
Expand All @@ -16,11 +17,21 @@
# Single line written to CLAUDE.md when pointing to AGENTS.md
CLAUDE_MD_REDIRECT_LINE = "@AGENTS.md\n"

# Command run by CLAUDEmdFixer to generate CLAUDE.md via Claude CLI
CLAUDE_MD_COMMAND = (
'claude -p "Initialize this project with a CLAUDE.md file" '
'--allowedTools "Read,Edit,Write,Bash"'
)

def _claude_md_command() -> str:
"""Build Claude CLI command with prompt loaded from resources (safe shell quoting)."""
import shlex

prompt = load_prompt("claude_md_generator")
return " ".join(
[
"claude",
"-p",
shlex.quote(prompt),
"--allowedTools",
shlex.quote("Read,Edit,Write,Bash"),
]
)


class _ClaudeMdToAgentRedirectFix(Fix):
Expand Down Expand Up @@ -138,7 +149,7 @@ def generate_fix(self, repository: Repository, finding: Finding) -> Optional[Fix
attribute_id=self.attribute_id,
description="Run Claude CLI to create CLAUDE.md in the project",
points_gained=points,
command=CLAUDE_MD_COMMAND,
command=_claude_md_command(),
working_dir=repository.path,
repository_path=repository.path,
capture_output=False, # Stream Claude output to terminal
Expand Down
5 changes: 5 additions & 0 deletions src/agentready/prompts/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""LLM prompt templates loaded from package resources."""

from .loader import load_prompt

__all__ = ["load_prompt"]
93 changes: 93 additions & 0 deletions src/agentready/prompts/claude_md_generator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
You are a senior technical documentation architect.

Your task is to autonomously analyze the repository and generate a concise, high-signal CLAUDE.md file.

Core Principle

LLMs are stateless. CLAUDE.md is guaranteed to appear in every conversation.
Every line multiplies across every task.
This file is onboarding, not configuration.

It must answer:
- WHAT is this project?
- WHY does it exist?
- HOW do I build, test, and verify it?
- WHERE are the authoritative sources? (file:line references)

Hard Constraints

- Maximum 300 lines; ideal under 60.
- Ruthlessly remove noise.
- Include only information universally applicable to every task.
- Prefer pointers over copies (path/file.ext:line).
- Never include large code snippets.
- Never include linter/style rules.
- Never include task-specific workflows.
- Never include boilerplate filler, mission statements, or fluff.
- If unsure whether something belongs, exclude it.

Autonomous Discovery Requirements

You must inspect the repository to determine:
- Primary programming language(s)
- Build system and dependency manager
- Test framework and test entrypoints
- Runtime/deployment method
- Project structure and major components
- Most authoritative source files
- Essential build, test, lint, and run commands

Read configuration files such as:
package.json, pyproject.toml, Cargo.toml, go.mod, Makefile, Dockerfile,
CI configs, README.md, and scan the directory structure.

Infer commands from scripts, Make targets, package scripts, or CI pipelines.
Do not ask the user questions.

Philosophy

1. Onboard, Don't Configure
Describe tech stack, structure, purpose, key components, essential commands.
Do not duplicate CI config or explain coding style rules.

2. Progressive Disclosure
Keep CLAUDE.md minimal.
Reference BOOKMARKS.md, docs/*, ADRs, or other docs instead of copying them.
Never paste full documentation into CLAUDE.md.

3. Pointers Over Copies
Use file references instead of prose explanations.
Examples:
- src/lib/auth.ts:45-120
- prisma/schema.prisma
Authoritative source always wins.

4. Assume Strong CI
Do not include formatting rules, style guides, commit conventions, or linter configs.
Those belong in CI and pre-commit hooks.

Output Requirements

Generate only the CLAUDE.md file contents.
Use clean Markdown.
No commentary or framing text.
No AI instructions inside the file.
High signal density.
Avoid redundant headings.
Tone: precise, technical, minimal.

Quality Check (silent before finalizing)

- Under 300 lines (under 60 ideal)?
- No task-specific instructions?
- No style/linter rules?
- Uses file references instead of copied code?
- References BOOKMARKS.md if present?
- Zero fluff?

If not, refine.

You are not a template engine.
You are a documentation editor with ruthless taste.
Every line must justify its existence.
If it does not apply to every task, remove it.
29 changes: 29 additions & 0 deletions src/agentready/prompts/loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Load prompt content from package .md files."""

from __future__ import annotations

import importlib.resources


def load_prompt(name: str) -> str:
"""Load prompt content by name from package resources.

Looks for a file named ``{name}.md`` under the prompts package.
Names must not contain path separators or dots (except the implied .md).

Args:
name: Base name of the prompt file (e.g. "claude_md_generator").

Returns:
Raw text content of the prompt file.

Raises:
FileNotFoundError: If no such prompt file exists.
"""
if "/" in name or "\\" in name or name.startswith("."):
raise ValueError(f"Invalid prompt name: {name}")
ref = importlib.resources.files("agentready.prompts")
path = ref / f"{name}.md"
if not path.is_file():
raise FileNotFoundError(f"Prompt file not found: {name}.md")
return path.read_text(encoding="utf-8")
12 changes: 10 additions & 2 deletions tests/unit/test_fixers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@

from agentready.fixers.documentation import (
ANTHROPIC_API_KEY_ENV,
CLAUDE_MD_COMMAND,
CLAUDE_MD_REDIRECT_LINE,
CLAUDEmdFixer,
GitignoreFixer,
_claude_md_command,
)
from agentready.models.attribute import Attribute
from agentready.models.finding import Finding, Remediation
Expand Down Expand Up @@ -107,6 +107,14 @@ def gitignore_failing_finding():
)


def test_claude_md_command_shell_safe():
"""_claude_md_command returns a command with shell-quoted arguments."""
cmd = _claude_md_command()
assert "claude" in cmd
assert "-p" in cmd
assert "'" in cmd or '"' in cmd # shlex.quote applied


class TestCLAUDEmdFixer:
"""Tests for CLAUDEmdFixer."""

Expand Down Expand Up @@ -142,7 +150,7 @@ def test_generate_fix_when_agent_md_missing(
assert isinstance(fix, MultiStepFix)
assert len(fix.steps) == 2
assert isinstance(fix.steps[0], CommandFix)
assert fix.steps[0].command == CLAUDE_MD_COMMAND
assert fix.steps[0].command == _claude_md_command()
assert fix.steps[0].working_dir == temp_repo.path
assert fix.steps[0].capture_output is False
assert fix.attribute_id == "claude_md_file"
Expand Down
37 changes: 37 additions & 0 deletions tests/unit/test_prompts_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Unit tests for prompts loader."""

import importlib.resources

import pytest

from agentready.prompts import load_prompt


def test_load_prompt_success():
"""load_prompt returns content for existing prompt file."""
content = load_prompt("claude_md_generator")
assert content
assert "senior technical documentation architect" in content
assert "CLAUDE.md" in content


def test_load_prompt_invalid_name_raises_value_error():
"""load_prompt raises ValueError for names with path separators or leading dot."""
with pytest.raises(ValueError, match="Invalid prompt name"):
load_prompt("../malicious")
with pytest.raises(ValueError, match="Invalid prompt name"):
load_prompt("foo/bar")
with pytest.raises(ValueError, match="Invalid prompt name"):
load_prompt(".hidden")


def test_load_prompt_missing_file_raises_file_not_found_error():
"""load_prompt raises FileNotFoundError for non-existent prompt."""
with pytest.raises(FileNotFoundError, match="nonexistent_prompt"):
load_prompt("nonexistent_prompt")


def test_prompts_packaged_correctly():
"""Ensure .md prompt files are included in package distribution."""
ref = importlib.resources.files("agentready.prompts")
assert (ref / "claude_md_generator.md").is_file()
Loading