aria-code 4.1.3__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 (284) hide show
  1. agents/__init__.py +32 -0
  2. agents/base.py +190 -0
  3. agents/deep/__init__.py +37 -0
  4. agents/deep/calibration_loop.py +144 -0
  5. agents/deep/critic.py +125 -0
  6. agents/deep/deepen.py +193 -0
  7. agents/deep/models.py +149 -0
  8. agents/deep/pipeline.py +164 -0
  9. agents/deep/quant_fusion.py +192 -0
  10. agents/deep/themes.py +95 -0
  11. agents/deep/tiers.py +106 -0
  12. agents/financial/__init__.py +10 -0
  13. agents/financial/catalyst.py +279 -0
  14. agents/financial/debate.py +145 -0
  15. agents/financial/earnings.py +303 -0
  16. agents/financial/fundamental.py +159 -0
  17. agents/financial/macro.py +99 -0
  18. agents/financial/news.py +207 -0
  19. agents/financial/risk.py +132 -0
  20. agents/financial/sector.py +279 -0
  21. agents/financial/synthesis.py +274 -0
  22. agents/financial/technical.py +258 -0
  23. agents/portfolio_agent.py +333 -0
  24. agents/realty/__init__.py +62 -0
  25. agents/realty/asset_diagnosis.py +150 -0
  26. agents/realty/business_match.py +165 -0
  27. agents/realty/cashflow_verify.py +208 -0
  28. agents/realty/contract_rules.py +209 -0
  29. agents/realty/energy_anomaly.py +188 -0
  30. agents/realty/exit_settlement.py +207 -0
  31. agents/realty/fulfillment_risk.py +205 -0
  32. agents/realty/ops_optimize.py +159 -0
  33. agents/realty/revenue_share.py +214 -0
  34. agents/registry.py +144 -0
  35. agents/sports/__init__.py +0 -0
  36. agents/sports/football_agent.py +169 -0
  37. agents/team.py +289 -0
  38. aliyun_data_client.py +660 -0
  39. apps/README.md +12 -0
  40. apps/__init__.py +2 -0
  41. apps/channels/README.md +15 -0
  42. apps/cli/README.md +13 -0
  43. apps/cli/__init__.py +2 -0
  44. apps/cli/bootstrap.py +99 -0
  45. apps/cli/codegen_paths.py +29 -0
  46. apps/cli/commands/__init__.py +16 -0
  47. apps/cli/commands/analysis_cmds.py +288 -0
  48. apps/cli/commands/backtest_cmds.py +1887 -0
  49. apps/cli/commands/broker_cmds.py +1154 -0
  50. apps/cli/commands/business_workflow_cmds.py +289 -0
  51. apps/cli/commands/catalog.py +84 -0
  52. apps/cli/commands/data_cmds.py +405 -0
  53. apps/cli/commands/diagnostic_cmds.py +179 -0
  54. apps/cli/commands/diagnostic_ops_cmds.py +696 -0
  55. apps/cli/commands/finance_render.py +12 -0
  56. apps/cli/commands/market.py +399 -0
  57. apps/cli/commands/market_cmds.py +1276 -0
  58. apps/cli/commands/market_context.py +425 -0
  59. apps/cli/commands/market_render.py +7 -0
  60. apps/cli/commands/model_cmds.py +1579 -0
  61. apps/cli/commands/ops_cmds.py +668 -0
  62. apps/cli/commands/portfolio_cmds.py +962 -0
  63. apps/cli/commands/report.py +377 -0
  64. apps/cli/commands/scaffold_templates.py +617 -0
  65. apps/cli/commands/session_cmds.py +179 -0
  66. apps/cli/commands/session_ux_cmds.py +280 -0
  67. apps/cli/commands/team.py +588 -0
  68. apps/cli/commands/team_render.py +8 -0
  69. apps/cli/commands/ui_cmds.py +358 -0
  70. apps/cli/commands/workflow_cmds.py +279 -0
  71. apps/cli/commands/workspace_cmds.py +1414 -0
  72. apps/cli/config_paths.py +70 -0
  73. apps/cli/config_store.py +61 -0
  74. apps/cli/deterministic.py +122 -0
  75. apps/cli/direct.py +48 -0
  76. apps/cli/github_app_auth.py +135 -0
  77. apps/cli/handlers/__init__.py +11 -0
  78. apps/cli/handlers/broker_handlers.py +122 -0
  79. apps/cli/handlers/chart_handlers.py +1309 -0
  80. apps/cli/handlers/market_handlers.py +2509 -0
  81. apps/cli/handlers/realty_handlers.py +114 -0
  82. apps/cli/handlers/strategy_advice.py +82 -0
  83. apps/cli/hooks.py +180 -0
  84. apps/cli/i18n.py +284 -0
  85. apps/cli/intent.py +136 -0
  86. apps/cli/intent_router.py +217 -0
  87. apps/cli/lifecycle_hooks.py +48 -0
  88. apps/cli/main.py +29 -0
  89. apps/cli/market_metadata.py +135 -0
  90. apps/cli/market_universe.py +265 -0
  91. apps/cli/message_processing.py +257 -0
  92. apps/cli/plan_mode.py +139 -0
  93. apps/cli/plotly_html.py +15 -0
  94. apps/cli/prediction_feedback.py +202 -0
  95. apps/cli/preflight.py +497 -0
  96. apps/cli/project_aria.py +60 -0
  97. apps/cli/prompts/__init__.py +0 -0
  98. apps/cli/prompts/coding.py +658 -0
  99. apps/cli/prompts/system_prompts.py +531 -0
  100. apps/cli/prompts/ui.py +434 -0
  101. apps/cli/providers/__init__.py +1 -0
  102. apps/cli/providers/base.py +271 -0
  103. apps/cli/providers/chat_routing.py +80 -0
  104. apps/cli/providers/llm/__init__.py +1 -0
  105. apps/cli/providers/llm/ollama_stream.py +1170 -0
  106. apps/cli/providers/llm/sse_stream.py +216 -0
  107. apps/cli/providers/runtime_bridge.py +185 -0
  108. apps/cli/runtime_consumer.py +489 -0
  109. apps/cli/session_export.py +87 -0
  110. apps/cli/session_jsonl.py +207 -0
  111. apps/cli/session_store.py +112 -0
  112. apps/cli/todo_tracker.py +190 -0
  113. apps/cli/tools/__init__.py +40 -0
  114. apps/cli/tools/context.py +46 -0
  115. apps/cli/tools/file_tools.py +112 -0
  116. apps/cli/tools/market_tools.py +549 -0
  117. apps/cli/tools/notebook_tools.py +111 -0
  118. apps/cli/tools/system_tools.py +669 -0
  119. apps/cli/tools/write_tools.py +715 -0
  120. apps/cli/tradingview_bridge.py +434 -0
  121. apps/cli/update_check.py +152 -0
  122. apps/cli/utils/__init__.py +0 -0
  123. apps/cli/utils/market_detect.py +1578 -0
  124. apps/daemon/README.md +14 -0
  125. apps/vscode/README.md +115 -0
  126. apps/vscode/package.json +70 -0
  127. aria_cli.py +11636 -0
  128. aria_code-4.1.3.dist-info/METADATA +952 -0
  129. aria_code-4.1.3.dist-info/RECORD +284 -0
  130. aria_code-4.1.3.dist-info/WHEEL +5 -0
  131. aria_code-4.1.3.dist-info/entry_points.txt +2 -0
  132. aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
  133. aria_code-4.1.3.dist-info/top_level.txt +50 -0
  134. aria_daemon.py +1295 -0
  135. aria_feishu_bot.py +1359 -0
  136. aria_relay_client.py +182 -0
  137. aria_relay_server.py +405 -0
  138. aria_telegram_bot.py +202 -0
  139. ariarc.py +328 -0
  140. artifacts.py +491 -0
  141. backtest_report.py +472 -0
  142. brokers/__init__.py +72 -0
  143. brokers/base.py +207 -0
  144. brokers/capabilities.py +264 -0
  145. brokers/cn/__init__.py +10 -0
  146. brokers/cn/easytrader_broker.py +193 -0
  147. brokers/cn/futu_broker.py +194 -0
  148. brokers/cn/longbridge_broker.py +190 -0
  149. brokers/cn/tiger_broker.py +196 -0
  150. brokers/cn/xtquant_broker.py +175 -0
  151. brokers/config.py +364 -0
  152. brokers/intl/__init__.py +5 -0
  153. brokers/intl/alpaca_broker.py +183 -0
  154. brokers/intl/ibkr_broker.py +215 -0
  155. brokers/intl/webull_broker.py +156 -0
  156. brokers/paper_broker.py +259 -0
  157. brokers/planning.py +296 -0
  158. brokers/registry.py +181 -0
  159. brokers/trading.py +237 -0
  160. change_store.py +127 -0
  161. command_safety.py +19 -0
  162. computer_use_tools.py +504 -0
  163. dashboard_generator.py +578 -0
  164. data_analysis_tools.py +808 -0
  165. data_cleaner.py +483 -0
  166. data_service.py +481 -0
  167. datasources/__init__.py +23 -0
  168. datasources/base.py +166 -0
  169. datasources/router.py +221 -0
  170. datasources/sources/__init__.py +15 -0
  171. datasources/sources/akshare_source.py +269 -0
  172. datasources/sources/alpha_vantage_source.py +202 -0
  173. datasources/sources/edgar_source.py +218 -0
  174. datasources/sources/finnhub_source.py +197 -0
  175. datasources/sources/fred_source.py +219 -0
  176. datasources/sources/tushare_source.py +141 -0
  177. datasources/sources/web_scraper_source.py +278 -0
  178. datasources/sources/world_bank_source.py +205 -0
  179. datasources/sources/yfinance_source.py +152 -0
  180. demo_player.py +204 -0
  181. doctor.py +508 -0
  182. file_analysis_tools.py +734 -0
  183. finance_formulas.py +389 -0
  184. football_data_client.py +1670 -0
  185. intent_classifier.py +358 -0
  186. local_finance_tools.py +3221 -0
  187. local_llm_provider.py +552 -0
  188. macro_tools.py +368 -0
  189. market_data_client.py +1899 -0
  190. mcp_client.py +506 -0
  191. memory_manager.py +245 -0
  192. model_capability.py +416 -0
  193. notification_tools.py +248 -0
  194. packages/__init__.py +23 -0
  195. packages/aria_agents/__init__.py +5 -0
  196. packages/aria_agents/manifest.py +69 -0
  197. packages/aria_core/__init__.py +34 -0
  198. packages/aria_core/architecture.py +192 -0
  199. packages/aria_core/export.py +124 -0
  200. packages/aria_core/manifest.py +65 -0
  201. packages/aria_infra/__init__.py +15 -0
  202. packages/aria_infra/arthera.py +52 -0
  203. packages/aria_infra/doctor.py +246 -0
  204. packages/aria_infra/product.py +37 -0
  205. packages/aria_mcp/__init__.py +25 -0
  206. packages/aria_mcp/bridge.py +38 -0
  207. packages/aria_mcp/config.py +97 -0
  208. packages/aria_mcp/tools.py +61 -0
  209. packages/aria_sdk/__init__.py +19 -0
  210. packages/aria_sdk/client.py +396 -0
  211. packages/aria_sdk/providers.py +70 -0
  212. packages/aria_sdk/streaming.py +73 -0
  213. packages/aria_sdk/types.py +86 -0
  214. packages/aria_services/__init__.py +55 -0
  215. packages/aria_services/context.py +258 -0
  216. packages/aria_services/data.py +11 -0
  217. packages/aria_services/provider_health.py +189 -0
  218. packages/aria_services/registry.py +213 -0
  219. packages/aria_services/usage.py +138 -0
  220. packages/aria_skills/__init__.py +5 -0
  221. packages/aria_skills/registry.py +59 -0
  222. packages/aria_tools/__init__.py +5 -0
  223. packages/aria_tools/registry.py +128 -0
  224. packages/quant_engine/__init__.py +6 -0
  225. packages/quant_engine/sports/__init__.py +72 -0
  226. packages/quant_engine/sports/calibrator.py +353 -0
  227. packages/quant_engine/sports/dixon_coles.py +234 -0
  228. packages/quant_engine/sports/elo.py +299 -0
  229. packages/quant_engine/sports/form.py +188 -0
  230. packages/quant_engine/sports/h2h.py +195 -0
  231. packages/quant_engine/sports/ml_model.py +354 -0
  232. packages/quant_engine/sports/predictor.py +311 -0
  233. packages/quant_engine/sports/tracker.py +664 -0
  234. packages/quant_engine/stochastic/__init__.py +27 -0
  235. packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  236. packages/quant_engine/stochastic/ito_calculus.py +477 -0
  237. packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  238. packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  239. packages/quant_engine/stochastic/options_pricing.py +573 -0
  240. packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  241. plan_utils.py +194 -0
  242. plugin_loader.py +328 -0
  243. portfolio_ledger.py +262 -0
  244. privacy/__init__.py +5 -0
  245. privacy/feedback.py +123 -0
  246. project_tools.py +525 -0
  247. providers/__init__.py +30 -0
  248. providers/llm/__init__.py +19 -0
  249. providers/llm/anthropic.py +184 -0
  250. providers/llm/base.py +139 -0
  251. providers/llm/ollama.py +128 -0
  252. providers/llm/openai_compat.py +282 -0
  253. providers/llm/registry.py +358 -0
  254. realty_data_tools.py +659 -0
  255. report_generator.py +1314 -0
  256. runtime/__init__.py +103 -0
  257. runtime/agent_loop.py +1183 -0
  258. runtime/approval.py +51 -0
  259. runtime/events.py +102 -0
  260. runtime/gateway.py +128 -0
  261. runtime/lsp.py +346 -0
  262. runtime/subagent.py +258 -0
  263. runtime/tool_executor.py +104 -0
  264. runtime/tool_policy.py +106 -0
  265. safety/__init__.py +21 -0
  266. safety/permissions.py +275 -0
  267. setup_wizard.py +653 -0
  268. strategy_vault.py +420 -0
  269. ui/__init__.py +100 -0
  270. ui/banner.py +310 -0
  271. ui/completer.py +391 -0
  272. ui/console.py +271 -0
  273. ui/image_render.py +243 -0
  274. ui/input_box.py +376 -0
  275. ui/picker.py +195 -0
  276. ui/render/__init__.py +11 -0
  277. ui/render/finance.py +1480 -0
  278. ui/render/market.py +225 -0
  279. ui/render/output.py +681 -0
  280. ui/render/team.py +346 -0
  281. ui/robot.py +235 -0
  282. workspace/__init__.py +6 -0
  283. workspace/files.py +170 -0
  284. workspace/verify.py +113 -0
workspace/files.py ADDED
@@ -0,0 +1,170 @@
1
+ """Safe workspace file operations for Aria Code."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pathlib
6
+ import re
7
+ import tempfile
8
+ from dataclasses import dataclass
9
+ from typing import Any, Dict, List
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class ReadResult:
14
+ path: str
15
+ lines: int
16
+ content: str
17
+
18
+
19
+ class WorkspaceSecurity:
20
+ """Path security policy shared by CLI tools and future runtimes."""
21
+
22
+ BLOCKED_ROOTS = (
23
+ "/etc",
24
+ "/sys",
25
+ "/proc",
26
+ "/dev",
27
+ "/boot",
28
+ "/root",
29
+ )
30
+
31
+ def __init__(self, cwd: str | pathlib.Path | None = None) -> None:
32
+ self.cwd = pathlib.Path(cwd or pathlib.Path.cwd()).expanduser().resolve()
33
+
34
+ def allowed_roots(self) -> List[pathlib.Path]:
35
+ roots = [pathlib.Path.home().resolve(), pathlib.Path("/tmp").resolve(), self.cwd]
36
+ for tmp_candidate in (
37
+ "/var/folders",
38
+ "/private/tmp",
39
+ "/private/var/folders",
40
+ "/var/tmp",
41
+ "/private/var/tmp",
42
+ tempfile.gettempdir(),
43
+ ):
44
+ try:
45
+ root = pathlib.Path(tmp_candidate).resolve()
46
+ if root not in roots:
47
+ roots.append(root)
48
+ except Exception:
49
+ pass
50
+ return roots
51
+
52
+ def resolve(self, path: str | pathlib.Path) -> pathlib.Path:
53
+ return pathlib.Path(path).expanduser().resolve()
54
+
55
+ def is_safe_path(self, path: str | pathlib.Path) -> bool:
56
+ resolved = self.resolve(path)
57
+ for blocked in self.BLOCKED_ROOTS:
58
+ try:
59
+ resolved.relative_to(pathlib.Path(blocked).resolve())
60
+ return False
61
+ except ValueError:
62
+ pass
63
+ for root in self.allowed_roots():
64
+ try:
65
+ resolved.relative_to(root)
66
+ return True
67
+ except ValueError:
68
+ pass
69
+ return False
70
+
71
+ def require_safe(self, path: str | pathlib.Path) -> pathlib.Path:
72
+ resolved = self.resolve(path)
73
+ if not self.is_safe_path(resolved):
74
+ raise PermissionError(f"Access denied: path '{resolved}' is outside allowed directories")
75
+ return resolved
76
+
77
+
78
+ class WorkspaceFiles:
79
+ """Read, list, and search files under Aria Code's safety policy."""
80
+
81
+ MAX_READ_BYTES = 2_000_000
82
+ LARGE_FILE_BYTES = 500_000
83
+ LARGE_FILE_DEFAULT_LINES = 500
84
+ MAX_SEARCH_BYTES = 5_000_000
85
+
86
+ def __init__(self, security: WorkspaceSecurity | None = None) -> None:
87
+ self.security = security or WorkspaceSecurity()
88
+
89
+ def read_file(self, path: str, offset: int = 0, limit: int = 0) -> ReadResult:
90
+ target = self.security.require_safe(path)
91
+ if not target.exists():
92
+ raise FileNotFoundError(f"File not found: {target}")
93
+ if not target.is_file():
94
+ raise ValueError(f"Not a file: {target}")
95
+ size = target.stat().st_size
96
+ if size > self.MAX_READ_BYTES:
97
+ raise ValueError(
98
+ f"File too large: {size:,} bytes (max 2 MB). "
99
+ "Use offset/limit parameters to read sections."
100
+ )
101
+ text = target.read_text(errors="replace")
102
+ lines = text.splitlines()
103
+ total_lines = len(lines)
104
+ if not offset and not limit and size > self.LARGE_FILE_BYTES:
105
+ limit = self.LARGE_FILE_DEFAULT_LINES
106
+ if offset or limit:
107
+ end = offset + limit if limit else total_lines
108
+ shown = lines[offset:end]
109
+ content = "\n".join(f"{i + offset + 1:4d}│ {line}" for i, line in enumerate(shown))
110
+ if limit and end < total_lines:
111
+ content += f"\n... [{len(shown)} of {total_lines} lines shown — use offset/limit to read more]"
112
+ return ReadResult(str(target), len(shown), content[:30000])
113
+ content = "\n".join(f"{i + 1:4d}│ {line}" for i, line in enumerate(lines))
114
+ return ReadResult(str(target), len(lines), content[:30000])
115
+
116
+ def list_files(self, path: str = ".", pattern: str = "*") -> Dict[str, Any]:
117
+ target = self.security.require_safe(path)
118
+ if not target.exists():
119
+ raise FileNotFoundError(f"Path not found: {target}")
120
+ if target.is_file():
121
+ read = self.read_file(str(target))
122
+ return {
123
+ "path": read.path,
124
+ "pattern": pattern,
125
+ "count": 1,
126
+ "items": [{"name": target.name, "type": "file", "size": target.stat().st_size}],
127
+ "content": read.content,
128
+ }
129
+ matches = sorted(target.glob(pattern))[:100]
130
+ items = []
131
+ for match in matches:
132
+ try:
133
+ self.security.require_safe(match)
134
+ except PermissionError:
135
+ continue
136
+ rel = match.relative_to(target) if match.is_relative_to(target) else match
137
+ kind = "dir" if match.is_dir() else "file"
138
+ size = match.stat().st_size if match.is_file() else 0
139
+ items.append({"name": str(rel), "type": kind, "size": size})
140
+ return {"path": str(target), "pattern": pattern, "count": len(items), "items": items}
141
+
142
+ def search_code(self, pattern: str, path: str = ".", file_glob: str = "**/*.py") -> Dict[str, Any]:
143
+ if not pattern:
144
+ raise ValueError("Missing 'pattern' parameter")
145
+ target = self.security.require_safe(path)
146
+ regex = re.compile(pattern, re.IGNORECASE)
147
+ matches = []
148
+ for file_path in sorted(target.glob(file_glob))[:200]:
149
+ try:
150
+ safe_file = self.security.require_safe(file_path)
151
+ except PermissionError:
152
+ continue
153
+ if not safe_file.is_file() or safe_file.stat().st_size > self.MAX_SEARCH_BYTES:
154
+ continue
155
+ try:
156
+ lines = safe_file.read_text(errors="replace").splitlines()
157
+ except Exception:
158
+ continue
159
+ for line_number, line in enumerate(lines, 1):
160
+ if regex.search(line):
161
+ matches.append({
162
+ "file": str(safe_file.relative_to(target) if safe_file.is_relative_to(target) else safe_file),
163
+ "line": line_number,
164
+ "content": line.strip()[:200],
165
+ })
166
+ if len(matches) >= 50:
167
+ break
168
+ if len(matches) >= 50:
169
+ break
170
+ return {"pattern": pattern, "path": str(target), "count": len(matches), "matches": matches}
workspace/verify.py ADDED
@@ -0,0 +1,113 @@
1
+ """Verification planning for Aria Code workspaces."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import pathlib
7
+ import shlex
8
+ import subprocess
9
+ from dataclasses import dataclass
10
+ from typing import Iterable, List
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class VerificationPlan:
15
+ commands: List[str]
16
+ reason: str
17
+
18
+
19
+ class VerificationPlanner:
20
+ """Infer focused verification commands from changed files and project files."""
21
+
22
+ def __init__(self, root: str | pathlib.Path = ".") -> None:
23
+ self.root = pathlib.Path(root).expanduser().resolve()
24
+
25
+ def infer(self, paths: Iterable[str] | None = None) -> VerificationPlan:
26
+ changed = [p for p in (paths or []) if p]
27
+ if not changed:
28
+ changed = self._git_changed_files()
29
+ commands: List[str] = []
30
+ reasons: List[str] = []
31
+
32
+ py_files = [p for p in changed if p.endswith(".py")]
33
+ if py_files:
34
+ quoted = " ".join(shlex.quote(p) for p in py_files[:20])
35
+ commands.append(f"python3 -m py_compile {quoted}")
36
+ reasons.append("Python files changed")
37
+ if self._has_any("pytest.ini", "pyproject.toml", "setup.cfg", "tox.ini") or (self.root / "tests").exists():
38
+ commands.append("python3 -m pytest -q")
39
+
40
+ frontend_files = [
41
+ p for p in changed
42
+ if p.endswith((".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".css"))
43
+ ]
44
+ if frontend_files and (self.root / "package.json").exists():
45
+ scripts = self._package_scripts()
46
+ reasons.append("Node/TypeScript files changed")
47
+ if "test" in scripts:
48
+ commands.append("npm test")
49
+ if "build" in scripts:
50
+ commands.append("npm run build")
51
+ elif (self.root / "tsconfig.json").exists():
52
+ commands.append("npx tsc --noEmit")
53
+
54
+ if not commands:
55
+ if (self.root / "pyproject.toml").exists() or (self.root / "tests").exists():
56
+ commands.append("python3 -m pytest -q")
57
+ reasons.append("Python project detected")
58
+ elif (self.root / "package.json").exists():
59
+ scripts = self._package_scripts()
60
+ if "test" in scripts:
61
+ commands.append("npm test")
62
+ reasons.append("Node project detected")
63
+ elif "build" in scripts:
64
+ commands.append("npm run build")
65
+ reasons.append("Node project detected")
66
+
67
+ deduped = list(dict.fromkeys(commands))
68
+ reason = "; ".join(dict.fromkeys(reasons)) if reasons else "No focused check inferred"
69
+ return VerificationPlan(deduped, reason)
70
+
71
+ def _has_any(self, *names: str) -> bool:
72
+ return any((self.root / name).exists() for name in names)
73
+
74
+ def _package_scripts(self) -> dict:
75
+ try:
76
+ data = json.loads((self.root / "package.json").read_text(encoding="utf-8"))
77
+ return data.get("scripts", {}) if isinstance(data.get("scripts", {}), dict) else {}
78
+ except Exception:
79
+ return {}
80
+
81
+ def _git_changed_files(self) -> List[str]:
82
+ try:
83
+ proc = subprocess.run(
84
+ ["git", "diff", "--name-only", "HEAD"],
85
+ cwd=self.root,
86
+ capture_output=True,
87
+ text=True,
88
+ timeout=5,
89
+ check=False,
90
+ )
91
+ if proc.returncode == 0 and proc.stdout.strip():
92
+ return [line.strip() for line in proc.stdout.splitlines() if line.strip()]
93
+ except Exception:
94
+ pass
95
+ try:
96
+ proc = subprocess.run(
97
+ ["git", "status", "--short"],
98
+ cwd=self.root,
99
+ capture_output=True,
100
+ text=True,
101
+ timeout=5,
102
+ check=False,
103
+ )
104
+ if proc.returncode != 0:
105
+ return []
106
+ files = []
107
+ for line in proc.stdout.splitlines():
108
+ if not line.strip():
109
+ continue
110
+ files.append(line[3:].strip())
111
+ return files
112
+ except Exception:
113
+ return []