ref-agents 1.0.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.
Files changed (175) hide show
  1. ref_agents/__init__.py +9 -0
  2. ref_agents/api_keys.json.example +8 -0
  3. ref_agents/auth.py +129 -0
  4. ref_agents/codemap/..md +62 -0
  5. ref_agents/codemap/CODE_MAP.md +37 -0
  6. ref_agents/codemap/core.md +43 -0
  7. ref_agents/codemap/models.md +43 -0
  8. ref_agents/codemap/prompts.md +40 -0
  9. ref_agents/codemap/security.md +45 -0
  10. ref_agents/codemap/tools.md +94 -0
  11. ref_agents/codemap/tools_browser.md +44 -0
  12. ref_agents/codemap/utils.md +42 -0
  13. ref_agents/codemap/workflow.md +42 -0
  14. ref_agents/config/ai_patterns.yaml +101 -0
  15. ref_agents/config/frameworks/angular.yaml +104 -0
  16. ref_agents/config/frameworks/aspnet.yaml +84 -0
  17. ref_agents/config/frameworks/ef_core.yaml +81 -0
  18. ref_agents/config/frameworks/react.yaml +111 -0
  19. ref_agents/config/frameworks/spring_boot.yaml +117 -0
  20. ref_agents/config/languages/csharp.yaml +153 -0
  21. ref_agents/config/languages/java.yaml +188 -0
  22. ref_agents/config/languages/javascript.yaml +172 -0
  23. ref_agents/config/languages/python.yaml +153 -0
  24. ref_agents/config/languages/typescript.yaml +193 -0
  25. ref_agents/constants.py +553 -0
  26. ref_agents/core/__init__.py +15 -0
  27. ref_agents/core/config_loader.py +160 -0
  28. ref_agents/core/config_models.py +167 -0
  29. ref_agents/core/config_parsing.py +84 -0
  30. ref_agents/core/language_detector.py +388 -0
  31. ref_agents/core/validation_models.py +66 -0
  32. ref_agents/core/validation_primitives.py +176 -0
  33. ref_agents/errors.py +34 -0
  34. ref_agents/license_client.py +247 -0
  35. ref_agents/models/__init__.py +22 -0
  36. ref_agents/models/gherkin.py +45 -0
  37. ref_agents/models/hierarchy.py +80 -0
  38. ref_agents/models/invest.py +59 -0
  39. ref_agents/models/version.py +49 -0
  40. ref_agents/prompts/__init__.py +9 -0
  41. ref_agents/prompts/start_agent.py +772 -0
  42. ref_agents/rules/architecture/backend_patterns.md +43 -0
  43. ref_agents/rules/architecture/diagramming.md +100 -0
  44. ref_agents/rules/architecture/frontend_patterns.md +40 -0
  45. ref_agents/rules/architecture/impact_analysis.md +129 -0
  46. ref_agents/rules/architecture/migration_strategy.md +208 -0
  47. ref_agents/rules/architecture/regression_protocol.md +77 -0
  48. ref_agents/rules/architecture/system_design.md +97 -0
  49. ref_agents/rules/common/codemap_standard.md +97 -0
  50. ref_agents/rules/common/core_protocol.md +59 -0
  51. ref_agents/rules/common/prompt_engineering.md +294 -0
  52. ref_agents/rules/development/debugging.md +32 -0
  53. ref_agents/rules/development/implementation.md +205 -0
  54. ref_agents/rules/operations/completion.md +119 -0
  55. ref_agents/rules/operations/cutover_protocol.md +218 -0
  56. ref_agents/rules/operations/discovery.md +179 -0
  57. ref_agents/rules/operations/fix_workflow.md +87 -0
  58. ref_agents/rules/operations/forensics.md +278 -0
  59. ref_agents/rules/operations/platform.md +263 -0
  60. ref_agents/rules/operations/synchronous_flow.md +25 -0
  61. ref_agents/rules/product/ac_validation.md +25 -0
  62. ref_agents/rules/product/brainstorming.md +27 -0
  63. ref_agents/rules/product/ref_flow.md +101 -0
  64. ref_agents/rules/product/requirements_std.md +114 -0
  65. ref_agents/rules/product/spec_writing.md +235 -0
  66. ref_agents/rules/product/strategy.md +96 -0
  67. ref_agents/rules/quality/documentation_standards.md +46 -0
  68. ref_agents/rules/quality/parity_testing.md +234 -0
  69. ref_agents/rules/quality/project_documentation.md +56 -0
  70. ref_agents/rules/quality/qa_lead.md +111 -0
  71. ref_agents/rules/quality/test_design.md +146 -0
  72. ref_agents/rules/quality/testing_standards.md +293 -0
  73. ref_agents/rules/review/pr_review.md +116 -0
  74. ref_agents/rules/security/security_audit.md +83 -0
  75. ref_agents/security/__init__.py +22 -0
  76. ref_agents/security/dependency_audit.py +188 -0
  77. ref_agents/security/file_audit.py +208 -0
  78. ref_agents/security/network_scan.py +179 -0
  79. ref_agents/security/report_generator.py +313 -0
  80. ref_agents/security/secret_scan.py +252 -0
  81. ref_agents/security/url_scan.py +240 -0
  82. ref_agents/security_scan.py +236 -0
  83. ref_agents/server.py +1586 -0
  84. ref_agents/session.py +100 -0
  85. ref_agents/tool_names.py +55 -0
  86. ref_agents/tools/__init__.py +8 -0
  87. ref_agents/tools/agents_generator.py +315 -0
  88. ref_agents/tools/ai_pattern_detector.py +815 -0
  89. ref_agents/tools/brownfield_populator.py +529 -0
  90. ref_agents/tools/browser/__init__.py +50 -0
  91. ref_agents/tools/browser/evidence_verifier.py +302 -0
  92. ref_agents/tools/browser/execution_logger.py +249 -0
  93. ref_agents/tools/browser/playwright_mcp_client.py +259 -0
  94. ref_agents/tools/browser/screenshot_utils.py +184 -0
  95. ref_agents/tools/browser/test_executor.py +537 -0
  96. ref_agents/tools/code_quality_scanner.py +629 -0
  97. ref_agents/tools/codemap/..md +93 -0
  98. ref_agents/tools/codemap/CODE_MAP.md +30 -0
  99. ref_agents/tools/codemap/browser.md +44 -0
  100. ref_agents/tools/codemap.py +403 -0
  101. ref_agents/tools/codemap_freshness.py +234 -0
  102. ref_agents/tools/comment_smell_scanner.py +346 -0
  103. ref_agents/tools/complexity.py +436 -0
  104. ref_agents/tools/complexity_ast.py +333 -0
  105. ref_agents/tools/compliance.py +246 -0
  106. ref_agents/tools/compliance_remediation.py +846 -0
  107. ref_agents/tools/context_graph.py +839 -0
  108. ref_agents/tools/context_manager.py +550 -0
  109. ref_agents/tools/context_tools.py +121 -0
  110. ref_agents/tools/cross_repo_linker.py +393 -0
  111. ref_agents/tools/dead_code_scanner.py +637 -0
  112. ref_agents/tools/debt_scanner.py +1092 -0
  113. ref_agents/tools/dependency_graph.py +272 -0
  114. ref_agents/tools/discovery_audit.py +372 -0
  115. ref_agents/tools/docs_scanner.py +600 -0
  116. ref_agents/tools/evaluate_gate.py +119 -0
  117. ref_agents/tools/external_detector.py +524 -0
  118. ref_agents/tools/features_generator.py +282 -0
  119. ref_agents/tools/flow_gap_detector.py +373 -0
  120. ref_agents/tools/flow_mapper.py +327 -0
  121. ref_agents/tools/full_suite_runner.py +740 -0
  122. ref_agents/tools/gherkin_parser.py +227 -0
  123. ref_agents/tools/guard_tools.py +139 -0
  124. ref_agents/tools/handoff_tools.py +282 -0
  125. ref_agents/tools/health_scanner.py +1211 -0
  126. ref_agents/tools/hierarchy_manager.py +289 -0
  127. ref_agents/tools/invest_scorer.py +249 -0
  128. ref_agents/tools/jira_confluence_export.py +306 -0
  129. ref_agents/tools/json_output.py +76 -0
  130. ref_agents/tools/migration_mapper.py +946 -0
  131. ref_agents/tools/migration_readiness_scanner.py +209 -0
  132. ref_agents/tools/pattern_learner.py +522 -0
  133. ref_agents/tools/report_utils.py +155 -0
  134. ref_agents/tools/requirements_serializer.py +225 -0
  135. ref_agents/tools/security_audit_tool.py +106 -0
  136. ref_agents/tools/sequencing_engine.py +288 -0
  137. ref_agents/tools/summary_generator.py +275 -0
  138. ref_agents/tools/symbol_resolver.py +306 -0
  139. ref_agents/tools/symbol_smoke_runner.py +336 -0
  140. ref_agents/tools/test_plan_validator.py +189 -0
  141. ref_agents/tools/test_smell_walker.py +902 -0
  142. ref_agents/tools/tier1_fixer.py +502 -0
  143. ref_agents/tools/validators/__init__.py +419 -0
  144. ref_agents/tools/validators/architect.py +268 -0
  145. ref_agents/tools/validators/cutover_engineer.py +167 -0
  146. ref_agents/tools/validators/developer.py +180 -0
  147. ref_agents/tools/validators/discovery.py +150 -0
  148. ref_agents/tools/validators/forensic_engineer.py +191 -0
  149. ref_agents/tools/validators/impact_architect.py +181 -0
  150. ref_agents/tools/validators/migration_planner.py +166 -0
  151. ref_agents/tools/validators/parity_tester.py +155 -0
  152. ref_agents/tools/validators/platform_engineer.py +134 -0
  153. ref_agents/tools/validators/pr_reviewer.py +129 -0
  154. ref_agents/tools/validators/product_manager.py +291 -0
  155. ref_agents/tools/validators/qa_lead.py +172 -0
  156. ref_agents/tools/validators/scrum_master.py +212 -0
  157. ref_agents/tools/validators/security_owner.py +162 -0
  158. ref_agents/tools/validators/specifier.py +134 -0
  159. ref_agents/tools/validators/strategist.py +149 -0
  160. ref_agents/tools/validators/tester.py +121 -0
  161. ref_agents/tools/version_manager.py +202 -0
  162. ref_agents/tools/workflow_tools.py +1549 -0
  163. ref_agents/utils/__init__.py +21 -0
  164. ref_agents/utils/git_utils.py +351 -0
  165. ref_agents/utils/handoff_logger.py +368 -0
  166. ref_agents/utils/ignore_matcher.py +270 -0
  167. ref_agents/workflow/__init__.py +19 -0
  168. ref_agents/workflow/capabilities.py +328 -0
  169. ref_agents/workflow/state_machine.py +708 -0
  170. ref_agents/workflow/transitions.py +658 -0
  171. ref_agents-1.0.0.dist-info/METADATA +365 -0
  172. ref_agents-1.0.0.dist-info/RECORD +175 -0
  173. ref_agents-1.0.0.dist-info/WHEEL +4 -0
  174. ref_agents-1.0.0.dist-info/entry_points.txt +2 -0
  175. ref_agents-1.0.0.dist-info/licenses/LICENSE +115 -0
@@ -0,0 +1,21 @@
1
+ """REF Agents utilities package."""
2
+
3
+ from ref_agents.utils.git_utils import (
4
+ GitUser,
5
+ REF_CACHE_DIR,
6
+ find_project_root,
7
+ get_current_user_id,
8
+ get_git_root,
9
+ get_git_user,
10
+ get_ref_cache_dir,
11
+ )
12
+
13
+ __all__ = [
14
+ "GitUser",
15
+ "REF_CACHE_DIR",
16
+ "find_project_root",
17
+ "get_current_user_id",
18
+ "get_git_root",
19
+ "get_git_user",
20
+ "get_ref_cache_dir",
21
+ ]
@@ -0,0 +1,351 @@
1
+ """Git utilities for REF Agents.
2
+
3
+ Provides user identification, repository context, and project root detection.
4
+ """
5
+
6
+ import subprocess
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING
10
+
11
+ import structlog
12
+
13
+ if TYPE_CHECKING:
14
+ pass
15
+
16
+ logger = structlog.get_logger(__name__)
17
+
18
+ # Standard marker files for project root detection
19
+ PROJECT_MARKERS = (
20
+ "pyproject.toml",
21
+ "CODE_MAP.md",
22
+ "package.json",
23
+ "setup.py",
24
+ "Cargo.toml",
25
+ "go.mod",
26
+ )
27
+
28
+ # Cache directory name (consistent with .pytest_cache, .mypy_cache, .ruff_cache)
29
+ REF_CACHE_DIR = ".ref_cache"
30
+
31
+
32
+ @dataclass
33
+ class GitUser:
34
+ """Git user identity."""
35
+
36
+ email: str
37
+ name: str
38
+
39
+ def __str__(self) -> str:
40
+ """Return formatted user string."""
41
+ return f"{self.name} <{self.email}>"
42
+
43
+
44
+ def is_git_repo(directory: str | Path | None = None) -> bool:
45
+ """Check if directory is inside a git repository.
46
+
47
+ Args:
48
+ directory: Path to check. Defaults to current working directory.
49
+
50
+ Returns:
51
+ True if inside a git repo, False otherwise.
52
+ """
53
+ try:
54
+ cmd = ["git", "rev-parse", "--git-dir"]
55
+ subprocess.run(
56
+ cmd,
57
+ capture_output=True,
58
+ check=True,
59
+ cwd=str(directory) if directory else None,
60
+ )
61
+ return True
62
+ except subprocess.CalledProcessError:
63
+ return False
64
+ except FileNotFoundError:
65
+ logger.warning("git_not_found", msg="Git is not installed")
66
+ return False
67
+
68
+
69
+ def get_git_root(directory: str | Path | None = None) -> Path | None:
70
+ """Get git repository root directory.
71
+
72
+ Uses `git rev-parse --show-toplevel` - the industry standard approach.
73
+
74
+ Args:
75
+ directory: Directory to start search from.
76
+
77
+ Returns:
78
+ Path to git root, or None if not in a git repo.
79
+ """
80
+ try:
81
+ result = subprocess.run(
82
+ ["git", "rev-parse", "--show-toplevel"],
83
+ capture_output=True,
84
+ text=True,
85
+ check=True,
86
+ cwd=str(directory) if directory else None,
87
+ )
88
+ return Path(result.stdout.strip())
89
+ except subprocess.CalledProcessError:
90
+ return None
91
+ except FileNotFoundError:
92
+ logger.debug("git_not_installed")
93
+ return None
94
+
95
+
96
+ def find_project_root(start_dir: Path | None = None) -> Path | None:
97
+ """Find project root using marker file detection.
98
+
99
+ Priority (industry best practice):
100
+ 1. Session project_root (explicit user setting via set_project_root)
101
+ 2. Git root (most reliable - used by pytest, mypy, ruff)
102
+ 3. Marker files (pyproject.toml, CODE_MAP.md, package.json, etc.)
103
+
104
+ Args:
105
+ start_dir: Directory to start search from. Defaults to cwd.
106
+
107
+ Returns:
108
+ Project root path, or None if not found.
109
+ """
110
+ # Avoid circular import
111
+ from ref_agents.session import SessionManager
112
+
113
+ # Priority 1: Explicit session setting (highest priority)
114
+ session_root = SessionManager.get().project_root
115
+ if session_root and session_root.exists():
116
+ logger.debug("project_root_found", source="session", path=str(session_root))
117
+ return session_root
118
+
119
+ # Priority 2: Git root (most reliable)
120
+ git_root = get_git_root(start_dir)
121
+ if git_root:
122
+ logger.debug("project_root_found", source="git", path=str(git_root))
123
+ return git_root
124
+
125
+ # Priority 3: Marker file traversal
126
+ current = start_dir or Path.cwd()
127
+ for path in [current, *current.parents]:
128
+ if any((path / marker).exists() for marker in PROJECT_MARKERS):
129
+ logger.debug("project_root_found", source="marker", path=str(path))
130
+ return path
131
+
132
+ logger.debug("project_root_not_found")
133
+ return None
134
+
135
+
136
+ def get_ref_reports_dir(project_root: Path | None = None) -> Path:
137
+ """Get REF reports directory path (durable — not gitignored content).
138
+
139
+ Args:
140
+ project_root: Project root. If None, uses find_project_root().
141
+
142
+ Returns:
143
+ Path to ref-reports directory.
144
+
145
+ Raises:
146
+ RuntimeError: If project root cannot be determined.
147
+ """
148
+ if project_root is None:
149
+ project_root = find_project_root()
150
+ if project_root is None:
151
+ raise RuntimeError(
152
+ "Cannot determine project root. "
153
+ "Use set_project_root() or run from a git repository."
154
+ )
155
+ return project_root / "ref-reports"
156
+
157
+
158
+ def get_ref_cache_dir(project_root: Path | None = None) -> Path:
159
+ """Get REF cache directory path.
160
+
161
+ Args:
162
+ project_root: Project root. If None, uses find_project_root().
163
+
164
+ Returns:
165
+ Path to .ref_cache directory.
166
+
167
+ Raises:
168
+ RuntimeError: If project root cannot be determined.
169
+ """
170
+ if project_root is None:
171
+ project_root = find_project_root()
172
+ if project_root is None:
173
+ raise RuntimeError(
174
+ "Cannot determine project root. "
175
+ "Use set_project_root() or run from a git repository."
176
+ )
177
+ return project_root / REF_CACHE_DIR
178
+
179
+
180
+ def get_git_user(directory: str | Path | None = None) -> GitUser | None:
181
+ """Get git user from configuration.
182
+
183
+ Args:
184
+ directory: Repository directory. Defaults to current working directory.
185
+
186
+ Returns:
187
+ GitUser with email and name, or None if not in git repo or not configured.
188
+ """
189
+ if not is_git_repo(directory):
190
+ logger.debug("not_git_repo", directory=str(directory))
191
+ return None
192
+
193
+ cwd = str(directory) if directory else None
194
+
195
+ try:
196
+ email_result = subprocess.run(
197
+ ["git", "config", "user.email"],
198
+ capture_output=True,
199
+ text=True,
200
+ check=True,
201
+ cwd=cwd,
202
+ )
203
+ name_result = subprocess.run(
204
+ ["git", "config", "user.name"],
205
+ capture_output=True,
206
+ text=True,
207
+ check=True,
208
+ cwd=cwd,
209
+ )
210
+
211
+ email = email_result.stdout.strip()
212
+ name = name_result.stdout.strip()
213
+
214
+ if not email:
215
+ logger.warning("git_email_not_configured")
216
+ return None
217
+
218
+ return GitUser(email=email, name=name or "Unknown")
219
+
220
+ except subprocess.CalledProcessError as e:
221
+ logger.warning("git_config_error", error=str(e))
222
+ return None
223
+ except FileNotFoundError:
224
+ logger.warning("git_not_found")
225
+ return None
226
+
227
+
228
+ def get_current_user_id(directory: str | Path | None = None) -> str:
229
+ """Get current user identifier for logging.
230
+
231
+ Falls back to OS user if git not available.
232
+
233
+ Args:
234
+ directory: Repository directory.
235
+
236
+ Returns:
237
+ User identifier string (email or OS username).
238
+ """
239
+ import os
240
+
241
+ git_user = get_git_user(directory)
242
+ if git_user:
243
+ return git_user.email
244
+
245
+ # Fallback to OS user
246
+ return os.environ.get("USER", "unknown")
247
+
248
+
249
+ def get_changed_files(
250
+ directory: str | Path | None = None,
251
+ extensions: set[str] | None = None,
252
+ ) -> list[Path]:
253
+ """Get list of files changed since last commit.
254
+
255
+ Includes: modified, added, untracked files.
256
+
257
+ Args:
258
+ directory: Repository directory.
259
+ extensions: Filter by file extensions (e.g., {'.py', '.ts'}).
260
+
261
+ Returns:
262
+ List of changed file paths relative to repo root.
263
+ """
264
+ if not is_git_repo(directory):
265
+ return []
266
+
267
+ dir_path = Path(directory) if directory else Path.cwd()
268
+ changed: list[Path] = []
269
+
270
+ try:
271
+ result = subprocess.run(
272
+ ["git", "diff", "--name-only", "HEAD"],
273
+ capture_output=True,
274
+ text=True,
275
+ cwd=str(dir_path),
276
+ check=False,
277
+ )
278
+ if result.returncode == 0:
279
+ for line in result.stdout.strip().splitlines():
280
+ if line:
281
+ changed.append(Path(line))
282
+
283
+ result = subprocess.run(
284
+ ["git", "ls-files", "--others", "--exclude-standard"],
285
+ capture_output=True,
286
+ text=True,
287
+ cwd=str(dir_path),
288
+ check=False,
289
+ )
290
+ if result.returncode == 0:
291
+ for line in result.stdout.strip().splitlines():
292
+ if line:
293
+ changed.append(Path(line))
294
+
295
+ except FileNotFoundError:
296
+ logger.warning("git_not_found")
297
+ return []
298
+
299
+ # Filter by extension if specified
300
+ if extensions:
301
+ changed = [f for f in changed if f.suffix in extensions]
302
+
303
+ return list(set(changed))
304
+
305
+
306
+ def get_git_file_stats(file_path: Path) -> dict[str, int]:
307
+ """Get commit statistics for a file (churn analysis).
308
+
309
+ Args:
310
+ file_path: Path to the file.
311
+
312
+ Returns:
313
+ Dict with keys: commit_count, author_count.
314
+ """
315
+ stats = {"commit_count": 0, "author_count": 0}
316
+
317
+ if not is_git_repo(file_path.parent):
318
+ return stats
319
+
320
+ try:
321
+ # Get commit count
322
+ cmd_commits = ["git", "rev-list", "--count", "HEAD", "--", str(file_path)]
323
+ result_commits = subprocess.run(
324
+ cmd_commits,
325
+ capture_output=True,
326
+ text=True,
327
+ check=False,
328
+ cwd=str(file_path.parent),
329
+ )
330
+ if result_commits.returncode == 0 and result_commits.stdout.strip():
331
+ stats["commit_count"] = int(result_commits.stdout.strip())
332
+
333
+ # Get unique authors
334
+ cmd_authors = ["git", "log", "--format=%aE", "--", str(file_path)]
335
+ result_authors = subprocess.run(
336
+ cmd_authors,
337
+ capture_output=True,
338
+ text=True,
339
+ check=False,
340
+ cwd=str(file_path.parent),
341
+ )
342
+ if result_authors.returncode == 0:
343
+ authors = set(result_authors.stdout.strip().splitlines())
344
+ # Filter empty lines
345
+ authors = {a for a in authors if a}
346
+ stats["author_count"] = len(authors)
347
+
348
+ except (subprocess.SubprocessError, ValueError, OSError) as e:
349
+ logger.debug("git_stats_failed", file=str(file_path), error=str(e))
350
+
351
+ return stats