ai-agent-rules 0.11.0__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.

Potentially problematic release.


This version of ai-agent-rules might be problematic. Click here for more details.

Files changed (42) hide show
  1. ai_agent_rules-0.11.0.dist-info/METADATA +390 -0
  2. ai_agent_rules-0.11.0.dist-info/RECORD +42 -0
  3. ai_agent_rules-0.11.0.dist-info/WHEEL +5 -0
  4. ai_agent_rules-0.11.0.dist-info/entry_points.txt +3 -0
  5. ai_agent_rules-0.11.0.dist-info/licenses/LICENSE +22 -0
  6. ai_agent_rules-0.11.0.dist-info/top_level.txt +1 -0
  7. ai_rules/__init__.py +8 -0
  8. ai_rules/agents/__init__.py +1 -0
  9. ai_rules/agents/base.py +68 -0
  10. ai_rules/agents/claude.py +121 -0
  11. ai_rules/agents/goose.py +44 -0
  12. ai_rules/agents/shared.py +35 -0
  13. ai_rules/bootstrap/__init__.py +75 -0
  14. ai_rules/bootstrap/config.py +261 -0
  15. ai_rules/bootstrap/installer.py +249 -0
  16. ai_rules/bootstrap/updater.py +221 -0
  17. ai_rules/bootstrap/version.py +52 -0
  18. ai_rules/cli.py +2292 -0
  19. ai_rules/completions.py +194 -0
  20. ai_rules/config/AGENTS.md +249 -0
  21. ai_rules/config/chat_agent_hints.md +1 -0
  22. ai_rules/config/claude/agents/code-reviewer.md +121 -0
  23. ai_rules/config/claude/commands/annotate-changelog.md +191 -0
  24. ai_rules/config/claude/commands/comment-cleanup.md +161 -0
  25. ai_rules/config/claude/commands/continue-crash.md +38 -0
  26. ai_rules/config/claude/commands/dev-docs.md +169 -0
  27. ai_rules/config/claude/commands/pr-creator.md +247 -0
  28. ai_rules/config/claude/commands/test-cleanup.md +244 -0
  29. ai_rules/config/claude/commands/update-docs.md +324 -0
  30. ai_rules/config/claude/hooks/subagentStop.py +92 -0
  31. ai_rules/config/claude/mcps.json +1 -0
  32. ai_rules/config/claude/settings.json +116 -0
  33. ai_rules/config/claude/skills/doc-writer/SKILL.md +293 -0
  34. ai_rules/config/claude/skills/doc-writer/resources/templates.md +495 -0
  35. ai_rules/config/claude/skills/prompt-engineer/SKILL.md +272 -0
  36. ai_rules/config/claude/skills/prompt-engineer/resources/prompt_engineering_guide_2025.md +855 -0
  37. ai_rules/config/claude/skills/prompt-engineer/resources/templates.md +232 -0
  38. ai_rules/config/goose/config.yaml +55 -0
  39. ai_rules/config.py +635 -0
  40. ai_rules/display.py +40 -0
  41. ai_rules/mcp.py +370 -0
  42. ai_rules/symlinks.py +207 -0
@@ -0,0 +1,221 @@
1
+ """Update checking and application utilities."""
2
+
3
+ import json
4
+ import logging
5
+ import re
6
+ import subprocess
7
+ import urllib.request
8
+
9
+ from collections.abc import Callable
10
+ from dataclasses import dataclass
11
+
12
+ from .installer import (
13
+ UV_NOT_FOUND_ERROR,
14
+ _validate_package_name,
15
+ get_tool_source,
16
+ get_tool_version,
17
+ is_command_available,
18
+ )
19
+ from .version import is_newer
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ PYPI_JSON_API_URL = "https://pypi.org/pypi/{package_name}/json"
24
+
25
+
26
+ @dataclass
27
+ class UpdateInfo:
28
+ """Information about available updates."""
29
+
30
+ has_update: bool
31
+ current_version: str
32
+ latest_version: str
33
+ source: str # "pypi"
34
+
35
+
36
+ def check_pypi_updates(
37
+ package_name: str, current_version: str, timeout: int = 10
38
+ ) -> UpdateInfo:
39
+ """Check PyPI for newer version.
40
+
41
+ Args:
42
+ package_name: Package name on PyPI
43
+ current_version: Currently installed version
44
+ timeout: Request timeout in seconds (default: 10)
45
+
46
+ Returns:
47
+ UpdateInfo with update status
48
+ """
49
+ # Validate package name (PEP 508 compliant)
50
+ if not re.match(r"^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$", package_name):
51
+ return UpdateInfo(
52
+ has_update=False,
53
+ current_version=current_version,
54
+ latest_version=current_version,
55
+ source="pypi",
56
+ )
57
+
58
+ try:
59
+ url = PYPI_JSON_API_URL.format(package_name=package_name)
60
+
61
+ req = urllib.request.Request(url)
62
+ req.add_header("User-Agent", f"{package_name}/{current_version}")
63
+
64
+ with urllib.request.urlopen(req, timeout=timeout) as response:
65
+ data = json.loads(response.read().decode())
66
+
67
+ latest_version = data["info"]["version"]
68
+ has_update = is_newer(latest_version, current_version)
69
+
70
+ return UpdateInfo(
71
+ has_update=has_update,
72
+ current_version=current_version,
73
+ latest_version=latest_version,
74
+ source="pypi",
75
+ )
76
+
77
+ except (urllib.error.URLError, json.JSONDecodeError, KeyError) as e:
78
+ logger.debug(f"PyPI check failed: {e}")
79
+ return UpdateInfo(
80
+ has_update=False,
81
+ current_version=current_version,
82
+ latest_version=current_version,
83
+ source="pypi",
84
+ )
85
+
86
+
87
+ def perform_pypi_update(package_name: str) -> tuple[bool, str, bool]:
88
+ """Upgrade via uv tool upgrade.
89
+
90
+ Args:
91
+ package_name: Name of package to upgrade
92
+
93
+ Returns:
94
+ Tuple of (success, message, was_upgraded)
95
+ - success: Whether command succeeded
96
+ - message: Human-readable status message
97
+ - was_upgraded: True if package was actually upgraded (not already up-to-date)
98
+ """
99
+ if not _validate_package_name(package_name):
100
+ return False, f"Invalid package name: {package_name}", False
101
+
102
+ if not is_command_available("uv"):
103
+ return False, UV_NOT_FOUND_ERROR, False
104
+
105
+ source = get_tool_source(package_name)
106
+
107
+ if source == "local":
108
+ cmd = ["uv", "tool", "install", package_name, "--force", "--no-cache"]
109
+ else:
110
+ cmd = ["uv", "tool", "upgrade", package_name, "--no-cache"]
111
+
112
+ try:
113
+ result = subprocess.run(
114
+ cmd,
115
+ capture_output=True,
116
+ text=True,
117
+ timeout=60,
118
+ )
119
+
120
+ if result.returncode == 0:
121
+ output = result.stdout + result.stderr
122
+
123
+ upgrade_patterns = [
124
+ r"Upgraded .+ from .+ to .+",
125
+ r"Installed .+ \d+\.\d+",
126
+ r"Successfully installed",
127
+ ]
128
+
129
+ already_up_to_date_patterns = [
130
+ r"Nothing to upgrade",
131
+ r"already.*installed",
132
+ r"already.*up.*to.*date",
133
+ ]
134
+
135
+ was_upgraded = False
136
+ if any(
137
+ re.search(pattern, output, re.IGNORECASE)
138
+ for pattern in upgrade_patterns
139
+ ):
140
+ was_upgraded = True
141
+ elif any(
142
+ re.search(pattern, output, re.IGNORECASE)
143
+ for pattern in already_up_to_date_patterns
144
+ ):
145
+ was_upgraded = False
146
+ else:
147
+ was_upgraded = True
148
+
149
+ return True, "Upgrade successful", was_upgraded
150
+
151
+ error_msg = result.stderr.strip()
152
+ if not error_msg:
153
+ error_msg = "Upgrade failed with no error message"
154
+
155
+ return False, error_msg, False
156
+
157
+ except subprocess.TimeoutExpired:
158
+ return False, "Upgrade timed out after 60 seconds", False
159
+ except Exception as e:
160
+ return False, f"Unexpected error: {e}", False
161
+
162
+
163
+ @dataclass
164
+ class ToolSpec:
165
+ """Specification for an updatable tool."""
166
+
167
+ tool_id: str
168
+ package_name: str
169
+ display_name: str
170
+ get_version: Callable[[], str | None]
171
+ is_installed: Callable[[], bool]
172
+
173
+
174
+ UPDATABLE_TOOLS: list[ToolSpec] = [
175
+ ToolSpec(
176
+ tool_id="ai-rules",
177
+ package_name="ai-agent-rules",
178
+ display_name="ai-rules",
179
+ get_version=lambda: get_tool_version("ai-agent-rules"),
180
+ is_installed=lambda: True, # Always installed (it's us)
181
+ ),
182
+ ToolSpec(
183
+ tool_id="statusline",
184
+ package_name="claude-code-statusline",
185
+ display_name="statusline",
186
+ get_version=lambda: get_tool_version("claude-code-statusline"),
187
+ is_installed=lambda: is_command_available("claude-statusline"),
188
+ ),
189
+ ]
190
+
191
+
192
+ def check_tool_updates(tool: ToolSpec, timeout: int = 10) -> UpdateInfo | None:
193
+ """Check for updates for any tool.
194
+
195
+ Args:
196
+ tool: Tool specification
197
+ timeout: Request timeout in seconds (default: 10)
198
+
199
+ Returns:
200
+ UpdateInfo if tool is installed and update check succeeds, None otherwise
201
+ """
202
+ if not tool.is_installed():
203
+ return None
204
+
205
+ current = tool.get_version()
206
+ if current is None:
207
+ return None
208
+
209
+ return check_pypi_updates(tool.package_name, current, timeout)
210
+
211
+
212
+ def get_tool_by_id(tool_id: str) -> ToolSpec | None:
213
+ """Look up tool spec by ID.
214
+
215
+ Args:
216
+ tool_id: Tool identifier (e.g., "ai-rules", "statusline")
217
+
218
+ Returns:
219
+ ToolSpec if found, None otherwise
220
+ """
221
+ return next((t for t in UPDATABLE_TOOLS if t.tool_id == tool_id), None)
@@ -0,0 +1,52 @@
1
+ """Version utilities for package management."""
2
+
3
+ from importlib.metadata import version as get_version
4
+
5
+ from packaging.version import InvalidVersion, Version
6
+
7
+
8
+ def parse_version(version_str: str) -> Version:
9
+ """Parse version string, handling 'v' prefix.
10
+
11
+ Args:
12
+ version_str: Version string (e.g., "1.2.3" or "v1.2.3")
13
+
14
+ Returns:
15
+ Parsed Version object
16
+
17
+ Raises:
18
+ InvalidVersion: If version string is malformed
19
+ """
20
+ clean = version_str.lstrip("v")
21
+ return Version(clean)
22
+
23
+
24
+ def is_newer(latest: str, current: str) -> bool:
25
+ """Check if latest version is newer than current.
26
+
27
+ Args:
28
+ latest: Latest version string
29
+ current: Current version string
30
+
31
+ Returns:
32
+ True if latest is newer, False otherwise
33
+ """
34
+ try:
35
+ return parse_version(latest) > parse_version(current)
36
+ except InvalidVersion:
37
+ return False
38
+
39
+
40
+ def get_package_version(package_name: str) -> str:
41
+ """Get installed version of a package.
42
+
43
+ Args:
44
+ package_name: Name of the package (e.g., "ai-rules")
45
+
46
+ Returns:
47
+ Version string (e.g., "0.3.0")
48
+
49
+ Raises:
50
+ PackageNotFoundError: If package is not installed
51
+ """
52
+ return get_version(package_name)