From 98eecb56a220058df83e33cd0aec01c080afcdb5 Mon Sep 17 00:00:00 2001 From: ddjain Date: Wed, 11 Feb 2026 19:17:35 +0530 Subject: [PATCH 1/2] fix: add prompts package and move CLAUDE.md generator prompt to loadable .md Signed-off-by: ddjain --- pyproject.toml | 1 + src/agentready/fixers/documentation.py | 22 ++++- src/agentready/prompts/__init__.py | 7 ++ src/agentready/prompts/claude_md_generator.md | 93 +++++++++++++++++++ src/agentready/prompts/loader.py | 29 ++++++ 5 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 src/agentready/prompts/__init__.py create mode 100644 src/agentready/prompts/claude_md_generator.md create mode 100644 src/agentready/prompts/loader.py diff --git a/pyproject.toml b/pyproject.toml index 442b34e7..cb76b546 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ where = ["src"] agentready = [ "data/*.md", "data/*.yaml", + "prompts/*.md", "templates/*.j2", "templates/**/*.j2", "templates/**/*.yml.j2", diff --git a/src/agentready/fixers/documentation.py b/src/agentready/fixers/documentation.py index c421e256..83513b14 100644 --- a/src/agentready/fixers/documentation.py +++ b/src/agentready/fixers/documentation.py @@ -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) @@ -16,11 +17,24 @@ # Single line written to CLAUDE.md when pointing to AGENTS.md CLAUDE_MD_REDIRECT_LINE = "@AGENTS.md\n" + +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"), + ] + ) + # 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"' -) +CLAUDE_MD_COMMAND = _claude_md_command() class _ClaudeMdToAgentRedirectFix(Fix): diff --git a/src/agentready/prompts/__init__.py b/src/agentready/prompts/__init__.py new file mode 100644 index 00000000..c24b8448 --- /dev/null +++ b/src/agentready/prompts/__init__.py @@ -0,0 +1,7 @@ +"""LLM prompt templates loaded from package resources.""" + +import importlib.resources + +from .loader import load_prompt + +__all__ = ["load_prompt"] diff --git a/src/agentready/prompts/claude_md_generator.md b/src/agentready/prompts/claude_md_generator.md new file mode 100644 index 00000000..1fa3e9fb --- /dev/null +++ b/src/agentready/prompts/claude_md_generator.md @@ -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. diff --git a/src/agentready/prompts/loader.py b/src/agentready/prompts/loader.py new file mode 100644 index 00000000..c908217c --- /dev/null +++ b/src/agentready/prompts/loader.py @@ -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") From 855dcb71967d51a98be3375e796f110413375fa2 Mon Sep 17 00:00:00 2001 From: ddjain Date: Wed, 11 Feb 2026 19:45:21 +0530 Subject: [PATCH 2/2] Add prompts loader tests and lazy command build for CLAUDE.md fixer Signed-off-by: ddjain --- CLAUDE.md | 1 + src/agentready/fixers/documentation.py | 5 +--- src/agentready/prompts/__init__.py | 2 -- tests/unit/test_fixers.py | 12 +++++++-- tests/unit/test_prompts_loader.py | 37 ++++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 tests/unit/test_prompts_loader.py diff --git a/CLAUDE.md b/CLAUDE.md index dbeb59ac..728f2a91 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/src/agentready/fixers/documentation.py b/src/agentready/fixers/documentation.py index 83513b14..80a7615a 100644 --- a/src/agentready/fixers/documentation.py +++ b/src/agentready/fixers/documentation.py @@ -33,9 +33,6 @@ def _claude_md_command() -> str: ] ) -# Command run by CLAUDEmdFixer to generate CLAUDE.md via Claude CLI -CLAUDE_MD_COMMAND = _claude_md_command() - class _ClaudeMdToAgentRedirectFix(Fix): """Post-step fix: move CLAUDE.md content to AGENTS.md, replace CLAUDE.md with @AGENTS.md.""" @@ -152,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 diff --git a/src/agentready/prompts/__init__.py b/src/agentready/prompts/__init__.py index c24b8448..8fe14516 100644 --- a/src/agentready/prompts/__init__.py +++ b/src/agentready/prompts/__init__.py @@ -1,7 +1,5 @@ """LLM prompt templates loaded from package resources.""" -import importlib.resources - from .loader import load_prompt __all__ = ["load_prompt"] diff --git a/tests/unit/test_fixers.py b/tests/unit/test_fixers.py index 05cbf3a5..ca311bb0 100644 --- a/tests/unit/test_fixers.py +++ b/tests/unit/test_fixers.py @@ -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 @@ -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.""" @@ -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" diff --git a/tests/unit/test_prompts_loader.py b/tests/unit/test_prompts_loader.py new file mode 100644 index 00000000..3c918af1 --- /dev/null +++ b/tests/unit/test_prompts_loader.py @@ -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()