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,240 @@
1
+ """URL/Endpoint Scanner.
2
+
3
+ Scans files for URLs and API endpoints that could indicate
4
+ external data transmission or cloud service connections.
5
+ """
6
+
7
+ import re
8
+ import subprocess
9
+ from dataclasses import dataclass, field
10
+ from pathlib import Path
11
+
12
+ import structlog
13
+
14
+ logger = structlog.get_logger(__name__)
15
+
16
+ # Directories to skip during scanning
17
+ IGNORED_DIRS: set[str] = {
18
+ ".venv",
19
+ "venv",
20
+ ".env",
21
+ "node_modules",
22
+ ".git",
23
+ "dist",
24
+ "build",
25
+ ".tox",
26
+ }
27
+
28
+
29
+ def _should_skip(path: Path) -> bool:
30
+ """Check if path should be skipped."""
31
+ for part in path.parts:
32
+ if part in IGNORED_DIRS:
33
+ return True
34
+ if part.endswith("_cache") or part.endswith("cache__"):
35
+ return True
36
+ return False
37
+
38
+
39
+ # Patterns to detect URLs and endpoints
40
+ URL_PATTERNS: list[tuple[str, re.Pattern[str]]] = [
41
+ ("HTTP/HTTPS URL", re.compile(r'https?://[^\s\'"<>)}\]]+', re.IGNORECASE)),
42
+ ("WebSocket URL", re.compile(r'wss?://[^\s\'"<>)}\]]+', re.IGNORECASE)),
43
+ ("FTP URL", re.compile(r'ftp://[^\s\'"<>)}\]]+', re.IGNORECASE)),
44
+ ]
45
+
46
+ # URLs that are safe and expected (documentation, localhost, etc.)
47
+ WHITELIST_PATTERNS: list[re.Pattern[str]] = [
48
+ re.compile(r"https?://localhost", re.IGNORECASE),
49
+ re.compile(r"https?://127\.0\.0\.1", re.IGNORECASE),
50
+ re.compile(r"https?://0\.0\.0\.0", re.IGNORECASE),
51
+ re.compile(r"https?://example\.com", re.IGNORECASE),
52
+ re.compile(r"https?://example\.org", re.IGNORECASE),
53
+ # Documentation and reference links
54
+ re.compile(r"https?://modelcontextprotocol\.io", re.IGNORECASE),
55
+ re.compile(r"https?://github\.com", re.IGNORECASE),
56
+ re.compile(r"https?://ghcr\.io", re.IGNORECASE),
57
+ re.compile(r"https?://files\.pythonhosted\.org", re.IGNORECASE),
58
+ re.compile(r"https?://pypi\.org", re.IGNORECASE),
59
+ re.compile(r"https?://docs\.pytest\.org", re.IGNORECASE),
60
+ # Schema references (not actual URLs called at runtime)
61
+ re.compile(r"https?://json-schema\.org", re.IGNORECASE),
62
+ re.compile(r"https?://www\.w3\.org", re.IGNORECASE),
63
+ re.compile(r"https?://docs\.astral\.sh", re.IGNORECASE),
64
+ re.compile(r"https?://blog\.crewai\.com", re.IGNORECASE),
65
+ re.compile(r"https?://api\.example\.com", re.IGNORECASE),
66
+ re.compile(r"https?://astral\.sh", re.IGNORECASE),
67
+ re.compile(r"https?://api\.stripe\.com", re.IGNORECASE),
68
+ re.compile(r"https?://stripe\.com", re.IGNORECASE),
69
+ re.compile(r"https?://boto3\.amazonaws\.com", re.IGNORECASE),
70
+ # Common Badges and Libraries
71
+ re.compile(r"https?://img\.shields\.io", re.IGNORECASE),
72
+ re.compile(r"https?://www\.structlog\.org", re.IGNORECASE),
73
+ # vis.js CDN — used in context_graph.to_html() for interactive graph viewer
74
+ re.compile(r"https?://cdn\.jsdelivr\.net", re.IGNORECASE),
75
+ # FastMCP Cloud schema references (not actual URLs called at runtime)
76
+ re.compile(r"https?://gofastmcp\.com", re.IGNORECASE),
77
+ # Microsoft XML schema references in .csproj / docs (not called at runtime)
78
+ re.compile(r"https?://schemas\.microsoft\.com", re.IGNORECASE),
79
+ ]
80
+
81
+
82
+ @dataclass
83
+ class URLScanResult:
84
+ """Result of URL scan."""
85
+
86
+ passed: bool
87
+ files_scanned: int
88
+ external_urls: list[dict[str, str]] = field(default_factory=list)
89
+ whitelisted_urls: list[dict[str, str]] = field(default_factory=list)
90
+
91
+ @property
92
+ def summary(self) -> str:
93
+ """One-line summary for CLI output."""
94
+ if self.passed:
95
+ return f"0 external URLs found in {self.files_scanned} files"
96
+ return f"{len(self.external_urls)} external URLs found"
97
+
98
+
99
+ def _is_whitelisted(url: str) -> bool:
100
+ """Check if URL matches any whitelist pattern."""
101
+ # Skip regex pattern fragments (contain backslashes or regex metacharacters)
102
+ if "\\" in url or "[^" in url or "\\s" in url:
103
+ return True
104
+ return any(pattern.search(url) for pattern in WHITELIST_PATTERNS)
105
+
106
+
107
+ def _should_scan_file(file_path: Path, include_extensions: set[str]) -> bool:
108
+ """Check if file should be scanned."""
109
+ # Skip directories and non-matching extensions
110
+ if file_path.is_dir():
111
+ return False
112
+ if file_path.suffix not in include_extensions:
113
+ return False
114
+ if _should_skip(file_path):
115
+ return False
116
+ # Skip test files (they contain test data)
117
+ if "/tests/" in str(file_path) or "test_" in file_path.name:
118
+ return False
119
+ # Skip generated reports
120
+ if "ref-reports" in str(file_path):
121
+ return False
122
+ # Skip lock files (they contain many package URLs)
123
+ if file_path.name in {"uv.lock", "poetry.lock", "package-lock.json"}:
124
+ return False
125
+ # Skip generated JSON reports (bandit, pip-audit)
126
+ if file_path.name in {"bandit-report.json", "pip-audit-report.json"}:
127
+ return False
128
+ # User defined ignores
129
+ if "GAP_ANALYSIS.md" in file_path.name:
130
+ return False
131
+ if "docs/misc" in str(file_path):
132
+ return False
133
+ return True
134
+
135
+
136
+ def _process_file_urls(
137
+ file_path: Path,
138
+ directory: Path,
139
+ external_urls: list[dict[str, str]],
140
+ whitelisted_urls: list[dict[str, str]],
141
+ ) -> None:
142
+ """Process a single file for URLs."""
143
+ try:
144
+ content = file_path.read_text(encoding="utf-8")
145
+ except UnicodeDecodeError:
146
+ return
147
+
148
+ relative_path = str(file_path.relative_to(directory))
149
+
150
+ for pattern_name, pattern in URL_PATTERNS:
151
+ for match in pattern.finditer(content):
152
+ url = match.group(0)
153
+ # Clean up trailing punctuation
154
+ url = url.rstrip(".,;:\"')")
155
+
156
+ url_info = {
157
+ "file": relative_path,
158
+ "url": url,
159
+ "type": pattern_name,
160
+ }
161
+
162
+ if _is_whitelisted(url):
163
+ whitelisted_urls.append(url_info)
164
+ else:
165
+ external_urls.append(url_info)
166
+ logger.info(
167
+ "external_url_found",
168
+ file=relative_path,
169
+ url=url,
170
+ )
171
+
172
+
173
+ def _iter_scannable_files(directory: Path, include_extensions: set[str]) -> list[Path]:
174
+ """Return files to scan, preferring git-tracked files when inside a repo.
175
+
176
+ Falls back to rglob when not in a git repository (e.g., CI without full
177
+ checkout).
178
+ """
179
+ try:
180
+ result = subprocess.run(
181
+ ["git", "ls-files", "--cached", "--others", "--exclude-standard"],
182
+ cwd=directory,
183
+ capture_output=True,
184
+ text=True,
185
+ timeout=10,
186
+ )
187
+ if result.returncode == 0:
188
+ paths = []
189
+ for line in result.stdout.splitlines():
190
+ p = directory / line.strip()
191
+ if p.suffix in include_extensions and p.exists():
192
+ paths.append(p)
193
+ return paths
194
+ except (subprocess.TimeoutExpired, FileNotFoundError):
195
+ pass
196
+ return list(directory.rglob("*"))
197
+
198
+
199
+ def scan_urls(
200
+ directory: Path,
201
+ include_extensions: set[str] | None = None,
202
+ ) -> URLScanResult:
203
+ """
204
+ Scan files for external URLs and endpoints.
205
+
206
+ Args:
207
+ directory: Root directory to scan
208
+ include_extensions: File extensions to scan (default: .py, .md, .txt, .json)
209
+
210
+ Returns:
211
+ URLScanResult with pass/fail status and found URLs
212
+ """
213
+ include_extensions = include_extensions or {".py", ".md", ".txt", ".json", ".toml"}
214
+ external_urls: list[dict[str, str]] = []
215
+ whitelisted_urls: list[dict[str, str]] = []
216
+ files_scanned = 0
217
+
218
+ for file_path in _iter_scannable_files(directory, include_extensions):
219
+ if not _should_scan_file(file_path, include_extensions):
220
+ continue
221
+
222
+ files_scanned += 1
223
+ _process_file_urls(file_path, directory, external_urls, whitelisted_urls)
224
+
225
+ passed = len(external_urls) == 0
226
+
227
+ logger.info(
228
+ "url_scan_complete",
229
+ passed=passed,
230
+ files_scanned=files_scanned,
231
+ external_count=len(external_urls),
232
+ whitelisted_count=len(whitelisted_urls),
233
+ )
234
+
235
+ return URLScanResult(
236
+ passed=passed,
237
+ files_scanned=files_scanned,
238
+ external_urls=external_urls,
239
+ whitelisted_urls=whitelisted_urls,
240
+ )
@@ -0,0 +1,236 @@
1
+ """REF Agents Security Scanner CLI.
2
+
3
+ A single command that runs all security checks and produces a professional
4
+ security audit report for enterprise customers.
5
+
6
+ Usage:
7
+ uv run python -m ref_agents.security_scan
8
+ uv run python -m ref_agents.security_scan --verbose
9
+ uv run python -m ref_agents.security_scan --format json
10
+ """
11
+
12
+ import argparse
13
+ import json
14
+ import sys
15
+ from datetime import datetime
16
+ from pathlib import Path
17
+
18
+ import structlog
19
+
20
+ from ref_agents.constants import Icons, Status
21
+ from ref_agents.security.dependency_audit import audit_dependencies
22
+ from ref_agents.security.file_audit import audit_file_operations
23
+ from ref_agents.security.network_scan import scan_network_imports
24
+ from ref_agents.security.report_generator import SecurityReport, save_report
25
+ from ref_agents.security.secret_scan import scan_secrets
26
+ from ref_agents.security.url_scan import scan_urls
27
+
28
+ # Configure structlog for CLI output
29
+ structlog.configure(
30
+ processors=[
31
+ structlog.processors.TimeStamper(fmt="iso"),
32
+ structlog.processors.JSONRenderer(),
33
+ ]
34
+ )
35
+ logger = structlog.get_logger(__name__)
36
+
37
+
38
+ def echo(message: str = "") -> None:
39
+ """Write message to stdout for CLI output.
40
+
41
+ Args:
42
+ message: Text to display. Empty string prints blank line.
43
+ """
44
+ sys.stdout.write(message + "\n")
45
+ sys.stdout.flush()
46
+
47
+
48
+ def print_status(check_num: int, total: int, name: str, passed: bool) -> None:
49
+ """Print formatted status line to stdout."""
50
+ status = Status.PASS if passed else Status.FAIL
51
+ dots = "." * (30 - len(name))
52
+ echo(f"[{check_num}/{total}] {name}{dots} {status}")
53
+
54
+
55
+ def run_security_scan(
56
+ directory: Path,
57
+ verbose: bool = False,
58
+ output_format: str = "text",
59
+ no_report: bool = False,
60
+ ) -> bool:
61
+ """
62
+ Run all security scans and generate report.
63
+
64
+ Args:
65
+ directory: Root directory to scan
66
+ verbose: Print detailed output
67
+ output_format: Output format (text, json, markdown)
68
+ no_report: If True, skip saving report to disk
69
+
70
+ Returns:
71
+ True if all scans passed, False otherwise
72
+ """
73
+ echo(Icons.HEAVY_LINE * 60)
74
+ echo(" REF-AGENTS SECURITY AUDIT")
75
+ echo(Icons.HEAVY_LINE * 60)
76
+ echo()
77
+ echo(f"Scanning: {directory}")
78
+ echo(f"Started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
79
+ echo()
80
+
81
+ # Run all scans
82
+ src_dir = directory / "src"
83
+ if not src_dir.exists():
84
+ src_dir = directory
85
+
86
+ # 1. Network Import Scan
87
+ if verbose:
88
+ echo("Running network import scan...")
89
+ network_result = scan_network_imports(src_dir)
90
+ print_status(1, 6, "Network Import Scan", network_result.passed)
91
+
92
+ # 2. URL Scan
93
+ if verbose:
94
+ echo("Running URL scan...")
95
+ url_result = scan_urls(directory)
96
+ print_status(2, 6, "Outbound URL Scan", url_result.passed)
97
+
98
+ # 3. Secret Scan
99
+ if verbose:
100
+ echo("Running secret scan...")
101
+ secret_result = scan_secrets(directory)
102
+ print_status(3, 6, "Secret Detection", secret_result.passed)
103
+
104
+ # 4. Dependency Audit
105
+ if verbose:
106
+ echo("Running dependency audit...")
107
+ dependency_result = audit_dependencies(directory)
108
+ print_status(4, 6, "Dependency Audit", dependency_result.passed)
109
+
110
+ # 5. License Check (part of dependency audit)
111
+ license_passed = len(dependency_result.unsafe_licenses) == 0
112
+ print_status(5, 6, "License Check", license_passed)
113
+
114
+ # 6. File Operation Audit
115
+ if verbose:
116
+ echo("Running file operation audit...")
117
+ file_result = audit_file_operations(src_dir)
118
+ print_status(6, 6, "File Operation Audit", file_result.passed)
119
+
120
+ echo()
121
+
122
+ # Generate report
123
+ report = SecurityReport(
124
+ network_result=network_result,
125
+ url_result=url_result,
126
+ secret_result=secret_result,
127
+ dependency_result=dependency_result,
128
+ file_result=file_result,
129
+ generated_at=datetime.now(),
130
+ )
131
+
132
+ if output_format == "json":
133
+ # JSON output for CI
134
+ result = {
135
+ "passed": report.overall_passed,
136
+ "timestamp": report.generated_at.isoformat(),
137
+ "checks": {
138
+ "network_scan": {
139
+ "passed": network_result.passed,
140
+ "violations": len(network_result.violations),
141
+ },
142
+ "url_scan": {
143
+ "passed": url_result.passed,
144
+ "external_urls": len(url_result.external_urls),
145
+ },
146
+ "secret_scan": {
147
+ "passed": secret_result.passed,
148
+ "secrets_found": len(secret_result.secrets_found),
149
+ },
150
+ "dependency_audit": {
151
+ "passed": dependency_result.passed,
152
+ "dependencies": len(dependency_result.dependencies),
153
+ },
154
+ "file_audit": {
155
+ "passed": file_result.passed,
156
+ "dangerous_ops": len(file_result.dangerous_operations),
157
+ },
158
+ },
159
+ }
160
+ echo(json.dumps(result, indent=2))
161
+ else:
162
+ # Save markdown report (unless --no-report)
163
+ if not no_report:
164
+ report_path = save_report(report)
165
+ echo(f"Report saved: {report_path}")
166
+ echo()
167
+
168
+ # Overall status
169
+ if report.overall_passed:
170
+ echo(Icons.HEAVY_LINE * 60)
171
+ echo(f" {Status.SUCCESS} ALL CHECKS PASSED")
172
+ echo(Icons.HEAVY_LINE * 60)
173
+ else:
174
+ echo(Icons.HEAVY_LINE * 60)
175
+ echo(f" {Status.ERROR} SOME CHECKS FAILED")
176
+ echo(Icons.HEAVY_LINE * 60)
177
+ echo()
178
+ echo("Review the report for details on failures.")
179
+
180
+ return report.overall_passed
181
+
182
+
183
+ def main() -> int:
184
+ """CLI entry point."""
185
+ parser = argparse.ArgumentParser(
186
+ description="REF Agents Security Scanner - Enterprise Security Audit Tool"
187
+ )
188
+ parser.add_argument(
189
+ "--directory",
190
+ "-d",
191
+ type=Path,
192
+ default=Path.cwd(),
193
+ help="Directory to scan (default: current directory)",
194
+ )
195
+ parser.add_argument(
196
+ "--verbose",
197
+ "-v",
198
+ action="store_true",
199
+ help="Print detailed output",
200
+ )
201
+ parser.add_argument(
202
+ "--format",
203
+ "-f",
204
+ choices=["text", "json"],
205
+ default="text",
206
+ help="Output format (default: text)",
207
+ )
208
+ parser.add_argument(
209
+ "--no-report",
210
+ action="store_true",
211
+ help="Skip saving report to disk (for pre-commit hooks)",
212
+ )
213
+
214
+ args = parser.parse_args()
215
+
216
+ # Find project root (look for pyproject.toml)
217
+ directory = args.directory.resolve()
218
+ if not (directory / "pyproject.toml").exists():
219
+ # Try parent directories
220
+ for parent in directory.parents:
221
+ if (parent / "pyproject.toml").exists():
222
+ directory = parent
223
+ break
224
+
225
+ passed = run_security_scan(
226
+ directory=directory,
227
+ verbose=args.verbose,
228
+ output_format=args.format,
229
+ no_report=args.no_report,
230
+ )
231
+
232
+ return 0 if passed else 1
233
+
234
+
235
+ if __name__ == "__main__":
236
+ sys.exit(main())