alysis-code 0.13.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 (458) hide show
  1. alysis_code/__init__.py +3 -0
  2. alysis_code/__main__.py +4 -0
  3. alysis_code/_build_info.py +14 -0
  4. alysis_code/account_login.py +468 -0
  5. alysis_code/agent/README.md +35 -0
  6. alysis_code/agent/__init__.py +11 -0
  7. alysis_code/agent/acceptance_contract.py +2217 -0
  8. alysis_code/agent/blast_radius.py +1403 -0
  9. alysis_code/agent/cache_keepalive.py +227 -0
  10. alysis_code/agent/completion_certificate.py +366 -0
  11. alysis_code/agent/completion_gate.py +306 -0
  12. alysis_code/agent/empty_response_stall.py +403 -0
  13. alysis_code/agent/errors.py +28 -0
  14. alysis_code/agent/llm_calls.py +475 -0
  15. alysis_code/agent/mutation_classification.py +227 -0
  16. alysis_code/agent/prompt_context.py +2508 -0
  17. alysis_code/agent/read_ledger.py +253 -0
  18. alysis_code/agent/regression_baseline.py +642 -0
  19. alysis_code/agent/reproduction_first.py +610 -0
  20. alysis_code/agent/sensitive_output.py +629 -0
  21. alysis_code/agent/session.py +3218 -0
  22. alysis_code/agent/steering.py +191 -0
  23. alysis_code/agent/subagent_execution.py +5177 -0
  24. alysis_code/agent/subagent_workspace.py +666 -0
  25. alysis_code/agent/tools_assembly.py +4728 -0
  26. alysis_code/agent/turn/__init__.py +101 -0
  27. alysis_code/agent/turn/core.py +8483 -0
  28. alysis_code/agent/turn/events.py +113 -0
  29. alysis_code/agent/turn/exploration.py +590 -0
  30. alysis_code/agent/turn/interventions.py +65 -0
  31. alysis_code/agent/turn/read_cache.py +420 -0
  32. alysis_code/agent/turn/snapshot.py +179 -0
  33. alysis_code/agent/turn_contract.py +661 -0
  34. alysis_code/agent/turn_path.py +129 -0
  35. alysis_code/agent/verification.py +2885 -0
  36. alysis_code/agent/verification_commands.py +512 -0
  37. alysis_code/agent/verification_evidence.py +738 -0
  38. alysis_code/agent_loop.py +693 -0
  39. alysis_code/agent_runtimes/__init__.py +51 -0
  40. alysis_code/agent_runtimes/base.py +114 -0
  41. alysis_code/agent_runtimes/builtins.py +129 -0
  42. alysis_code/agent_runtimes/codex_cli.py +664 -0
  43. alysis_code/agent_runtimes/host.py +263 -0
  44. alysis_code/agent_runtimes/registry.py +64 -0
  45. alysis_code/agent_runtimes/service.py +150 -0
  46. alysis_code/agentbox_client.py +416 -0
  47. alysis_code/agentbox_integration.py +310 -0
  48. alysis_code/alysis_cloud.py +152 -0
  49. alysis_code/approval_scope.py +276 -0
  50. alysis_code/assets/README.md +33 -0
  51. alysis_code/assets/__init__.py +126 -0
  52. alysis_code/assets/asset_read_core.py +281 -0
  53. alysis_code/assets/budget_allocator.py +456 -0
  54. alysis_code/assets/comprehender.py +759 -0
  55. alysis_code/assets/index.py +654 -0
  56. alysis_code/assets/ingestion.py +275 -0
  57. alysis_code/assets/legacy_migration.py +413 -0
  58. alysis_code/assets/models.py +263 -0
  59. alysis_code/assets/ocr.py +239 -0
  60. alysis_code/assets/owl/ascii/f-000.txt +13 -0
  61. alysis_code/assets/owl/ascii/f-001.txt +13 -0
  62. alysis_code/assets/owl/ascii/f-002.txt +13 -0
  63. alysis_code/assets/owl/ascii/f-003.txt +13 -0
  64. alysis_code/assets/owl/ascii/f-004.txt +13 -0
  65. alysis_code/assets/owl/ascii/f-005.txt +13 -0
  66. alysis_code/assets/owl/ascii/f-006.txt +13 -0
  67. alysis_code/assets/owl/ascii/f-007.txt +13 -0
  68. alysis_code/assets/owl/ascii/f-008.txt +13 -0
  69. alysis_code/assets/owl/ascii/f-009.txt +13 -0
  70. alysis_code/assets/owl/ascii/f-010.txt +13 -0
  71. alysis_code/assets/owl/ascii/f-011.txt +13 -0
  72. alysis_code/assets/owl/ascii/f-012.txt +13 -0
  73. alysis_code/assets/owl/ascii/f-013.txt +13 -0
  74. alysis_code/assets/owl/ascii/f-014.txt +13 -0
  75. alysis_code/assets/owl/ascii/f-015.txt +13 -0
  76. alysis_code/assets/owl/ascii/f-016.txt +13 -0
  77. alysis_code/assets/owl/ascii/f-017.txt +13 -0
  78. alysis_code/assets/owl/ascii/f-018.txt +13 -0
  79. alysis_code/assets/owl/ascii/f-019.txt +13 -0
  80. alysis_code/assets/owl/ascii/f-020.txt +13 -0
  81. alysis_code/assets/owl/index.html +98 -0
  82. alysis_code/assets/owl/show-owl.sh +761 -0
  83. alysis_code/assets/paths.py +49 -0
  84. alysis_code/assets/plan_binding.py +326 -0
  85. alysis_code/assets/planner_context.py +466 -0
  86. alysis_code/assets/planner_tools.py +184 -0
  87. alysis_code/assets/prompts.py +101 -0
  88. alysis_code/assets/replanner_context.py +239 -0
  89. alysis_code/assets/surface.py +521 -0
  90. alysis_code/assets/untrusted_content.py +48 -0
  91. alysis_code/assets/usage_logger.py +94 -0
  92. alysis_code/assets/worker_mirror.py +428 -0
  93. alysis_code/assets/worker_section.py +303 -0
  94. alysis_code/assets/worker_tools.py +468 -0
  95. alysis_code/atomic_io.py +83 -0
  96. alysis_code/auth_diagnostics.py +272 -0
  97. alysis_code/background_runner.py +366 -0
  98. alysis_code/branding.py +270 -0
  99. alysis_code/budget_policy.py +390 -0
  100. alysis_code/build_identity.py +465 -0
  101. alysis_code/builtin_hooks/__init__.py +7 -0
  102. alysis_code/builtin_hooks/notify_done_windows.py +65 -0
  103. alysis_code/bwrap_etc.py +76 -0
  104. alysis_code/cancellation.py +41 -0
  105. alysis_code/capabilities.py +137 -0
  106. alysis_code/chatgpt_codex_static_provider.py +133 -0
  107. alysis_code/cli.py +51 -0
  108. alysis_code/cli_impl/__init__.py +1 -0
  109. alysis_code/cli_impl/assets_cli.py +537 -0
  110. alysis_code/cli_impl/assets_modal.py +412 -0
  111. alysis_code/cli_impl/chat/__init__.py +156 -0
  112. alysis_code/cli_impl/chat/commands.py +2616 -0
  113. alysis_code/cli_impl/chat/loop.py +4508 -0
  114. alysis_code/cli_impl/chat/mid_turn_policy.py +125 -0
  115. alysis_code/cli_impl/chat/rendering.py +444 -0
  116. alysis_code/cli_impl/chat/state.py +124 -0
  117. alysis_code/cli_impl/chat_resume.py +830 -0
  118. alysis_code/cli_impl/chat_slash_completer.py +258 -0
  119. alysis_code/cli_impl/commands/__init__.py +11 -0
  120. alysis_code/cli_impl/commands/_shared.py +89 -0
  121. alysis_code/cli_impl/commands/auth.py +623 -0
  122. alysis_code/cli_impl/commands/chat_resume_helpers.py +1531 -0
  123. alysis_code/cli_impl/commands/chat_state.py +158 -0
  124. alysis_code/cli_impl/commands/chat_status.py +1248 -0
  125. alysis_code/cli_impl/commands/chat_terminal.py +942 -0
  126. alysis_code/cli_impl/commands/chat_tui_panels.py +1018 -0
  127. alysis_code/cli_impl/commands/cli_common.py +1223 -0
  128. alysis_code/cli_impl/commands/cli_surface.py +77 -0
  129. alysis_code/cli_impl/commands/config.py +131 -0
  130. alysis_code/cli_impl/commands/conventions.py +85 -0
  131. alysis_code/cli_impl/commands/execution_helpers.py +350 -0
  132. alysis_code/cli_impl/commands/extensions.py +401 -0
  133. alysis_code/cli_impl/commands/forge.py +1282 -0
  134. alysis_code/cli_impl/commands/forge_asset_view.py +121 -0
  135. alysis_code/cli_impl/commands/forge_helpers.py +1215 -0
  136. alysis_code/cli_impl/commands/hooks.py +737 -0
  137. alysis_code/cli_impl/commands/ide_bridge.py +31 -0
  138. alysis_code/cli_impl/commands/mcp.py +700 -0
  139. alysis_code/cli_impl/commands/profile.py +453 -0
  140. alysis_code/cli_impl/commands/prompt_helpers.py +307 -0
  141. alysis_code/cli_impl/commands/report.py +88 -0
  142. alysis_code/cli_impl/commands/root.py +1118 -0
  143. alysis_code/cli_impl/commands/sandbox.py +184 -0
  144. alysis_code/cli_impl/commands/server.py +54 -0
  145. alysis_code/cli_impl/commands/sessions.py +252 -0
  146. alysis_code/cli_impl/commands/skills.py +404 -0
  147. alysis_code/cli_impl/commands/startup.py +946 -0
  148. alysis_code/cli_impl/commands/tools.py +335 -0
  149. alysis_code/cli_impl/commands/update.py +364 -0
  150. alysis_code/cli_impl/commands/welcome.py +972 -0
  151. alysis_code/cli_impl/config_menu.py +3882 -0
  152. alysis_code/cli_impl/forge.py +4509 -0
  153. alysis_code/cli_impl/forge_recovery.py +485 -0
  154. alysis_code/cli_impl/setup_wizard.py +2409 -0
  155. alysis_code/cli_impl/tui/__init__.py +58 -0
  156. alysis_code/cli_impl/tui/app.py +4551 -0
  157. alysis_code/cli_impl/tui/config.py +32 -0
  158. alysis_code/cli_impl/tui/config_flow.py +2754 -0
  159. alysis_code/cli_impl/tui/config_overlay.py +566 -0
  160. alysis_code/cli_impl/tui/content.py +78 -0
  161. alysis_code/cli_impl/tui/footer.py +218 -0
  162. alysis_code/cli_impl/tui/forge_status.py +136 -0
  163. alysis_code/cli_impl/tui/markdown.py +244 -0
  164. alysis_code/cli_impl/tui/owl.py +109 -0
  165. alysis_code/cli_impl/tui/plan_meta.py +477 -0
  166. alysis_code/cli_impl/tui/setup_app.py +519 -0
  167. alysis_code/cli_impl/tui/setup_flow.py +1622 -0
  168. alysis_code/cli_impl/tui/state.py +101 -0
  169. alysis_code/cli_impl/tui/subagent_identity.py +66 -0
  170. alysis_code/cli_impl/tui/subagent_panel.py +186 -0
  171. alysis_code/cli_impl/tui/surface.py +796 -0
  172. alysis_code/cli_impl/tui/transcript.py +514 -0
  173. alysis_code/cli_impl/tui/update_prompt.py +79 -0
  174. alysis_code/cli_impl/tui/workspace_guard.py +384 -0
  175. alysis_code/clipboard.py +172 -0
  176. alysis_code/code_review.py +1211 -0
  177. alysis_code/compaction/__init__.py +28 -0
  178. alysis_code/compaction/conversation_compactor.py +2932 -0
  179. alysis_code/compaction/importance.py +177 -0
  180. alysis_code/compaction/settings.py +297 -0
  181. alysis_code/compaction/tool_output_offload.py +447 -0
  182. alysis_code/config.py +3509 -0
  183. alysis_code/conflict_auto_resolver.py +895 -0
  184. alysis_code/context/__init__.py +1 -0
  185. alysis_code/context/tool_schema_budgeter.py +220 -0
  186. alysis_code/crash_diagnostics.py +282 -0
  187. alysis_code/custom_tools/README.md +34 -0
  188. alysis_code/custom_tools/__init__.py +43 -0
  189. alysis_code/custom_tools/discovery.py +903 -0
  190. alysis_code/custom_tools/runtime.py +1516 -0
  191. alysis_code/custom_tools/session.py +227 -0
  192. alysis_code/custom_tools/trust.py +232 -0
  193. alysis_code/diff_paths.py +113 -0
  194. alysis_code/direction_change.py +293 -0
  195. alysis_code/dispatch_timing.py +306 -0
  196. alysis_code/durable_service_manager.py +1236 -0
  197. alysis_code/edit_discipline.py +659 -0
  198. alysis_code/error_text.py +73 -0
  199. alysis_code/execution_budget.py +411 -0
  200. alysis_code/execution_context.py +915 -0
  201. alysis_code/execution_deadline.py +1065 -0
  202. alysis_code/execution_shared.py +1904 -0
  203. alysis_code/extensions/README.md +30 -0
  204. alysis_code/extensions/__init__.py +93 -0
  205. alysis_code/extensions/activation.py +138 -0
  206. alysis_code/extensions/install.py +1436 -0
  207. alysis_code/extensions/manifest.py +487 -0
  208. alysis_code/extensions/models.py +74 -0
  209. alysis_code/extensions/paths.py +56 -0
  210. alysis_code/extensions/registry.json +4 -0
  211. alysis_code/extensions/registry.py +52 -0
  212. alysis_code/extensions/state.py +83 -0
  213. alysis_code/extensions/workspace_trust.py +101 -0
  214. alysis_code/failed_task_evidence.py +369 -0
  215. alysis_code/failure_category.py +315 -0
  216. alysis_code/feedback_report.py +1647 -0
  217. alysis_code/file_classification.py +485 -0
  218. alysis_code/forge.py +2064 -0
  219. alysis_code/forge_completion.py +362 -0
  220. alysis_code/forge_events.py +475 -0
  221. alysis_code/frontmatter_utils.py +95 -0
  222. alysis_code/git_evidence.py +1181 -0
  223. alysis_code/git_ops.py +560 -0
  224. alysis_code/git_safe.py +62 -0
  225. alysis_code/git_worktrees.py +190 -0
  226. alysis_code/hooks/README.md +33 -0
  227. alysis_code/hooks/__init__.py +67 -0
  228. alysis_code/hooks/audit.py +171 -0
  229. alysis_code/hooks/config.py +225 -0
  230. alysis_code/hooks/dispatcher.py +1110 -0
  231. alysis_code/hooks/models.py +447 -0
  232. alysis_code/hooks/trust.py +202 -0
  233. alysis_code/host_actions.py +543 -0
  234. alysis_code/host_browser.py +103 -0
  235. alysis_code/ide/__init__.py +5 -0
  236. alysis_code/ide/activity_events.py +399 -0
  237. alysis_code/ide/approvals.py +337 -0
  238. alysis_code/ide/artifacts.py +153 -0
  239. alysis_code/ide/browser_egress_proxy.py +1076 -0
  240. alysis_code/ide/cdp_websocket_transport.py +1192 -0
  241. alysis_code/ide/change_ledger.py +1721 -0
  242. alysis_code/ide/context_blocks.py +979 -0
  243. alysis_code/ide/event_stream.py +531 -0
  244. alysis_code/ide/forge_protocol.py +3112 -0
  245. alysis_code/ide/forge_request_ledger.py +737 -0
  246. alysis_code/ide/health.py +965 -0
  247. alysis_code/ide/managed_browser.py +2251 -0
  248. alysis_code/ide/management_protocol.py +3414 -0
  249. alysis_code/ide/mcp_oauth_coordinator.py +744 -0
  250. alysis_code/ide/mcp_oauth_lifecycle.py +1504 -0
  251. alysis_code/ide/prompt_queue.py +1070 -0
  252. alysis_code/ide/protocol.py +191 -0
  253. alysis_code/ide/resumable_swarm.py +1543 -0
  254. alysis_code/ide/session_search.py +295 -0
  255. alysis_code/ide/stdio_bridge.py +9935 -0
  256. alysis_code/ide/structured_state.py +1579 -0
  257. alysis_code/ide/swarm_protocol.py +816 -0
  258. alysis_code/integration_gate.py +506 -0
  259. alysis_code/interactive_input_guard.py +39 -0
  260. alysis_code/interactive_plan_mode.py +26 -0
  261. alysis_code/internal_artifacts.py +179 -0
  262. alysis_code/knowledge_base.py +1409 -0
  263. alysis_code/knowledge_capture.py +1190 -0
  264. alysis_code/knowledge_librarian.py +605 -0
  265. alysis_code/language_policy.py +34 -0
  266. alysis_code/litellm_static_provider.py +535 -0
  267. alysis_code/llm/__init__.py +1 -0
  268. alysis_code/llm/anthropic_messages.py +2288 -0
  269. alysis_code/llm/base.py +71 -0
  270. alysis_code/llm/cache_capabilities.py +985 -0
  271. alysis_code/llm/cache_control_blocks.py +244 -0
  272. alysis_code/llm/cache_policy.py +388 -0
  273. alysis_code/llm/factory.py +373 -0
  274. alysis_code/llm/gemini_generate_content.py +2652 -0
  275. alysis_code/llm/gemini_interactions.py +739 -0
  276. alysis_code/llm/metadata.py +450 -0
  277. alysis_code/llm/openai_compat.py +2947 -0
  278. alysis_code/llm/openai_responses.py +2604 -0
  279. alysis_code/llm/protocols.py +609 -0
  280. alysis_code/llm/provider_limits.py +525 -0
  281. alysis_code/llm/request_plan.py +389 -0
  282. alysis_code/llm/request_shape.py +238 -0
  283. alysis_code/llm/streaming.py +108 -0
  284. alysis_code/llm/temperature_compat.py +78 -0
  285. alysis_code/llm/types.py +195 -0
  286. alysis_code/llm/usage_normalization.py +222 -0
  287. alysis_code/llm_error_display.py +315 -0
  288. alysis_code/logging_redaction.py +326 -0
  289. alysis_code/managed_host_deadline.py +196 -0
  290. alysis_code/mcp/README.md +33 -0
  291. alysis_code/mcp/__init__.py +24 -0
  292. alysis_code/mcp/client.py +1137 -0
  293. alysis_code/mcp/config.py +597 -0
  294. alysis_code/mcp/errors.py +113 -0
  295. alysis_code/mcp/forge_scope.py +154 -0
  296. alysis_code/mcp/jsonrpc.py +214 -0
  297. alysis_code/mcp/manager.py +2308 -0
  298. alysis_code/mcp/models.py +666 -0
  299. alysis_code/mcp/oauth.py +972 -0
  300. alysis_code/mcp/oauth_runtime.py +310 -0
  301. alysis_code/mcp/oauth_store.py +276 -0
  302. alysis_code/mcp/prompts.py +329 -0
  303. alysis_code/mcp/resources.py +295 -0
  304. alysis_code/mcp/roots.py +106 -0
  305. alysis_code/mcp/server_requests.py +75 -0
  306. alysis_code/mcp/token_store.py +859 -0
  307. alysis_code/mcp/transport_http.py +1338 -0
  308. alysis_code/mcp/transport_stdio.py +1267 -0
  309. alysis_code/mcp/untrusted_content.py +119 -0
  310. alysis_code/merge_conflict_reviewer.py +729 -0
  311. alysis_code/model_catalog/__init__.py +1 -0
  312. alysis_code/model_catalog/chatgpt_codex_subscription_snapshot.json +186 -0
  313. alysis_code/model_catalog/litellm_model_prices_snapshot.json +44715 -0
  314. alysis_code/model_catalog/litellm_model_prices_snapshot.meta.json +17 -0
  315. alysis_code/model_metadata_policy.py +223 -0
  316. alysis_code/model_metadata_utils.py +103 -0
  317. alysis_code/model_registry.py +1420 -0
  318. alysis_code/model_router.py +147 -0
  319. alysis_code/permission_policy.py +1016 -0
  320. alysis_code/personas.py +451 -0
  321. alysis_code/pipeline_facts.py +233 -0
  322. alysis_code/plan_assistant.py +4763 -0
  323. alysis_code/plan_mode.py +393 -0
  324. alysis_code/plan_reconciliation.py +1228 -0
  325. alysis_code/plan_repair.py +652 -0
  326. alysis_code/plan_validation.py +1099 -0
  327. alysis_code/planning_constraints.py +904 -0
  328. alysis_code/policy.py +95 -0
  329. alysis_code/preview_server.py +457 -0
  330. alysis_code/process_reaping.py +566 -0
  331. alysis_code/profile_presets.py +1834 -0
  332. alysis_code/profiles.py +666 -0
  333. alysis_code/provider_auth/__init__.py +29 -0
  334. alysis_code/provider_auth/base.py +99 -0
  335. alysis_code/provider_auth/openai_codex.py +951 -0
  336. alysis_code/provider_auth/registry.py +76 -0
  337. alysis_code/provider_auth/store.py +125 -0
  338. alysis_code/provider_diagnostics.py +1209 -0
  339. alysis_code/provider_model_catalog.py +685 -0
  340. alysis_code/provider_telemetry.py +1699 -0
  341. alysis_code/provider_url.py +75 -0
  342. alysis_code/reasoning_contracts.py +911 -0
  343. alysis_code/remote_sync.py +350 -0
  344. alysis_code/replanning.py +1195 -0
  345. alysis_code/repo_scan.py +1152 -0
  346. alysis_code/request_estimation.py +296 -0
  347. alysis_code/review_gate.py +617 -0
  348. alysis_code/run_lock.py +1141 -0
  349. alysis_code/run_outcome.py +58 -0
  350. alysis_code/run_provenance.py +774 -0
  351. alysis_code/run_state.py +445 -0
  352. alysis_code/runtime_artifacts.py +116 -0
  353. alysis_code/runtime_context_features.py +78 -0
  354. alysis_code/runtime_kind.py +52 -0
  355. alysis_code/safety/__init__.py +11 -0
  356. alysis_code/safety/mcp_sanitize.py +29 -0
  357. alysis_code/safety/safe_http.py +297 -0
  358. alysis_code/safety/subagent_report.py +184 -0
  359. alysis_code/sandbox_doctor.py +682 -0
  360. alysis_code/sandbox_runner.py +1025 -0
  361. alysis_code/sandbox_settings.py +423 -0
  362. alysis_code/serialized_paths.py +355 -0
  363. alysis_code/server/__init__.py +3 -0
  364. alysis_code/server/app.py +367 -0
  365. alysis_code/server/auth.py +34 -0
  366. alysis_code/server/job_config.py +30 -0
  367. alysis_code/server/settings.py +215 -0
  368. alysis_code/server/store.py +193 -0
  369. alysis_code/server/worker_runner.py +657 -0
  370. alysis_code/service_persistence.py +355 -0
  371. alysis_code/session_artifacts.py +108 -0
  372. alysis_code/session_metrics.py +331 -0
  373. alysis_code/session_store.py +624 -0
  374. alysis_code/skills/README.md +34 -0
  375. alysis_code/skills/__init__.py +104 -0
  376. alysis_code/skills/conventions.py +84 -0
  377. alysis_code/skills/discovery.py +176 -0
  378. alysis_code/skills/eval_models.py +232 -0
  379. alysis_code/skills/eval_runner.py +372 -0
  380. alysis_code/skills/evals.py +1344 -0
  381. alysis_code/skills/install.py +293 -0
  382. alysis_code/skills/loader.py +118 -0
  383. alysis_code/skills/matching.py +103 -0
  384. alysis_code/skills/models.py +71 -0
  385. alysis_code/skills/paths.py +56 -0
  386. alysis_code/skills/prompting.py +500 -0
  387. alysis_code/skills/scaffold.py +142 -0
  388. alysis_code/skills/state.py +441 -0
  389. alysis_code/skills/transactions.py +125 -0
  390. alysis_code/skills/validation.py +304 -0
  391. alysis_code/step_budget.py +238 -0
  392. alysis_code/subagent_labels.py +49 -0
  393. alysis_code/subagents.py +1072 -0
  394. alysis_code/surface/__init__.py +80 -0
  395. alysis_code/surface/base.py +305 -0
  396. alysis_code/surface/console.py +387 -0
  397. alysis_code/surface/events.py +372 -0
  398. alysis_code/surface/hidden_surface.py +529 -0
  399. alysis_code/surface/noop_surface.py +219 -0
  400. alysis_code/surface/rich_surface.py +1555 -0
  401. alysis_code/surface/styles.py +67 -0
  402. alysis_code/surface/theme.py +455 -0
  403. alysis_code/surface/types.py +100 -0
  404. alysis_code/swarm_backend.py +926 -0
  405. alysis_code/swarm_orchestrator.py +4020 -0
  406. alysis_code/swarm_scheduler.py +441 -0
  407. alysis_code/swarm_trace.py +429 -0
  408. alysis_code/swarm_worker.py +2119 -0
  409. alysis_code/swarm_write_guard.py +348 -0
  410. alysis_code/task_dependencies.py +170 -0
  411. alysis_code/task_readiness.py +992 -0
  412. alysis_code/task_scope.py +2148 -0
  413. alysis_code/terminal_manager.py +762 -0
  414. alysis_code/terminal_ownership.py +460 -0
  415. alysis_code/text_normalization.py +30 -0
  416. alysis_code/token_budget.py +97 -0
  417. alysis_code/tools/README.md +34 -0
  418. alysis_code/tools/__init__.py +1 -0
  419. alysis_code/tools/artifacts.py +127 -0
  420. alysis_code/tools/availability.py +188 -0
  421. alysis_code/tools/fs.py +1456 -0
  422. alysis_code/tools/git.py +461 -0
  423. alysis_code/tools/history.py +229 -0
  424. alysis_code/tools/http_timeout.py +78 -0
  425. alysis_code/tools/image_generation.py +552 -0
  426. alysis_code/tools/registry.py +2936 -0
  427. alysis_code/tools/repo_map.py +476 -0
  428. alysis_code/tools/search.py +563 -0
  429. alysis_code/tools/shell.py +135 -0
  430. alysis_code/tools/symbols.py +1350 -0
  431. alysis_code/tools/test_discovery.py +643 -0
  432. alysis_code/tools/web.py +482 -0
  433. alysis_code/tools/web_search.py +2012 -0
  434. alysis_code/tools/web_search_dashscope.py +557 -0
  435. alysis_code/tools/web_search_ddgs.py +221 -0
  436. alysis_code/tools/web_search_provider_adapters.py +1429 -0
  437. alysis_code/tools/web_search_tavily.py +194 -0
  438. alysis_code/updates.py +933 -0
  439. alysis_code/usage_tracker.py +1990 -0
  440. alysis_code/verification_command_analysis.py +1004 -0
  441. alysis_code/verification_contract.py +574 -0
  442. alysis_code/verification_failure_summary.py +273 -0
  443. alysis_code/verification_repair.py +385 -0
  444. alysis_code/verify_gate.py +3129 -0
  445. alysis_code/web_research.py +1872 -0
  446. alysis_code/web_search_adapters.py +66 -0
  447. alysis_code/web_search_policy.py +27 -0
  448. alysis_code/workspace_binding.py +389 -0
  449. alysis_code/workspace_binding_ui.py +408 -0
  450. alysis_code/workspace_context.py +273 -0
  451. alysis_code/workspace_isolation.py +138 -0
  452. alysis_code/workspace_provisioning.py +455 -0
  453. alysis_code-0.13.0.dist-info/METADATA +507 -0
  454. alysis_code-0.13.0.dist-info/RECORD +458 -0
  455. alysis_code-0.13.0.dist-info/WHEEL +4 -0
  456. alysis_code-0.13.0.dist-info/entry_points.txt +3 -0
  457. alysis_code-0.13.0.dist-info/licenses/LICENSE +176 -0
  458. alysis_code-0.13.0.dist-info/licenses/NOTICE +4 -0
@@ -0,0 +1,104 @@
1
+ from .conventions import load_repo_conventions, render_repo_conventions_context
2
+ from .discovery import discover_skills, resolve_skill_by_name, resolve_skills_enabled
3
+ from .install import (
4
+ SkillInstallResult,
5
+ SkillRemoveResult,
6
+ install_skill_bundle,
7
+ remove_managed_skill,
8
+ )
9
+ from .loader import SkillReadError, read_skill_bundle_file
10
+ from .matching import match_skills
11
+ from .models import (
12
+ ConventionDocument,
13
+ DiscoveredSkills,
14
+ SkillBundle,
15
+ SkillDiscoveryIssue,
16
+ SkillMatch,
17
+ )
18
+ from .paths import (
19
+ global_managed_skill_root,
20
+ global_skills_state_path,
21
+ project_managed_skill_root,
22
+ project_skill_root_for_family,
23
+ project_skills_state_path,
24
+ skill_bundle_dir_name,
25
+ )
26
+ from .prompting import (
27
+ build_explicit_skill_context_message,
28
+ build_matched_skill_context,
29
+ build_skill_advertise_block,
30
+ render_skill_info_text,
31
+ )
32
+ from .scaffold import SkillScaffoldResult, scaffold_skill_bundle
33
+ from .state import (
34
+ ManagedSkillRecord,
35
+ SkillCatalog,
36
+ SkillCatalogEntry,
37
+ SkillLifecycleError,
38
+ SkillLifecycleIssue,
39
+ SkillLifecycleState,
40
+ load_global_skill_state,
41
+ load_global_skill_state_tolerant,
42
+ load_project_skill_state,
43
+ load_project_skill_state_tolerant,
44
+ resolve_managed_bundle_path,
45
+ resolve_skill_catalog,
46
+ resolve_skill_catalog_entry,
47
+ save_global_skill_state,
48
+ save_project_skill_state,
49
+ set_global_skill_disabled,
50
+ set_project_skill_override,
51
+ )
52
+ from .validation import SkillValidationIssue, SkillValidationResult, validate_skill_bundle
53
+
54
+ __all__ = [
55
+ "ConventionDocument",
56
+ "DiscoveredSkills",
57
+ "ManagedSkillRecord",
58
+ "SkillBundle",
59
+ "SkillCatalog",
60
+ "SkillCatalogEntry",
61
+ "SkillDiscoveryIssue",
62
+ "SkillInstallResult",
63
+ "SkillLifecycleError",
64
+ "SkillLifecycleIssue",
65
+ "SkillLifecycleState",
66
+ "SkillMatch",
67
+ "SkillReadError",
68
+ "SkillRemoveResult",
69
+ "SkillScaffoldResult",
70
+ "SkillValidationIssue",
71
+ "SkillValidationResult",
72
+ "build_explicit_skill_context_message",
73
+ "build_matched_skill_context",
74
+ "build_skill_advertise_block",
75
+ "discover_skills",
76
+ "global_managed_skill_root",
77
+ "global_skills_state_path",
78
+ "install_skill_bundle",
79
+ "load_global_skill_state",
80
+ "load_global_skill_state_tolerant",
81
+ "load_project_skill_state",
82
+ "load_project_skill_state_tolerant",
83
+ "load_repo_conventions",
84
+ "match_skills",
85
+ "project_managed_skill_root",
86
+ "project_skill_root_for_family",
87
+ "project_skills_state_path",
88
+ "read_skill_bundle_file",
89
+ "render_repo_conventions_context",
90
+ "render_skill_info_text",
91
+ "remove_managed_skill",
92
+ "resolve_managed_bundle_path",
93
+ "resolve_skill_by_name",
94
+ "resolve_skill_catalog",
95
+ "resolve_skill_catalog_entry",
96
+ "resolve_skills_enabled",
97
+ "save_global_skill_state",
98
+ "save_project_skill_state",
99
+ "scaffold_skill_bundle",
100
+ "set_global_skill_disabled",
101
+ "set_project_skill_override",
102
+ "skill_bundle_dir_name",
103
+ "validate_skill_bundle",
104
+ ]
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from .models import ConventionDocument
6
+
7
+ _CONVENTION_FILENAMES: tuple[str, ...] = ("AGENTS.md", "CLAUDE.md", "CONVENTIONS.md")
8
+
9
+
10
+ def load_repo_conventions(
11
+ *,
12
+ focus_path: Path,
13
+ workspace_root: Path | None = None,
14
+ ) -> tuple[ConventionDocument, ...]:
15
+ resolved_focus = focus_path.expanduser().resolve()
16
+ if resolved_focus.is_file():
17
+ resolved_focus = resolved_focus.parent
18
+ resolved_workspace_root = (
19
+ workspace_root.expanduser().resolve() if workspace_root is not None else resolved_focus
20
+ )
21
+ if resolved_workspace_root.is_file():
22
+ resolved_workspace_root = resolved_workspace_root.parent
23
+ documents: list[ConventionDocument] = []
24
+ current = resolved_focus
25
+ while True:
26
+ for filename in _CONVENTION_FILENAMES:
27
+ candidate = current / filename
28
+ if not candidate.exists() or not candidate.is_file():
29
+ continue
30
+ try:
31
+ content = candidate.read_text(encoding="utf-8", errors="replace").strip()
32
+ except OSError:
33
+ continue
34
+ if not content:
35
+ continue
36
+ documents.append(
37
+ ConventionDocument(
38
+ name=filename,
39
+ path=candidate,
40
+ content=content,
41
+ )
42
+ )
43
+ if current == resolved_workspace_root:
44
+ break
45
+ if resolved_workspace_root not in current.parents:
46
+ break
47
+ current = current.parent
48
+ return tuple(documents)
49
+
50
+
51
+ def render_repo_conventions_context(
52
+ *,
53
+ documents: tuple[ConventionDocument, ...] | list[ConventionDocument],
54
+ max_chars: int,
55
+ ) -> str | None:
56
+ docs = [doc for doc in documents if isinstance(doc, ConventionDocument)]
57
+ if not docs:
58
+ return None
59
+ lines = [
60
+ "<repo_conventions>",
61
+ "source: repo-authored conventions files",
62
+ "trust: lower_priority_than_system_direct_user_and_explicit_skill_context",
63
+ "Apply only when consistent with higher-priority instructions.",
64
+ "",
65
+ ]
66
+ truncated = False
67
+ for document in docs:
68
+ section = [
69
+ f"[{document.name} @ {document.path.as_posix()}]",
70
+ document.content,
71
+ "",
72
+ ]
73
+ projected = "\n".join([*lines, *section, "</repo_conventions>"])
74
+ if len(projected) > max_chars:
75
+ truncated = True
76
+ break
77
+ lines.extend(section)
78
+ if truncated:
79
+ lines.append("...(truncated)")
80
+ lines.append("</repo_conventions>")
81
+ content = "\n".join(lines).strip() + "\n"
82
+ if len(content) <= max_chars:
83
+ return content
84
+ return content[: max(0, max_chars - 18)].rstrip() + "\n...(truncated)\n"
@@ -0,0 +1,176 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from ..branding import canonical_user_config_dir
6
+ from ..config import AppConfig
7
+ from .loader import load_skill_bundle
8
+ from .models import DiscoveredSkills, SkillBundle, SkillDiscoveryIssue
9
+
10
+ _PROJECT_SKILL_ROOT_SPECS: tuple[tuple[str, tuple[str, ...], str], ...] = (
11
+ ("native", (".alysis_skills",), ".alysis_skills"),
12
+ ("interop", (".agents", "skills"), ".agents/skills"),
13
+ ("interop", (".claude", "skills"), ".claude/skills"),
14
+ ("interop", (".github", "skills"), ".github/skills"),
15
+ )
16
+ _USER_SKILL_ROOT_SPECS: tuple[tuple[str, tuple[str, ...], str], ...] = (
17
+ ("native", ("skills",), "skills"),
18
+ ("interop", (".config", "agents", "skills"), ".config/agents/skills"),
19
+ ("interop", (".claude", "skills"), ".claude/skills"),
20
+ ("interop", (".copilot", "skills"), ".copilot/skills"),
21
+ )
22
+
23
+
24
+ def project_skill_root_relative_paths() -> tuple[tuple[str, ...], ...]:
25
+ return tuple(parts for _, parts, _ in _PROJECT_SKILL_ROOT_SPECS)
26
+
27
+
28
+ def resolve_skills_enabled(cfg: AppConfig | None) -> bool:
29
+ if cfg is None:
30
+ return True
31
+ return bool(getattr(cfg, "skills_enabled", True))
32
+
33
+
34
+ def discover_skills(
35
+ *,
36
+ focus_path: Path,
37
+ workspace_root: Path | None = None,
38
+ user_config_dir: Path | None = None,
39
+ home_dir: Path | None = None,
40
+ ) -> DiscoveredSkills:
41
+ resolved_focus = focus_path.expanduser().resolve()
42
+ if resolved_focus.is_file():
43
+ resolved_focus = resolved_focus.parent
44
+ resolved_workspace_root = (
45
+ workspace_root.expanduser().resolve() if workspace_root is not None else resolved_focus
46
+ )
47
+ if resolved_workspace_root.is_file():
48
+ resolved_workspace_root = resolved_workspace_root.parent
49
+ project_ancestors = _project_search_ancestors(
50
+ focus_path=resolved_focus,
51
+ workspace_root=resolved_workspace_root,
52
+ )
53
+ issues: list[SkillDiscoveryIssue] = []
54
+ resolved: dict[str, SkillBundle] = {}
55
+ ordered: list[SkillBundle] = []
56
+
57
+ def _consider(skill: SkillBundle) -> None:
58
+ primary_key = skill.name.casefold()
59
+ if primary_key in resolved:
60
+ return
61
+ resolved[primary_key] = skill
62
+ ordered.append(skill)
63
+
64
+ for ancestor_distance, ancestor_path in project_ancestors:
65
+ for source_kind, parts, source_family in _PROJECT_SKILL_ROOT_SPECS:
66
+ root = ancestor_path.joinpath(*parts)
67
+ try:
68
+ if not root.exists() or not root.is_dir():
69
+ continue
70
+ bundle_paths = sorted(child for child in root.iterdir() if child.is_dir())
71
+ except OSError as exc:
72
+ issues.append(
73
+ SkillDiscoveryIssue(
74
+ source_path=root,
75
+ message=f"failed to scan skill root: {exc}",
76
+ )
77
+ )
78
+ continue
79
+ for bundle_path in bundle_paths:
80
+ skill, issue = load_skill_bundle(
81
+ bundle_path=bundle_path,
82
+ source_scope="project",
83
+ source_kind="native" if source_kind == "native" else "interop",
84
+ source_family=source_family,
85
+ ancestor_distance=ancestor_distance,
86
+ )
87
+ if issue is not None:
88
+ issues.append(issue)
89
+ continue
90
+ assert skill is not None
91
+ if skill.name.casefold() in resolved:
92
+ continue
93
+ _consider(skill)
94
+
95
+ canonical_user_root = (
96
+ user_config_dir.expanduser().resolve()
97
+ if user_config_dir is not None
98
+ else canonical_user_config_dir().resolve()
99
+ )
100
+ resolved_home_dir = (
101
+ home_dir.expanduser().resolve() if home_dir is not None else Path.home().resolve()
102
+ )
103
+ for source_kind, parts, source_family in _USER_SKILL_ROOT_SPECS:
104
+ base_root = canonical_user_root if source_kind == "native" else resolved_home_dir
105
+ root = base_root.joinpath(*parts)
106
+ try:
107
+ if not root.exists() or not root.is_dir():
108
+ continue
109
+ bundle_paths = sorted(child for child in root.iterdir() if child.is_dir())
110
+ except OSError as exc:
111
+ issues.append(
112
+ SkillDiscoveryIssue(
113
+ source_path=root,
114
+ message=f"failed to scan skill root: {exc}",
115
+ )
116
+ )
117
+ continue
118
+ for bundle_path in bundle_paths:
119
+ skill, issue = load_skill_bundle(
120
+ bundle_path=bundle_path,
121
+ source_scope="user",
122
+ source_kind="native" if source_kind == "native" else "interop",
123
+ source_family=source_family,
124
+ ancestor_distance=None,
125
+ )
126
+ if issue is not None:
127
+ issues.append(issue)
128
+ continue
129
+ assert skill is not None
130
+ if skill.name.casefold() in resolved:
131
+ continue
132
+ _consider(skill)
133
+
134
+ return DiscoveredSkills(
135
+ skills=dict(sorted(resolved.items(), key=lambda item: item[0])),
136
+ ordered=tuple(ordered),
137
+ issues=tuple(issues),
138
+ )
139
+
140
+
141
+ def resolve_skill_by_name(
142
+ skill_registry: dict[str, SkillBundle] | None,
143
+ raw_name: str,
144
+ ) -> SkillBundle | None:
145
+ if not skill_registry:
146
+ return None
147
+ candidate = str(raw_name or "").strip()
148
+ if not candidate:
149
+ return None
150
+ lowered = candidate.casefold()
151
+ direct = skill_registry.get(lowered)
152
+ if direct is not None:
153
+ return direct
154
+ for skill in skill_registry.values():
155
+ if lowered in skill.lookup_keys():
156
+ return skill
157
+ return None
158
+
159
+
160
+ def _project_search_ancestors(
161
+ *,
162
+ focus_path: Path,
163
+ workspace_root: Path,
164
+ ) -> list[tuple[int, Path]]:
165
+ ancestors: list[tuple[int, Path]] = []
166
+ current = focus_path
167
+ distance = 0
168
+ while True:
169
+ ancestors.append((distance, current))
170
+ if current == workspace_root:
171
+ break
172
+ if workspace_root not in current.parents:
173
+ break
174
+ current = current.parent
175
+ distance += 1
176
+ return ancestors
@@ -0,0 +1,232 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ from typing import Literal
6
+
7
+ SkillsEvalInvocationMode = Literal["normal", "explicit_skill"]
8
+ SkillsEvalRunStatus = Literal["passed", "failed", "skipped"]
9
+ SkillsEvalPreflightClassification = Literal[
10
+ "ok",
11
+ "auth",
12
+ "rate_limit",
13
+ "provider",
14
+ "runtime",
15
+ ]
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class SkillsEvalCase:
20
+ id: str
21
+ workspace: Path
22
+ task: str
23
+ invocation_mode: SkillsEvalInvocationMode = "normal"
24
+ explicit_skill_name: str | None = None
25
+ expected_skills: tuple[str, ...] = ()
26
+ verification_command: str | None = None
27
+ tags: tuple[str, ...] = ()
28
+ notes: str = ""
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class SkillsEvalMode:
33
+ name: str
34
+ conventions_enabled: bool
35
+ skills_enabled: bool
36
+ skills_auto_invoke: bool
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class SkillsEvalExecutionRequest:
41
+ case: SkillsEvalCase
42
+ mode: SkillsEvalMode
43
+ workspace: Path
44
+ output_dir: Path
45
+ sessions_dir: Path
46
+ session_id: str
47
+ max_steps: int
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class SkillsEvalExecutionResult:
52
+ agent_exit_code: int | None
53
+ skills_advertised_present: bool = False
54
+ repo_conventions_present: bool = False
55
+ matched_skill_context_attached: bool = False
56
+ matched_skill_names: tuple[str, ...] = ()
57
+ explicit_skill_context_used: bool = False
58
+ skill_read_called: bool = False
59
+ skill_read_names: tuple[str, ...] = ()
60
+ skill_read_call_count: int = 0
61
+ skill_lifecycle_cli_used: bool = False
62
+ skill_lifecycle_cli_commands: tuple[str, ...] = ()
63
+ skill_lifecycle_cli_call_count: int = 0
64
+ manual_skill_bundle_accessed: bool = False
65
+ manual_skill_bundle_names: tuple[str, ...] = ()
66
+ manual_skill_bundle_access_count: int = 0
67
+ tool_call_count: int = 0
68
+ completion_gate_failure_count: int = 0
69
+ completion_gate_incomplete_after_retries_count: int = 0
70
+ forced_final_summary_count: int = 0
71
+ verification_credit_miss_count: int = 0
72
+ session_log_path: Path | None = None
73
+ session_artifact_root: Path | None = None
74
+ error: str | None = None
75
+
76
+
77
+ @dataclass(frozen=True)
78
+ class SkillsEvalAuthPreflightResult:
79
+ ok: bool
80
+ classification: SkillsEvalPreflightClassification
81
+ message: str
82
+ agent_exit_code: int | None
83
+ session_log_path: Path | None = None
84
+ session_artifact_root: Path | None = None
85
+
86
+
87
+ @dataclass(frozen=True)
88
+ class SkillsEvalVerificationResult:
89
+ exit_code: int
90
+ output_preview: str = ""
91
+
92
+
93
+ @dataclass(frozen=True)
94
+ class SkillsEvalRecord:
95
+ case_id: str
96
+ mode: str
97
+ workspace: Path
98
+ task: str
99
+ invocation_mode: SkillsEvalInvocationMode
100
+ explicit_skill_name: str | None
101
+ expected_skills: tuple[str, ...]
102
+ tags: tuple[str, ...]
103
+ notes: str
104
+ status: SkillsEvalRunStatus
105
+ passed: bool | None
106
+ skip_reason: str | None
107
+ agent_exit_code: int | None
108
+ verification_command: str | None
109
+ verification_exit_code: int | None
110
+ verification_output_preview: str | None
111
+ skills_advertised_present: bool
112
+ repo_conventions_present: bool
113
+ matched_skill_context_attached: bool
114
+ matched_skill_names: tuple[str, ...]
115
+ explicit_skill_context_used: bool
116
+ skill_read_called: bool
117
+ skill_read_names: tuple[str, ...]
118
+ skill_read_call_count: int
119
+ manual_skill_bundle_accessed: bool
120
+ manual_skill_bundle_names: tuple[str, ...]
121
+ manual_skill_bundle_access_count: int
122
+ tool_call_count: int
123
+ session_log_path: Path | None
124
+ session_artifact_root: Path | None
125
+ skill_lifecycle_cli_used: bool = False
126
+ skill_lifecycle_cli_commands: tuple[str, ...] = ()
127
+ skill_lifecycle_cli_call_count: int = 0
128
+ completion_gate_failure_count: int = 0
129
+ completion_gate_incomplete_after_retries_count: int = 0
130
+ forced_final_summary_count: int = 0
131
+ verification_credit_miss_count: int = 0
132
+ error: str | None = None
133
+
134
+ def observed_skill_names(self) -> tuple[str, ...]:
135
+ names: list[str] = []
136
+ seen: set[str] = set()
137
+ for raw in (
138
+ *self.matched_skill_names,
139
+ *self.skill_read_names,
140
+ *self.manual_skill_bundle_names,
141
+ *(
142
+ (self.explicit_skill_name,)
143
+ if self.explicit_skill_context_used and self.explicit_skill_name
144
+ else ()
145
+ ),
146
+ ):
147
+ value = str(raw or "").strip()
148
+ if not value:
149
+ continue
150
+ lowered = value.casefold()
151
+ if lowered in seen:
152
+ continue
153
+ seen.add(lowered)
154
+ names.append(value)
155
+ return tuple(names)
156
+
157
+ def relevant_skill_used(self) -> bool:
158
+ expected = {item.casefold() for item in self.expected_skills}
159
+ if not expected:
160
+ return False
161
+ return any(name.casefold() in expected for name in self.observed_skill_names())
162
+
163
+ def any_skill_activity(self) -> bool:
164
+ return bool(
165
+ self.explicit_skill_context_used
166
+ or self.matched_skill_context_attached
167
+ or self.skill_read_called
168
+ or self.manual_skill_bundle_accessed
169
+ )
170
+
171
+ def to_payload(self, *, output_dir: Path) -> dict[str, object]:
172
+ return {
173
+ "case_id": self.case_id,
174
+ "mode": self.mode,
175
+ "workspace": self.workspace.as_posix(),
176
+ "task": self.task,
177
+ "invocation_mode": self.invocation_mode,
178
+ "explicit_skill_name": self.explicit_skill_name,
179
+ "expected_skills": list(self.expected_skills),
180
+ "tags": list(self.tags),
181
+ "notes": self.notes,
182
+ "status": self.status,
183
+ "passed": self.passed,
184
+ "skip_reason": self.skip_reason,
185
+ "agent_exit_code": self.agent_exit_code,
186
+ "verification_command": self.verification_command,
187
+ "verification_exit_code": self.verification_exit_code,
188
+ "verification_output_preview": self.verification_output_preview,
189
+ "skills_advertised_present": self.skills_advertised_present,
190
+ "repo_conventions_present": self.repo_conventions_present,
191
+ "matched_skill_context_attached": self.matched_skill_context_attached,
192
+ "matched_skill_names": list(self.matched_skill_names),
193
+ "explicit_skill_context_used": self.explicit_skill_context_used,
194
+ "skill_read_called": self.skill_read_called,
195
+ "skill_read_names": list(self.skill_read_names),
196
+ "skill_read_call_count": self.skill_read_call_count,
197
+ "skill_lifecycle_cli_used": self.skill_lifecycle_cli_used,
198
+ "skill_lifecycle_cli_commands": list(self.skill_lifecycle_cli_commands),
199
+ "skill_lifecycle_cli_call_count": self.skill_lifecycle_cli_call_count,
200
+ "manual_skill_bundle_accessed": self.manual_skill_bundle_accessed,
201
+ "manual_skill_bundle_names": list(self.manual_skill_bundle_names),
202
+ "manual_skill_bundle_access_count": self.manual_skill_bundle_access_count,
203
+ "tool_call_count": self.tool_call_count,
204
+ "completion_gate_failure_count": self.completion_gate_failure_count,
205
+ "completion_gate_incomplete_after_retries_count": self.completion_gate_incomplete_after_retries_count,
206
+ "forced_final_summary_count": self.forced_final_summary_count,
207
+ "verification_credit_miss_count": self.verification_credit_miss_count,
208
+ "session_log_path": _relativize_path(self.session_log_path, output_dir),
209
+ "session_artifact_root": _relativize_path(self.session_artifact_root, output_dir),
210
+ "observed_skill_names": list(self.observed_skill_names()),
211
+ "relevant_skill_used": self.relevant_skill_used(),
212
+ "error": self.error,
213
+ }
214
+
215
+
216
+ @dataclass(frozen=True)
217
+ class SkillsEvalArtifacts:
218
+ output_dir: Path
219
+ results_path: Path
220
+ summary_json_path: Path
221
+ summary_md_path: Path
222
+ records: tuple[SkillsEvalRecord, ...] = field(default_factory=tuple)
223
+ summary: dict[str, object] = field(default_factory=dict)
224
+
225
+
226
+ def _relativize_path(path: Path | None, output_dir: Path) -> str | None:
227
+ if path is None:
228
+ return None
229
+ try:
230
+ return path.resolve().relative_to(output_dir.resolve()).as_posix()
231
+ except ValueError:
232
+ return path.resolve().as_posix()