reporails-cli 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. reporails_cli/.env.example +1 -0
  2. reporails_cli/__init__.py +24 -0
  3. reporails_cli/bundled/.semgrepignore +51 -0
  4. reporails_cli/bundled/__init__.py +31 -0
  5. reporails_cli/bundled/capability-patterns.yml +54 -0
  6. reporails_cli/bundled/levels.yml +99 -0
  7. reporails_cli/core/__init__.py +35 -0
  8. reporails_cli/core/agents.py +147 -0
  9. reporails_cli/core/applicability.py +150 -0
  10. reporails_cli/core/bootstrap.py +147 -0
  11. reporails_cli/core/cache.py +352 -0
  12. reporails_cli/core/capability.py +245 -0
  13. reporails_cli/core/discover.py +362 -0
  14. reporails_cli/core/engine.py +177 -0
  15. reporails_cli/core/init.py +309 -0
  16. reporails_cli/core/levels.py +177 -0
  17. reporails_cli/core/models.py +329 -0
  18. reporails_cli/core/opengrep/__init__.py +34 -0
  19. reporails_cli/core/opengrep/runner.py +203 -0
  20. reporails_cli/core/opengrep/semgrepignore.py +39 -0
  21. reporails_cli/core/opengrep/templates.py +138 -0
  22. reporails_cli/core/registry.py +155 -0
  23. reporails_cli/core/sarif.py +181 -0
  24. reporails_cli/core/scorer.py +178 -0
  25. reporails_cli/core/semantic.py +193 -0
  26. reporails_cli/core/utils.py +139 -0
  27. reporails_cli/formatters/__init__.py +19 -0
  28. reporails_cli/formatters/json.py +137 -0
  29. reporails_cli/formatters/mcp.py +68 -0
  30. reporails_cli/formatters/text/__init__.py +32 -0
  31. reporails_cli/formatters/text/box.py +89 -0
  32. reporails_cli/formatters/text/chars.py +42 -0
  33. reporails_cli/formatters/text/compact.py +119 -0
  34. reporails_cli/formatters/text/components.py +117 -0
  35. reporails_cli/formatters/text/full.py +135 -0
  36. reporails_cli/formatters/text/rules.py +50 -0
  37. reporails_cli/formatters/text/violations.py +92 -0
  38. reporails_cli/interfaces/__init__.py +1 -0
  39. reporails_cli/interfaces/cli/__init__.py +7 -0
  40. reporails_cli/interfaces/cli/main.py +352 -0
  41. reporails_cli/interfaces/mcp/__init__.py +5 -0
  42. reporails_cli/interfaces/mcp/server.py +194 -0
  43. reporails_cli/interfaces/mcp/tools.py +136 -0
  44. reporails_cli/py.typed +0 -0
  45. reporails_cli/templates/__init__.py +65 -0
  46. reporails_cli/templates/cli_box.txt +10 -0
  47. reporails_cli/templates/cli_cta.txt +4 -0
  48. reporails_cli/templates/cli_delta.txt +1 -0
  49. reporails_cli/templates/cli_file_header.txt +1 -0
  50. reporails_cli/templates/cli_legend.txt +1 -0
  51. reporails_cli/templates/cli_pending.txt +3 -0
  52. reporails_cli/templates/cli_violation.txt +1 -0
  53. reporails_cli/templates/cli_working.txt +2 -0
  54. reporails_cli-0.0.1.dist-info/METADATA +108 -0
  55. reporails_cli-0.0.1.dist-info/RECORD +58 -0
  56. reporails_cli-0.0.1.dist-info/WHEEL +4 -0
  57. reporails_cli-0.0.1.dist-info/entry_points.txt +3 -0
  58. reporails_cli-0.0.1.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1 @@
1
+ OPENGREP_VERSION=1.51.1
@@ -0,0 +1,24 @@
1
+ """Reporails - Lint and score CLAUDE.md files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.0.1"
6
+
7
+ from reporails_cli.core.models import (
8
+ Category,
9
+ Level,
10
+ RuleType,
11
+ Severity,
12
+ ValidationResult,
13
+ Violation,
14
+ )
15
+
16
+ __all__ = [
17
+ "Category",
18
+ "Level",
19
+ "RuleType",
20
+ "Severity",
21
+ "ValidationResult",
22
+ "Violation",
23
+ "__version__",
24
+ ]
@@ -0,0 +1,51 @@
1
+ # Default semgrepignore for reporails
2
+ # Reduces scan time by excluding common non-instruction directories
3
+
4
+ # Package managers
5
+ node_modules/
6
+ vendor/
7
+ bower_components/
8
+
9
+ # Build outputs
10
+ dist/
11
+ build/
12
+ out/
13
+ target/
14
+ *.min.js
15
+ *.min.css
16
+ *.bundle.js
17
+
18
+ # Python
19
+ __pycache__/
20
+ *.pyc
21
+ .venv/
22
+ venv/
23
+ .tox/
24
+ .eggs/
25
+ *.egg-info/
26
+
27
+ # Version control
28
+ .git/
29
+
30
+ # Coverage and testing
31
+ coverage/
32
+ .coverage
33
+ htmlcov/
34
+ .pytest_cache/
35
+ .nyc_output/
36
+
37
+ # IDE and editors
38
+ .idea/
39
+ .vscode/
40
+ *.swp
41
+ *.swo
42
+
43
+ # OS files
44
+ .DS_Store
45
+ Thumbs.db
46
+
47
+ # Large binary files
48
+ *.wasm
49
+ *.so
50
+ *.dylib
51
+ *.dll
@@ -0,0 +1,31 @@
1
+ """Bundled configuration files for reporails CLI.
2
+
3
+ This package contains CLI-owned configuration:
4
+ - levels.yml: Level definitions and rule-to-level mapping
5
+ - capability-patterns.yml: OpenGrep patterns for capability detection
6
+ - .semgrepignore: Default ignore patterns for OpenGrep/Semgrep
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+
13
+
14
+ def get_bundled_path() -> Path:
15
+ """Get path to bundled configuration directory."""
16
+ return Path(__file__).parent
17
+
18
+
19
+ def get_levels_path() -> Path:
20
+ """Get path to bundled levels.yml."""
21
+ return get_bundled_path() / "levels.yml"
22
+
23
+
24
+ def get_capability_patterns_path() -> Path:
25
+ """Get path to bundled capability-patterns.yml."""
26
+ return get_bundled_path() / "capability-patterns.yml"
27
+
28
+
29
+ def get_semgrepignore_path() -> Path:
30
+ """Get path to bundled .semgrepignore."""
31
+ return get_bundled_path() / ".semgrepignore"
@@ -0,0 +1,54 @@
1
+ # OpenGrep patterns for capability detection
2
+ # CLI-owned configuration for content analysis (Phase 2)
3
+
4
+ rules:
5
+ - id: capability.has-sections
6
+ message: "Has markdown sections (H2+)"
7
+ languages: [generic]
8
+ severity: INFO
9
+ pattern-regex: "^##+ "
10
+ paths:
11
+ include:
12
+ - "*.md"
13
+ - "**/CLAUDE.md"
14
+ - "**/*.md"
15
+
16
+ - id: capability.has-imports
17
+ message: "Has @imports or file references"
18
+ languages: [generic]
19
+ severity: INFO
20
+ pattern-either:
21
+ - pattern-regex: "@import"
22
+ - pattern-regex: "@docs/"
23
+ - pattern-regex: "@\\.shared/"
24
+ - pattern-regex: "Read `[^`]+`"
25
+ paths:
26
+ include:
27
+ - "*.md"
28
+ - "**/CLAUDE.md"
29
+ - "**/*.md"
30
+
31
+ - id: capability.has-explicit-constraints
32
+ message: "Has MUST/MUST NOT/NEVER constraints"
33
+ languages: [generic]
34
+ severity: INFO
35
+ pattern-either:
36
+ - pattern-regex: "\\bMUST\\b"
37
+ - pattern-regex: "\\bMUST NOT\\b"
38
+ - pattern-regex: "\\bNEVER\\b"
39
+ paths:
40
+ include:
41
+ - "*.md"
42
+ - "**/CLAUDE.md"
43
+ - "**/*.md"
44
+
45
+ - id: capability.has-path-scoped-rules
46
+ message: "Has path-scoped rules (paths: in frontmatter)"
47
+ languages: [generic]
48
+ severity: INFO
49
+ pattern-regex: "^paths:\\s*$"
50
+ paths:
51
+ include:
52
+ - ".claude/rules/*.md"
53
+ - ".cursor/rules/*.md"
54
+ - ".ai/rules/*.md"
@@ -0,0 +1,99 @@
1
+ # Level definitions and rule-to-level mapping
2
+ # CLI-owned configuration for orchestration logic
3
+
4
+ levels:
5
+ L1:
6
+ name: Absent
7
+ required_rules: []
8
+
9
+ L2:
10
+ name: Basic
11
+ includes: [L1]
12
+ required_rules:
13
+ - S1 # Size limits
14
+ - C1 # Core sections
15
+ - C2 # Explicit over implicit
16
+ - C4 # Anti-pattern documentation
17
+ - C7 # Emphasis discipline
18
+ - C8 # Instructions over philosophy
19
+ - C9 # Has project description
20
+ - C10 # Has NEVER statements
21
+ - C12 # Has version/date
22
+ - M5 # Auto-generated content review
23
+
24
+ L3:
25
+ name: Structured
26
+ includes: [L2]
27
+ required_rules:
28
+ - S2 # Progressive disclosure
29
+ - S3 # No embedded code snippets
30
+ - S7 # Clear markdown structure
31
+ - C3 # Context-specific content
32
+ - C6 # Single source of truth
33
+ - C11 # Links are valid
34
+ - E6 # Code block line limit
35
+ - E7 # Import count
36
+ - M1 # Version control
37
+ - M2 # Review process
38
+
39
+ L4:
40
+ name: Abstracted
41
+ includes: [L3]
42
+ required_rules:
43
+ - S4 # Hierarchical memory
44
+ - S5 # Path-scoped rules
45
+ - E1 # Deterministic tools for style
46
+ - E3 # Purpose-based file reading
47
+ - E4 # Memory reference
48
+ - E5 # Grep efficiency
49
+ - E8 # Context window awareness
50
+ - M7 # Rule snippet length enforcement
51
+
52
+ L5:
53
+ name: Governed
54
+ includes: [L4]
55
+ required_rules:
56
+ - G1 # Organization-level policies
57
+ - G2 # Team governance structure
58
+ - G3 # Security rules ownership
59
+ - G4 # Ownership assignment
60
+ - G8 # Metrics and CI/CD checks
61
+ - M3 # Change management
62
+ - M4 # No conflicting rules
63
+
64
+ L6:
65
+ name: Adaptive
66
+ includes: [L5]
67
+ required_rules:
68
+ - S6 # YAML backbone
69
+ - C5 # MUST/MUST NOT with context
70
+ - E2 # Session start ritual
71
+ - G5 # Contract registry
72
+ - G6 # Component-contract binding
73
+ - G7 # Architecture tests
74
+ - M6 # Map staleness prevention
75
+
76
+ # Capability score thresholds
77
+ score_thresholds:
78
+ L1: 0
79
+ L2: 1
80
+ L3: 3
81
+ L4: 5
82
+ L5: 7
83
+ L6: 10
84
+
85
+ # Feature detection for capability scoring
86
+ detection:
87
+ L6:
88
+ - has_backbone
89
+ L5:
90
+ - component_count_3plus
91
+ - has_shared_files
92
+ L4:
93
+ - has_rules_dir
94
+ L3:
95
+ - has_imports
96
+ - has_multiple_instruction_files
97
+ L2:
98
+ - has_instruction_file
99
+ L1: []
@@ -0,0 +1,35 @@
1
+ """Core domain logic for reporails."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from reporails_cli.core.models import (
6
+ Category,
7
+ Check,
8
+ JudgmentRequest,
9
+ JudgmentResponse,
10
+ Level,
11
+ Rule,
12
+ RuleType,
13
+ Severity,
14
+ ValidationResult,
15
+ Violation,
16
+ )
17
+ from reporails_cli.core.scorer import (
18
+ calculate_score,
19
+ estimate_friction,
20
+ )
21
+
22
+ __all__ = [
23
+ "Category",
24
+ "Check",
25
+ "JudgmentRequest",
26
+ "JudgmentResponse",
27
+ "Level",
28
+ "Rule",
29
+ "RuleType",
30
+ "Severity",
31
+ "ValidationResult",
32
+ "Violation",
33
+ "calculate_score",
34
+ "estimate_friction",
35
+ ]
@@ -0,0 +1,147 @@
1
+ """Agent definitions - coding agent agnostic discovery.
2
+
3
+ Supports multiple AI coding assistants:
4
+ - Claude (Anthropic)
5
+ - Cursor
6
+ - Windsurf
7
+ - GitHub Copilot
8
+ - Aider
9
+ - Generic/custom
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass, field
15
+ from pathlib import Path
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class AgentType:
20
+ """Definition of a coding agent's file conventions."""
21
+
22
+ id: str
23
+ name: str
24
+ instruction_patterns: tuple[str, ...] # Glob patterns for instruction files
25
+ config_patterns: tuple[str, ...] # Glob patterns for config files
26
+ rule_patterns: tuple[str, ...] # Glob patterns for rule/snippet files
27
+
28
+
29
+ # Known coding agents and their conventions
30
+ KNOWN_AGENTS: dict[str, AgentType] = {
31
+ "claude": AgentType(
32
+ id="claude",
33
+ name="Claude (Anthropic)",
34
+ instruction_patterns=("CLAUDE.md", "**/CLAUDE.md"),
35
+ config_patterns=(".claude/settings.json", ".claude/mcp.json"),
36
+ rule_patterns=(".claude/rules/*.md", ".claude/**/*.md"),
37
+ ),
38
+ "cursor": AgentType(
39
+ id="cursor",
40
+ name="Cursor",
41
+ instruction_patterns=(".cursorrules", ".cursor/rules/*.md"),
42
+ config_patterns=(".cursor/settings.json",),
43
+ rule_patterns=(".cursor/rules/*.md",),
44
+ ),
45
+ "windsurf": AgentType(
46
+ id="windsurf",
47
+ name="Windsurf",
48
+ instruction_patterns=(".windsurfrules",),
49
+ config_patterns=(),
50
+ rule_patterns=(),
51
+ ),
52
+ "copilot": AgentType(
53
+ id="copilot",
54
+ name="GitHub Copilot",
55
+ instruction_patterns=(".github/copilot-instructions.md",),
56
+ config_patterns=(),
57
+ rule_patterns=(),
58
+ ),
59
+ "aider": AgentType(
60
+ id="aider",
61
+ name="Aider",
62
+ instruction_patterns=(".aider.conf.yml", "CONVENTIONS.md"),
63
+ config_patterns=(".aider.conf.yml",),
64
+ rule_patterns=(),
65
+ ),
66
+ "generic": AgentType(
67
+ id="generic",
68
+ name="Generic AI Instructions",
69
+ instruction_patterns=("AGENTS.md", ".ai/instructions.md", ".ai/**/*.md"),
70
+ config_patterns=(),
71
+ rule_patterns=(".ai/rules/*.md",),
72
+ ),
73
+ }
74
+
75
+
76
+ @dataclass
77
+ class DetectedAgent:
78
+ """An agent detected in a project."""
79
+
80
+ agent_type: AgentType
81
+ instruction_files: list[Path] = field(default_factory=list)
82
+ config_files: list[Path] = field(default_factory=list)
83
+ rule_files: list[Path] = field(default_factory=list)
84
+
85
+
86
+ def detect_agents(target: Path) -> list[DetectedAgent]:
87
+ """
88
+ Detect which coding agents are configured in the target directory.
89
+
90
+ Scans for known file patterns and returns detected agents with their files.
91
+
92
+ Args:
93
+ target: Project root to scan
94
+
95
+ Returns:
96
+ List of detected agents with their associated files
97
+ """
98
+ detected: list[DetectedAgent] = []
99
+
100
+ for _agent_id, agent_type in KNOWN_AGENTS.items():
101
+ instruction_files: list[Path] = []
102
+ config_files: list[Path] = []
103
+ rule_files: list[Path] = []
104
+
105
+ # Find instruction files
106
+ for pattern in agent_type.instruction_patterns:
107
+ instruction_files.extend(target.glob(pattern))
108
+
109
+ # Find config files
110
+ for pattern in agent_type.config_patterns:
111
+ config_files.extend(target.glob(pattern))
112
+
113
+ # Find rule files
114
+ for pattern in agent_type.rule_patterns:
115
+ rule_files.extend(target.glob(pattern))
116
+
117
+ # Only include if we found at least one instruction file
118
+ if instruction_files:
119
+ detected.append(
120
+ DetectedAgent(
121
+ agent_type=agent_type,
122
+ instruction_files=sorted(set(instruction_files)),
123
+ config_files=sorted(set(config_files)),
124
+ rule_files=sorted(set(rule_files)),
125
+ )
126
+ )
127
+
128
+ return detected
129
+
130
+
131
+ def get_all_instruction_files(target: Path) -> list[Path]:
132
+ """
133
+ Get all instruction files for all detected agents.
134
+
135
+ Args:
136
+ target: Project root to scan
137
+
138
+ Returns:
139
+ Deduplicated list of all instruction file paths
140
+ """
141
+ all_files: set[Path] = set()
142
+
143
+ for detected in detect_agents(target):
144
+ all_files.update(detected.instruction_files)
145
+ all_files.update(detected.rule_files)
146
+
147
+ return sorted(all_files)
@@ -0,0 +1,150 @@
1
+ """Feature detection (filesystem) and rule applicability.
2
+
3
+ Phase 1 of capability detection - scans filesystem for features.
4
+ Phase 2 (content detection) is in capability.py.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+
11
+ import yaml
12
+
13
+ from reporails_cli.core.levels import get_rules_for_level
14
+ from reporails_cli.core.models import DetectedFeatures, Level, Rule
15
+
16
+
17
+ def detect_features_filesystem(target: Path) -> DetectedFeatures:
18
+ """Detect project features from file structure.
19
+
20
+ Phase 1 of capability detection - filesystem only, no content analysis.
21
+
22
+ Args:
23
+ target: Project root path
24
+
25
+ Returns:
26
+ DetectedFeatures with filesystem-based indicators
27
+ """
28
+ features = DetectedFeatures()
29
+
30
+ # Check for CLAUDE.md at root
31
+ root_claude = target / "CLAUDE.md"
32
+ features.has_claude_md = root_claude.exists()
33
+ features.has_instruction_file = features.has_claude_md
34
+
35
+ # Check for .claude/rules/
36
+ rules_dir = target / ".claude" / "rules"
37
+ features.has_rules_dir = rules_dir.exists() and any(rules_dir.glob("*.md"))
38
+
39
+ # Check for other agent rules directories
40
+ other_rules = [".cursor/rules", ".ai/rules"]
41
+ for pattern in other_rules:
42
+ other_dir = target / pattern
43
+ if other_dir.exists() and any(other_dir.glob("*.md")):
44
+ features.has_rules_dir = True
45
+ break
46
+
47
+ # Check for backbone.yml
48
+ backbone_path = target / ".reporails" / "backbone.yml"
49
+ features.has_backbone = backbone_path.exists()
50
+
51
+ # Count instruction files
52
+ claude_files = list(target.rglob("CLAUDE.md"))
53
+ features.instruction_file_count = len(claude_files)
54
+ features.has_multiple_instruction_files = len(claude_files) > 1
55
+
56
+ if features.instruction_file_count > 0:
57
+ features.has_instruction_file = True
58
+
59
+ # Check for hierarchical structure (nested CLAUDE.md)
60
+ for cf in claude_files:
61
+ if cf.parent != target:
62
+ features.has_hierarchical_structure = True
63
+ break
64
+
65
+ # Check for @imports in content (simple check, full check in Phase 2)
66
+ if features.has_claude_md:
67
+ try:
68
+ content = root_claude.read_text(encoding="utf-8")
69
+ features.has_imports = "@" in content
70
+ except (OSError, UnicodeDecodeError):
71
+ pass
72
+
73
+ # Check for shared files
74
+ shared_patterns = [".shared", "shared", ".ai/shared"]
75
+ for pattern in shared_patterns:
76
+ if (target / pattern).exists():
77
+ features.has_shared_files = True
78
+ break
79
+
80
+ # Count components from backbone if present
81
+ if features.has_backbone:
82
+ try:
83
+ backbone_content = backbone_path.read_text(encoding="utf-8")
84
+ backbone_data = yaml.safe_load(backbone_content)
85
+ features.component_count = len(backbone_data.get("components", {}))
86
+ except (yaml.YAMLError, OSError):
87
+ pass
88
+
89
+ return features
90
+
91
+
92
+ def get_applicable_rules(rules: dict[str, Rule], level: Level) -> dict[str, Rule]:
93
+ """Filter rules to those applicable at the given level.
94
+
95
+ Rules apply at their minimum level and above.
96
+
97
+ Args:
98
+ rules: Dict of all rules
99
+ level: Detected capability level
100
+
101
+ Returns:
102
+ Dict of applicable rules
103
+ """
104
+ # Get rule IDs for this level from levels.yml
105
+ applicable_ids = get_rules_for_level(level)
106
+
107
+ # Filter rules
108
+ return {k: v for k, v in rules.items() if k in applicable_ids}
109
+
110
+
111
+ def get_feature_summary(features: DetectedFeatures) -> str:
112
+ """Generate human-readable summary of detected features.
113
+
114
+ Args:
115
+ features: Detected project features
116
+
117
+ Returns:
118
+ Summary string for display
119
+ """
120
+ parts = []
121
+
122
+ # File count
123
+ if features.instruction_file_count == 0:
124
+ parts.append("No instruction files")
125
+ elif features.instruction_file_count == 1:
126
+ parts.append("1 instruction file")
127
+ else:
128
+ parts.append(f"{features.instruction_file_count} instruction files")
129
+
130
+ # Features present
131
+ feature_list = []
132
+ if features.has_rules_dir:
133
+ feature_list.append(".claude/rules/")
134
+ if features.has_backbone:
135
+ feature_list.append("backbone.yml")
136
+ if features.has_shared_files:
137
+ feature_list.append("shared files")
138
+ if features.has_hierarchical_structure:
139
+ feature_list.append("hierarchical")
140
+
141
+ if feature_list:
142
+ parts.append(" + ".join(feature_list))
143
+
144
+ return ", ".join(parts) if parts else "No features detected"
145
+
146
+
147
+ # Legacy alias for backward compatibility
148
+ def detect_features(target: Path) -> DetectedFeatures:
149
+ """Legacy alias for detect_features_filesystem()."""
150
+ return detect_features_filesystem(target)