code-context-control 2.79.0__tar.gz → 2.81.0__tar.gz

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 (429) hide show
  1. {code_context_control-2.79.0/code_context_control.egg-info → code_context_control-2.81.0}/PKG-INFO +2 -2
  2. {code_context_control-2.79.0 → code_context_control-2.81.0}/README.md +1 -1
  3. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/c3.py +12 -4
  4. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/commands/parser.py +15 -0
  5. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_server.py +11 -4
  6. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/hub_ci.js +23 -7
  7. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/mcp_server.py +10 -4
  8. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/ci.py +42 -5
  9. {code_context_control-2.79.0 → code_context_control-2.81.0/code_context_control.egg-info}/PKG-INFO +2 -2
  10. {code_context_control-2.79.0 → code_context_control-2.81.0}/code_context_control.egg-info/SOURCES.txt +4 -0
  11. {code_context_control-2.79.0 → code_context_control-2.81.0}/pyproject.toml +1 -1
  12. code_context_control-2.81.0/services/ci_act.py +274 -0
  13. code_context_control-2.81.0/services/ci_expr.py +435 -0
  14. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/ci_runner.py +295 -19
  15. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/ci_workflow.py +129 -33
  16. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/claude_md.py +1 -1
  17. code_context_control-2.81.0/tests/test_ci_act.py +418 -0
  18. code_context_control-2.81.0/tests/test_ci_expr.py +340 -0
  19. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_ci_runner.py +10 -3
  20. {code_context_control-2.79.0 → code_context_control-2.81.0}/LICENSE +0 -0
  21. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/__init__.py +0 -0
  22. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/_hook_utils.py +0 -0
  23. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/commands/__init__.py +0 -0
  24. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/commands/common.py +0 -0
  25. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/docs.html +0 -0
  26. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/edits.html +0 -0
  27. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/access.html +0 -0
  28. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/bitbucket.html +0 -0
  29. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/credentials.html +0 -0
  30. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/getting-started.html +0 -0
  31. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/index.html +0 -0
  32. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/masking.html +0 -0
  33. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/oracle.html +0 -0
  34. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/shared.css +0 -0
  35. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/tools.html +0 -0
  36. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/guide/workflow.html +0 -0
  37. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_access_guard.py +0 -0
  38. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_artifact.py +0 -0
  39. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_auto_snapshot.py +0 -0
  40. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_c3_signal.py +0 -0
  41. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_c3read.py +0 -0
  42. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_dispatch.py +0 -0
  43. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_edit_ledger.py +0 -0
  44. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_edit_unlock.py +0 -0
  45. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_filter.py +0 -0
  46. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_ghost_files.py +0 -0
  47. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_pretool_enforce.py +0 -0
  48. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_prompt_recall.py +0 -0
  49. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_read.py +0 -0
  50. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_session_stats.py +0 -0
  51. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hook_terse_advisor.py +0 -0
  52. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/app.js +0 -0
  53. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/add_project.js +0 -0
  54. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/config_editor.js +0 -0
  55. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/drill_artifacts.js +0 -0
  56. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/drill_health.js +0 -0
  57. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/drill_panel.js +0 -0
  58. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/drill_subprojects.js +0 -0
  59. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/drill_tasks.js +0 -0
  60. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/drill_views.js +0 -0
  61. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/global_search.js +0 -0
  62. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/hub_credentials.js +0 -0
  63. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/hub_enforcement.js +0 -0
  64. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/hub_locks.js +0 -0
  65. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/mcp_manager.js +0 -0
  66. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/modals.js +0 -0
  67. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/project_card.js +0 -0
  68. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/project_tree.js +0 -0
  69. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/session_drawer.js +0 -0
  70. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/settings_modal.js +0 -0
  71. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/sidebar.js +0 -0
  72. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/summary_bar.js +0 -0
  73. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/task_board.js +0 -0
  74. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/toasts.js +0 -0
  75. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/components/topbar.js +0 -0
  76. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui/state.js +0 -0
  77. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/hub_ui.html +0 -0
  78. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/mcp_proxy.py +0 -0
  79. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/progress.py +0 -0
  80. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/server.py +0 -0
  81. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/__init__.py +0 -0
  82. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/_grants.py +0 -0
  83. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/_helpers.py +0 -0
  84. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/agent.py +0 -0
  85. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/artifacts.py +0 -0
  86. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/bitbucket.py +0 -0
  87. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/compress.py +0 -0
  88. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/credentials.py +0 -0
  89. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/delegate.py +0 -0
  90. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/edit.py +0 -0
  91. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/edit_verify.py +0 -0
  92. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/edits.py +0 -0
  93. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/federate.py +0 -0
  94. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/filter.py +0 -0
  95. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/impact.py +0 -0
  96. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/jira.py +0 -0
  97. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/locks.py +0 -0
  98. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/memory.py +0 -0
  99. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/override.py +0 -0
  100. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/project.py +0 -0
  101. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/read.py +0 -0
  102. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/search.py +0 -0
  103. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/session.py +0 -0
  104. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/shell.py +0 -0
  105. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/status.py +0 -0
  106. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/tasks.py +0 -0
  107. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/tools/validate.py +0 -0
  108. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/api.js +0 -0
  109. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/app.js +0 -0
  110. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/access.js +0 -0
  111. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/bitbucket.js +0 -0
  112. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/chat.js +0 -0
  113. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/credentials.js +0 -0
  114. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/dashboard.js +0 -0
  115. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/edits.js +0 -0
  116. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/enforcement.js +0 -0
  117. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/instructions.js +0 -0
  118. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/jira.js +0 -0
  119. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/memory.js +0 -0
  120. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/sessions.js +0 -0
  121. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/settings.js +0 -0
  122. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/sidebar.js +0 -0
  123. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/components/tasks.js +0 -0
  124. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/icons.js +0 -0
  125. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/pm_shared.js +0 -0
  126. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/shared.js +0 -0
  127. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui/theme.js +0 -0
  128. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui.html +0 -0
  129. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui_legacy.html +0 -0
  130. {code_context_control-2.79.0 → code_context_control-2.81.0}/cli/ui_nano.html +0 -0
  131. {code_context_control-2.79.0 → code_context_control-2.81.0}/code_context_control.egg-info/dependency_links.txt +0 -0
  132. {code_context_control-2.79.0 → code_context_control-2.81.0}/code_context_control.egg-info/entry_points.txt +0 -0
  133. {code_context_control-2.79.0 → code_context_control-2.81.0}/code_context_control.egg-info/requires.txt +0 -0
  134. {code_context_control-2.79.0 → code_context_control-2.81.0}/code_context_control.egg-info/top_level.txt +0 -0
  135. {code_context_control-2.79.0 → code_context_control-2.81.0}/core/__init__.py +0 -0
  136. {code_context_control-2.79.0 → code_context_control-2.81.0}/core/config.py +0 -0
  137. {code_context_control-2.79.0 → code_context_control-2.81.0}/core/ide.py +0 -0
  138. {code_context_control-2.79.0 → code_context_control-2.81.0}/core/mcp_toml.py +0 -0
  139. {code_context_control-2.79.0 → code_context_control-2.81.0}/core/web_security.py +0 -0
  140. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/__init__.py +0 -0
  141. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/config.py +0 -0
  142. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/mcp_oracle.py +0 -0
  143. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/oracle_server.py +0 -0
  144. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/oracle_ui.html +0 -0
  145. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/__init__.py +0 -0
  146. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/activity_reporter.py +0 -0
  147. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/api_auth.py +0 -0
  148. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/c3_bridge.py +0 -0
  149. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/chat_engine.py +0 -0
  150. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/chat_poll.py +0 -0
  151. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/chat_store.py +0 -0
  152. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/cross_memory.py +0 -0
  153. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/discovery_audit.py +0 -0
  154. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/federated_graph.py +0 -0
  155. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/health_checker.py +0 -0
  156. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/insight_engine.py +0 -0
  157. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/local_session.py +0 -0
  158. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/memory_reader.py +0 -0
  159. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/memory_writer.py +0 -0
  160. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/mobile_api.py +0 -0
  161. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/project_scanner.py +0 -0
  162. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/review_agent.py +0 -0
  163. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/tool_executor.py +0 -0
  164. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/services/tool_registry.py +0 -0
  165. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/activity.js +0 -0
  166. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/agents.js +0 -0
  167. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/app.js +0 -0
  168. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/busy.js +0 -0
  169. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/chat/conversations.js +0 -0
  170. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/chat/input.js +0 -0
  171. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/chat/markdown.js +0 -0
  172. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/chat/send.js +0 -0
  173. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/chat/stream_renderer.js +0 -0
  174. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/chat/toolbar.js +0 -0
  175. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/core.js +0 -0
  176. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/crossgraph.js +0 -0
  177. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/header.js +0 -0
  178. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/insights.js +0 -0
  179. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/projects.js +0 -0
  180. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/qrcode.js +0 -0
  181. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/settings.js +0 -0
  182. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/suggestions.js +0 -0
  183. {code_context_control-2.79.0 → code_context_control-2.81.0}/oracle/ui/theme_tabs.js +0 -0
  184. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/__init__.py +0 -0
  185. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/access_guard.py +0 -0
  186. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/access_telemetry.py +0 -0
  187. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/activity_log.py +0 -0
  188. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/agent_base.py +0 -0
  189. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/agent_locks.py +0 -0
  190. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/agents.py +0 -0
  191. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/artifact_defs.py +0 -0
  192. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/artifact_store.py +0 -0
  193. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/auto_memory.py +0 -0
  194. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/bench/__init__.py +0 -0
  195. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/bench/external/__init__.py +0 -0
  196. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/bench/external/aider_polyglot.py +0 -0
  197. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/bench/external/swe_bench.py +0 -0
  198. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/benchmark_dashboard.py +0 -0
  199. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/bitbucket_client.py +0 -0
  200. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/bitbucket_credentials.py +0 -0
  201. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/ci_failures.py +0 -0
  202. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/circuit_breaker.py +0 -0
  203. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/compressor.py +0 -0
  204. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/context_snapshot.py +0 -0
  205. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/conversation_store.py +0 -0
  206. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/credential_store.py +0 -0
  207. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/doc_index.py +0 -0
  208. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/e2e_benchmark.py +0 -0
  209. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/e2e_evaluator.py +0 -0
  210. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/e2e_tasks.py +0 -0
  211. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/edit_ledger.py +0 -0
  212. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/embedding_index.py +0 -0
  213. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/enforcement_policy.py +0 -0
  214. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/error_reporting.py +0 -0
  215. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/file_memory.py +0 -0
  216. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/git_context.py +0 -0
  217. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/hub_service.py +0 -0
  218. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/indexer.py +0 -0
  219. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/jira_client.py +0 -0
  220. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/jira_cloud.py +0 -0
  221. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/jira_credentials.py +0 -0
  222. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/jira_data_center.py +0 -0
  223. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/jira_links.py +0 -0
  224. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/mask_activation.py +0 -0
  225. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/mask_mirror.py +0 -0
  226. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/mask_presets.py +0 -0
  227. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/memory.py +0 -0
  228. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/memory_consolidator.py +0 -0
  229. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/memory_distiller.py +0 -0
  230. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/memory_graph.py +0 -0
  231. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/memory_grounder.py +0 -0
  232. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/memory_queue.py +0 -0
  233. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/memory_scorer.py +0 -0
  234. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/metrics.py +0 -0
  235. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/notifications.py +0 -0
  236. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/ollama_bridge.py +0 -0
  237. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/ollama_client.py +0 -0
  238. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/ollama_credentials.py +0 -0
  239. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/output_filter.py +0 -0
  240. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/override_grants.py +0 -0
  241. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/override_policy.py +0 -0
  242. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/override_requests.py +0 -0
  243. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/override_wake.py +0 -0
  244. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/parser.py +0 -0
  245. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/project_manager.py +0 -0
  246. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/project_runtime.py +0 -0
  247. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/protocol.py +0 -0
  248. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/proxy_state.py +0 -0
  249. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/repo_map.py +0 -0
  250. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/retention.py +0 -0
  251. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/retrieval_broker.py +0 -0
  252. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/router.py +0 -0
  253. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/runtime.py +0 -0
  254. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/scanner.py +0 -0
  255. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/session_benchmark.py +0 -0
  256. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/session_manager.py +0 -0
  257. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/session_preloader.py +0 -0
  258. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/subprojects.py +0 -0
  259. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/task_store.py +0 -0
  260. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/telemetry.py +0 -0
  261. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/text_index.py +0 -0
  262. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/time_tracker.py +0 -0
  263. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/tool_classifier.py +0 -0
  264. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/transcript_index.py +0 -0
  265. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/validation_cache.py +0 -0
  266. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/vector_store.py +0 -0
  267. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/watcher.py +0 -0
  268. {code_context_control-2.79.0 → code_context_control-2.81.0}/services/win_subprocess.py +0 -0
  269. {code_context_control-2.79.0 → code_context_control-2.81.0}/setup.cfg +0 -0
  270. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_access_builtin_optout.py +0 -0
  271. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_access_guard.py +0 -0
  272. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_access_guard_meta.py +0 -0
  273. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_access_guard_shell_project.py +0 -0
  274. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_access_guard_surfaces.py +0 -0
  275. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_access_guard_wiring.py +0 -0
  276. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_activity_reporter.py +0 -0
  277. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_agent_locks.py +0 -0
  278. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_aider_polyglot.py +0 -0
  279. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_antigravity_transition.py +0 -0
  280. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_artifact_store.py +0 -0
  281. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_artifact_tool.py +0 -0
  282. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_bitbucket_cli_smoke.py +0 -0
  283. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_bitbucket_client.py +0 -0
  284. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_bitbucket_config_fallback.py +0 -0
  285. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_bitbucket_credentials.py +0 -0
  286. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_bitbucket_tool.py +0 -0
  287. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_c3_bridge_project_artifacts.py +0 -0
  288. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_c3_shell.py +0 -0
  289. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_chat_poll.py +0 -0
  290. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_ci_workflow.py +0 -0
  291. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_circuit_breaker.py +0 -0
  292. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_claude_md_merge.py +0 -0
  293. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_cli_smoke.py +0 -0
  294. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_compressor_large_file.py +0 -0
  295. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_credential_store.py +0 -0
  296. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_credentials_routes.py +0 -0
  297. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_credentials_tool.py +0 -0
  298. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_creds_cli_smoke.py +0 -0
  299. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_delegate_cascade.py +0 -0
  300. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_delegate_version_probe.py +0 -0
  301. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_e2e_benchmark.py +0 -0
  302. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_edit_ledger_deadline.py +0 -0
  303. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_edit_ledger_hook.py +0 -0
  304. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_edit_locking.py +0 -0
  305. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_edit_normalization.py +0 -0
  306. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_edit_verify.py +0 -0
  307. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_embedding_index_deadlock.py +0 -0
  308. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_enforcement_flip.py +0 -0
  309. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_enforcement_policy.py +0 -0
  310. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_enforcement_routes.py +0 -0
  311. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_federated_graph.py +0 -0
  312. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_filter_backoff.py +0 -0
  313. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_ghost_files.py +0 -0
  314. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_ghost_generation.py +0 -0
  315. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_git_branch_awareness.py +0 -0
  316. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_access_guard.py +0 -0
  317. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_codex_compat.py +0 -0
  318. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_dispatch.py +0 -0
  319. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_pretool_enforce.py +0 -0
  320. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_prompt_recall.py +0 -0
  321. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_smoke.py +0 -0
  322. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_state.py +0 -0
  323. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hook_utf8_stdio.py +0 -0
  324. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hub_credentials_route.py +0 -0
  325. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hub_credentials_routes_write.py +0 -0
  326. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hub_enforcement_routes.py +0 -0
  327. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hub_inspect_api.py +0 -0
  328. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hub_locks_routes.py +0 -0
  329. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hub_server_smoke.py +0 -0
  330. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_hub_subproject_links.py +0 -0
  331. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_ide_workflow_adaptation.py +0 -0
  332. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_init_scan.py +0 -0
  333. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_install_mcp_entrypoint.py +0 -0
  334. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_jira_cli_smoke.py +0 -0
  335. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_jira_client.py +0 -0
  336. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_jira_config_fallback.py +0 -0
  337. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_jira_credentials.py +0 -0
  338. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_jira_links.py +0 -0
  339. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_jira_routes.py +0 -0
  340. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_jira_tool.py +0 -0
  341. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_lazy_store_init.py +0 -0
  342. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mask_guard.py +0 -0
  343. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mask_routes.py +0 -0
  344. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mask_surfaces.py +0 -0
  345. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mcp_host_guard.py +0 -0
  346. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mcp_memory_timeout.py +0 -0
  347. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mcp_server_smoke.py +0 -0
  348. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mcp_toml.py +0 -0
  349. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_memory_distiller.py +0 -0
  350. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_memory_graph_api.py +0 -0
  351. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_memory_queue.py +0 -0
  352. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_memory_system.py +0 -0
  353. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mobile_api.py +0 -0
  354. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mobile_extras.py +0 -0
  355. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mobile_override_routes.py +0 -0
  356. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_mobile_security_api.py +0 -0
  357. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_no_stale_model_pins.py +0 -0
  358. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_notification_dedup.py +0 -0
  359. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_notification_discipline.py +0 -0
  360. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_ollama_credentials.py +0 -0
  361. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_api_auth.py +0 -0
  362. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_apikey_api.py +0 -0
  363. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_c3_bridge.py +0 -0
  364. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_chat_engine.py +0 -0
  365. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_chat_store.py +0 -0
  366. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_discovery_api.py +0 -0
  367. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_discovery_audit.py +0 -0
  368. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_local_auth.py +0 -0
  369. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_ollama_bridge.py +0 -0
  370. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_scanner_cache.py +0 -0
  371. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_security_fixes.py +0 -0
  372. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_session_bootstrap.py +0 -0
  373. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_subproject_awareness.py +0 -0
  374. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_oracle_ui_bundle.py +0 -0
  375. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_output_filter.py +0 -0
  376. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_override_grants.py +0 -0
  377. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_override_grants_mcp.py +0 -0
  378. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_override_requests.py +0 -0
  379. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_override_wake.py +0 -0
  380. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_permissions.py +0 -0
  381. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_pm_api.py +0 -0
  382. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_project_manager.py +0 -0
  383. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_project_manager_merge.py +0 -0
  384. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_project_tool.py +0 -0
  385. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_read_coercion.py +0 -0
  386. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_read_edit_parity.py +0 -0
  387. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_related_facts.py +0 -0
  388. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_repo_map.py +0 -0
  389. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_response_boilerplate.py +0 -0
  390. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_retention.py +0 -0
  391. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_review_digest.py +0 -0
  392. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_service_durability.py +0 -0
  393. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_session_benchmark.py +0 -0
  394. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_session_budget.py +0 -0
  395. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_shell_creds.py +0 -0
  396. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_shell_robustness.py +0 -0
  397. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_subproject_exclusion.py +0 -0
  398. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_subproject_federation.py +0 -0
  399. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_subprojects.py +0 -0
  400. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_swe_bench.py +0 -0
  401. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_task_store.py +0 -0
  402. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_task_tool.py +0 -0
  403. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_time_tracker.py +0 -0
  404. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_token_telemetry.py +0 -0
  405. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_tool_registry.py +0 -0
  406. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_upgrade_and_version.py +0 -0
  407. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_validate.py +0 -0
  408. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_vault_write_guard.py +0 -0
  409. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_version_sync.py +0 -0
  410. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_web_security.py +0 -0
  411. {code_context_control-2.79.0 → code_context_control-2.81.0}/tests/test_windows_reliability.py +0 -0
  412. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/__init__.py +0 -0
  413. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/backend.py +0 -0
  414. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/main.py +0 -0
  415. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/__init__.py +0 -0
  416. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/benchmark_view.py +0 -0
  417. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/claudemd_view.py +0 -0
  418. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/compress_view.py +0 -0
  419. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/index_view.py +0 -0
  420. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/init_view.py +0 -0
  421. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/mcp_view.py +0 -0
  422. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/optimize_view.py +0 -0
  423. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/pipe_view.py +0 -0
  424. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/projects_view.py +0 -0
  425. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/search_view.py +0 -0
  426. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/session_view.py +0 -0
  427. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/stats.py +0 -0
  428. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/screens/ui_view.py +0 -0
  429. {code_context_control-2.79.0 → code_context_control-2.81.0}/tui/theme.tcss +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.79.0
3
+ Version: 2.81.0
4
4
  Summary: Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub.
5
5
  Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -202,7 +202,7 @@ C3 exposes **21 tools** as a native MCP server. Your IDE calls them directly:
202
202
  | `c3_session` | Snapshot, restore, log decisions, compact history |
203
203
  | `c3_impact` | Blast-radius analysis before editing shared symbols |
204
204
  | `c3_locks` | Agent leases — who is working on which file, so two agents don't collide (v2.65.0) |
205
- | `c3_ci` | Run this repo's real `.github/workflows` locally, with structured failures, before pushing (v2.79.0) |
205
+ | `c3_ci` | Run this repo's real `.github/workflows` locally — in containers via `act` — with structured failures, before pushing (v2.79.0) |
206
206
  | `c3_delegate` | Offload heavy work to local Ollama / Codex / Gemini |
207
207
  | `c3_agent` | Workflows: `review_changes`, `investigate`, `preflight`, `prepare_context`, `validate_compress` |
208
208
  | `c3_edits` | Edit-ledger queries, version diffs, restore points, per-branch filter, `verify` (did a failed `c3_edit` still land?) |
@@ -139,7 +139,7 @@ C3 exposes **21 tools** as a native MCP server. Your IDE calls them directly:
139
139
  | `c3_session` | Snapshot, restore, log decisions, compact history |
140
140
  | `c3_impact` | Blast-radius analysis before editing shared symbols |
141
141
  | `c3_locks` | Agent leases — who is working on which file, so two agents don't collide (v2.65.0) |
142
- | `c3_ci` | Run this repo's real `.github/workflows` locally, with structured failures, before pushing (v2.79.0) |
142
+ | `c3_ci` | Run this repo's real `.github/workflows` locally — in containers via `act` — with structured failures, before pushing (v2.79.0) |
143
143
  | `c3_delegate` | Offload heavy work to local Ollama / Codex / Gemini |
144
144
  | `c3_agent` | Workflows: `review_changes`, `investigate`, `preflight`, `prepare_context`, `validate_compress` |
145
145
  | `c3_edits` | Edit-ledger queries, version diffs, restore points, per-branch filter, `verify` (did a failed `c3_edit` still land?) |
@@ -92,7 +92,7 @@ console = Console() if HAS_RICH else None
92
92
  # Config
93
93
  CONFIG_DIR = ".c3"
94
94
  CONFIG_FILE = ".c3/config.json"
95
- __version__ = "2.79.0"
95
+ __version__ = "2.81.0"
96
96
 
97
97
 
98
98
  def _compress_file_cli(compressor, path, mode="smart", **kw):
@@ -6065,7 +6065,7 @@ def _ci_main(args) -> int:
6065
6065
  from services import ci_runner as cr
6066
6066
  from services.ci_workflow import inspect_project
6067
6067
  if sub == "inspect":
6068
- payload = inspect_project(project_path)
6068
+ payload = inspect_project(project_path, event=getattr(args, "event", "") or "")
6069
6069
  elif sub in ("run", "rerun"):
6070
6070
  only = None
6071
6071
  if sub == "rerun":
@@ -6078,7 +6078,11 @@ def _ci_main(args) -> int:
6078
6078
  project_path, selector=getattr(args, "job", ""),
6079
6079
  allow_foreign=bool(getattr(args, "allow_foreign", False)),
6080
6080
  only=only, timeout=getattr(args, "timeout", 0) or cr.DEFAULT_STEP_TIMEOUT,
6081
- workflow=getattr(args, "workflow", "")).to_dict()
6081
+ workflow=getattr(args, "workflow", ""),
6082
+ event=getattr(args, "event", "") or "",
6083
+ engine=getattr(args, "engine", "auto") or "auto",
6084
+ allow_side_effects=bool(getattr(args, "allow_side_effects", False)),
6085
+ network=getattr(args, "network", "") or "").to_dict()
6082
6086
  elif sub == "runs":
6083
6087
  payload = {"runs": cr.list_runs(project_path)}
6084
6088
  else:
@@ -6096,7 +6100,11 @@ def _ci_main(args) -> int:
6096
6100
  sub, getattr(args, "job", "") or "", getattr(args, "run_id", "") or "",
6097
6101
  bool(getattr(args, "allow_foreign", False)),
6098
6102
  getattr(args, "workflow", "") or "", getattr(args, "tail", 200) or 200,
6099
- getattr(args, "timeout", 0) or 0, svc, finalize)
6103
+ getattr(args, "timeout", 0) or 0, svc, finalize,
6104
+ getattr(args, "event", "") or "",
6105
+ getattr(args, "engine", "auto") or "auto",
6106
+ bool(getattr(args, "allow_side_effects", False)),
6107
+ getattr(args, "network", "") or "")
6100
6108
  print(out)
6101
6109
  # Exit code is the finding: a non-full verdict must not read as success to
6102
6110
  # a shell script or a pre-push hook.
@@ -483,6 +483,9 @@ def build_parser(version: str, parse_cli_ide_arg):
483
483
  sub.add_argument("--path", dest="project_path", default=".",
484
484
  help="Project directory (default: current)")
485
485
  sub.add_argument("--json", action="store_true", help="Machine-readable output")
486
+ sub.add_argument("--event", default="",
487
+ help="GitHub event to simulate (push, pull_request) — "
488
+ "only needed when an `if:` reads github.event_name")
486
489
  return sub
487
490
 
488
491
  _ci_common(ci_subs.add_parser(
@@ -497,6 +500,16 @@ def build_parser(version: str, parse_cli_ide_arg):
497
500
  "can never yield FULL_CI_PASS)")
498
501
  ci_run.add_argument("--timeout", type=int, default=0,
499
502
  help="Per-step timeout in seconds (default 900)")
503
+ ci_run.add_argument("--engine", choices=["auto", "native", "act"],
504
+ default="auto",
505
+ help="auto (default) uses act for Linux jobs when act + "
506
+ "Docker are present, native otherwise")
507
+ ci_run.add_argument("--allow-side-effects", action="store_true",
508
+ help="Let the act engine run jobs that look like they "
509
+ "publish or deploy. Refused by default.")
510
+ ci_run.add_argument("--network", default="",
511
+ help="Container network for the act engine (e.g. `none` "
512
+ "to cut egress). Default: act's own.")
500
513
 
501
514
  ci_rerun = _ci_common(ci_subs.add_parser(
502
515
  "rerun", help="Re-run only the jobs that failed in the last run"))
@@ -517,6 +530,8 @@ def build_parser(version: str, parse_cli_ide_arg):
517
530
  ci_logs.add_argument("--tail", type=int, default=200)
518
531
 
519
532
  _ci_common(ci_subs.add_parser("runs", help="Recent local CI runs"))
533
+ _ci_common(ci_subs.add_parser(
534
+ "doctor", help="Which execution engines are available on this machine"))
520
535
 
521
536
  # ── Override Requests (v2.69.0, docs/override-requests.md) ──────────
522
537
  # Human-only approval surface. A grant is single-use, session-bound,
@@ -1293,11 +1293,12 @@ _ci_runs: dict = {} # project path -> in-flight state
1293
1293
  _ci_lock = threading.Lock()
1294
1294
 
1295
1295
 
1296
- def _ci_worker(project: str, job: str, allow_foreign: bool, workflow: str) -> None:
1296
+ def _ci_worker(project: str, job: str, allow_foreign: bool, workflow: str,
1297
+ engine: str = "auto") -> None:
1297
1298
  from services import ci_runner as cr
1298
1299
  try:
1299
1300
  result = cr.run_ci(project, selector=job, allow_foreign=allow_foreign,
1300
- workflow=workflow)
1301
+ workflow=workflow, engine=engine)
1301
1302
  payload = {"running": False, "done": True, "error": None,
1302
1303
  "run_id": result.run_id, "verdict": result.verdict,
1303
1304
  "note": result.note}
@@ -1320,7 +1321,12 @@ def api_ci_inspect():
1320
1321
  except ValueError as e:
1321
1322
  return jsonify({"error": str(e)}), 404
1322
1323
  try:
1323
- return jsonify(inspect_project(resolved))
1324
+ from services import ci_act
1325
+ data = inspect_project(resolved)
1326
+ # The UI has to be able to say WHY a job cannot run here, and
1327
+ # "install act" is a different answer from "macOS is impossible".
1328
+ data["engines"] = ci_act.availability()
1329
+ return jsonify(data)
1324
1330
  except Exception as e:
1325
1331
  return jsonify({"error": f"{type(e).__name__}: {e}"}), 500
1326
1332
 
@@ -1391,7 +1397,8 @@ def api_ci_run_start():
1391
1397
  threading.Thread(
1392
1398
  target=_ci_worker,
1393
1399
  args=(resolved, str(data.get("job") or ""),
1394
- bool(data.get("allow_foreign")), str(data.get("workflow") or "")),
1400
+ bool(data.get("allow_foreign")), str(data.get("workflow") or ""),
1401
+ str(data.get("engine") or "auto")),
1395
1402
  daemon=True,
1396
1403
  ).start()
1397
1404
  return jsonify({"started": True})
@@ -142,6 +142,7 @@ function HubCI({ projects, onOpenDrill }) {
142
142
  };
143
143
 
144
144
  const jobs = (inspect && inspect.jobs) || [];
145
+ const engines = inspect && inspect.engines;
145
146
  const runJobs = ((run && run.jobs) || []).filter(j => j.status !== 'deselected');
146
147
 
147
148
  return (
@@ -172,8 +173,16 @@ function HubCI({ projects, onOpenDrill }) {
172
173
  border: `1px solid ${T.border}`, borderRadius: 6, padding: '8px 10px', lineHeight: 1.5,
173
174
  }}>
174
175
  Runs the repository's real <span className="mono">.github/workflows</span> here —
175
- C3 does not define a second CI config. Jobs targeting another OS are refused
176
- unless you opt in, and a cross-OS result is indicative, never equivalent.
176
+ C3 does not define a second CI config.{' '}
177
+ {engines && engines.ok ? (
178
+ <span>Linux jobs run in a container via <span className="mono">act</span>{' '}
179
+ ({engines.act_version}, docker {engines.docker_version}), real actions included.</span>
180
+ ) : (
181
+ <span style={{ color: T.warn }}>
182
+ Container engine unavailable — {(engines && engines.reason) || 'checking…'}
183
+ </span>
184
+ )}{' '}
185
+ macOS jobs can never run locally: there are no macOS containers.
177
186
  </div>
178
187
 
179
188
  {err && (
@@ -236,8 +245,12 @@ function HubCI({ projects, onOpenDrill }) {
236
245
  <span className="mono" style={{
237
246
  color: T.text, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
238
247
  }}>{j.key}</span>
239
- {j.cross_os && j.status === 'passed' && (
240
- <span className="mono" title={`Ran on this host, but targets ${j.runs_on}.`}
248
+ {j.status === 'passed' && j.fidelity === 'container' && (
249
+ <span className="mono" title={`Ran in a container of ${j.runs_on} via act — real actions included.`}
250
+ style={{ fontSize: 10, color: T.accent }}>container</span>
251
+ )}
252
+ {j.status === 'passed' && j.fidelity === 'cross-os' && (
253
+ <span className="mono" title={`Ran on this host, but targets ${j.runs_on}. Indicative, not equivalent.`}
241
254
  style={{ fontSize: 10, color: T.blue }}>cross-OS</span>
242
255
  )}
243
256
  <div style={{ flex: 1 }} />
@@ -277,9 +290,12 @@ function HubCI({ projects, onOpenDrill }) {
277
290
  borderTop: `1px solid ${T.border}`, fontSize: 11,
278
291
  }}>
279
292
  <CiStatusPill
280
- status={!j.supported ? 'unsupported' : (j.foreign_runner ? 'foreign' : 'passed')}
281
- title={!j.supported ? (j.blockers || []).join('; ')
282
- : (j.foreign_runner ? `targets ${j.runs_on}` : 'runnable on this host')} />
293
+ status={(!j.supported && !j.act_could_run) ? 'unsupported'
294
+ : ((j.foreign_runner && !(j.act_could_run && engines && engines.ok))
295
+ ? 'foreign' : 'passed')}
296
+ title={(!j.supported && !j.act_could_run) ? (j.blockers || []).join('; ')
297
+ : (j.act_could_run && !j.supported ? 'the act engine can run this'
298
+ : (j.foreign_runner ? `targets ${j.runs_on}` : 'runnable on this host'))} />
283
299
  <span className="mono" style={{ color: T.textMuted }}>{j.key}</span>
284
300
  {!!(j.needs || []).length && (
285
301
  <span className="mono" style={{ fontSize: 10, color: T.textDim }}>
@@ -769,10 +769,12 @@ async def c3_locks(action: str = "list", paths: str = "", intent: str = "",
769
769
  @mcp.tool()
770
770
  async def c3_ci(action: str = "inspect", job: str = "", run_id: str = "",
771
771
  allow_foreign: bool = False, workflow: str = "",
772
- tail: int = 200, timeout: int = 0, ctx: Context = None) -> str:
772
+ tail: int = 200, timeout: int = 0, event: str = "",
773
+ engine: str = "auto", allow_side_effects: bool = False,
774
+ network: str = "", ctx: Context = None) -> str:
773
775
  """RUN THIS REPO'S REAL CI LOCALLY — before pushing, not after.
774
776
  Reads .github/workflows/*.yml as the source of truth (no second CI config).
775
- actions: inspect (default) | run | rerun | status | failures | logs | runs.
777
+ actions: inspect | run | rerun | status | failures | logs | runs | doctor.
776
778
  inspect: workflows, the job DAG, and which jobs are runnable on THIS host.
777
779
  run: execute in dependency order; job='lint' or 'test (ubuntu-latest, 3.12)'
778
780
  selects one (bare job name = all its matrix cells). A job whose dependency
@@ -783,7 +785,10 @@ async def c3_ci(action: str = "inspect", job: str = "", run_id: str = "",
783
785
  that means "safe to push". PARTIAL_PASS means something did not run
784
786
  (different OS, unsupported action, or you selected a subset); it is NOT a
785
787
  green light. Jobs targeting another OS are refused unless allow_foreign=true,
786
- which runs them and labels the result cross-OS."""
788
+ which runs them and labels the result cross-OS.
789
+ event: declare which GitHub event you are simulating ('push',
790
+ 'pull_request'). Needed only when an `if:` reads github.event_name —
791
+ there is no event locally, so C3 refuses to guess one."""
787
792
  svc = _svc(ctx)
788
793
 
789
794
  def finalize(name, args, resp, summ, **kw):
@@ -792,7 +797,8 @@ async def c3_ci(action: str = "inspect", job: str = "", run_id: str = "",
792
797
  from cli.tools.ci import handle_ci
793
798
  return await asyncio.to_thread(handle_ci, action, job, run_id,
794
799
  allow_foreign, workflow, tail, timeout,
795
- svc, finalize)
800
+ svc, finalize, event, engine,
801
+ allow_side_effects, network)
796
802
 
797
803
 
798
804
  @mcp.tool()
@@ -39,7 +39,12 @@ def _job_rows(jobs: list, include_deselected: bool = False) -> list:
39
39
  if status == cr.DESELECTED and not include_deselected:
40
40
  continue
41
41
  mark = _MARK.get(status, status.upper())
42
- cross = " [cross-OS]" if job.get("cross_os") and status == cr.PASSED else ""
42
+ fid = job.get("fidelity", "")
43
+ cross = ""
44
+ if status == cr.PASSED and fid == cr.FIDELITY_CONTAINER:
45
+ cross = " [container]"
46
+ elif status == cr.PASSED and fid == cr.FIDELITY_CROSS_OS:
47
+ cross = " [cross-OS]"
43
48
  ms = job.get("duration_ms") or 0
44
49
  timing = f" {ms / 1000:.1f}s" if ms else ""
45
50
  line = f" {mark:<9} {job.get('key', '?')}{cross}{timing}"
@@ -74,7 +79,9 @@ def _failure_rows(jobs: list, limit: int = 25) -> list:
74
79
 
75
80
 
76
81
  def handle_ci(action: str, job: str, run_id: str, allow_foreign: bool,
77
- workflow: str, tail: int, timeout: int, svc, finalize) -> str:
82
+ workflow: str, tail: int, timeout: int, svc, finalize,
83
+ event: str = "", engine: str = "auto",
84
+ allow_side_effects: bool = False, network: str = "") -> str:
78
85
  """Route c3_ci actions."""
79
86
  project = str(svc.project_path)
80
87
  action = (action or "inspect").strip().lower()
@@ -84,7 +91,7 @@ def handle_ci(action: str, job: str, run_id: str, allow_foreign: bool,
84
91
 
85
92
  # ── inspect ──────────────────────────────────────────────────────────
86
93
  if action in ("", "inspect", "jobs"):
87
- data = inspect_project(project)
94
+ data = inspect_project(project, event=event)
88
95
  if not data["workflows"]:
89
96
  return finalize(
90
97
  "c3_ci", args,
@@ -150,7 +157,9 @@ def handle_ci(action: str, job: str, run_id: str, allow_foreign: bool,
150
157
 
151
158
  result = cr.run_ci(project, selector=job, allow_foreign=allow_foreign,
152
159
  only=only, timeout=timeout or cr.DEFAULT_STEP_TIMEOUT,
153
- workflow=workflow)
160
+ workflow=workflow, event=event, engine=engine,
161
+ allow_side_effects=allow_side_effects,
162
+ network=network)
154
163
  run = result.to_dict()
155
164
  lines = [_verdict_line(run), f"run {run['run_id']} host={run['host_os']}"]
156
165
  fp = run.get("fingerprint") or {}
@@ -223,6 +232,34 @@ def handle_ci(action: str, job: str, run_id: str, allow_foreign: bool,
223
232
  f"{row.get('started_at', '')[:19]} {tally}")
224
233
  return finalize("c3_ci", args, "\n".join(lines), f"{len(rows)} runs")
225
234
 
235
+ # ── doctor ───────────────────────────────────────────────────────────
236
+ if action == "doctor":
237
+ from services import ci_act
238
+ state = ci_act.availability()
239
+ lines = [
240
+ "Local CI execution engines:",
241
+ "",
242
+ f" native available — runs jobs whose runs-on matches "
243
+ f"{cr.host_os()}",
244
+ ]
245
+ if state["ok"]:
246
+ lines += [
247
+ f" act available — {state['act_version']}, "
248
+ f"docker {state['docker_version']}",
249
+ f" runs Linux jobs in a container "
250
+ f"({ci_act.DEFAULT_IMAGE}), real `uses:` actions included",
251
+ ]
252
+ else:
253
+ lines += [" act UNAVAILABLE", f" {state['reason']}"]
254
+ lines += [
255
+ "",
256
+ "macOS jobs cannot run locally on any engine — there are no macOS "
257
+ "containers, so a matrix containing them can never reach "
258
+ "FULL_CI_PASS here.",
259
+ ]
260
+ return finalize("c3_ci", args, "\n".join(lines),
261
+ "act ready" if state["ok"] else "act unavailable")
262
+
226
263
  return finalize("c3_ci", args,
227
264
  f"Unknown action '{action}'. Use: inspect, run, rerun, "
228
- "status, failures, logs, runs.", "unknown action")
265
+ "status, failures, logs, runs, doctor.", "unknown action")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.79.0
3
+ Version: 2.81.0
4
4
  Summary: Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub.
5
5
  Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -202,7 +202,7 @@ C3 exposes **21 tools** as a native MCP server. Your IDE calls them directly:
202
202
  | `c3_session` | Snapshot, restore, log decisions, compact history |
203
203
  | `c3_impact` | Blast-radius analysis before editing shared symbols |
204
204
  | `c3_locks` | Agent leases — who is working on which file, so two agents don't collide (v2.65.0) |
205
- | `c3_ci` | Run this repo's real `.github/workflows` locally, with structured failures, before pushing (v2.79.0) |
205
+ | `c3_ci` | Run this repo's real `.github/workflows` locally — in containers via `act` — with structured failures, before pushing (v2.79.0) |
206
206
  | `c3_delegate` | Offload heavy work to local Ollama / Codex / Gemini |
207
207
  | `c3_agent` | Workflows: `review_changes`, `investigate`, `preflight`, `prepare_context`, `validate_compress` |
208
208
  | `c3_edits` | Edit-ledger queries, version diffs, restore points, per-branch filter, `verify` (did a failed `c3_edit` still land?) |
@@ -185,6 +185,8 @@ services/auto_memory.py
185
185
  services/benchmark_dashboard.py
186
186
  services/bitbucket_client.py
187
187
  services/bitbucket_credentials.py
188
+ services/ci_act.py
189
+ services/ci_expr.py
188
190
  services/ci_failures.py
189
191
  services/ci_runner.py
190
192
  services/ci_workflow.py
@@ -280,6 +282,8 @@ tests/test_bitbucket_tool.py
280
282
  tests/test_c3_bridge_project_artifacts.py
281
283
  tests/test_c3_shell.py
282
284
  tests/test_chat_poll.py
285
+ tests/test_ci_act.py
286
+ tests/test_ci_expr.py
283
287
  tests/test_ci_runner.py
284
288
  tests/test_ci_workflow.py
285
289
  tests/test_circuit_breaker.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code-context-control"
7
- version = "2.79.0"
7
+ version = "2.81.0"
8
8
  description = "Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,274 @@
1
+ """AgentCI — the `act` execution engine (Linux jobs in real containers).
2
+
3
+ The native engine runs a job's shell steps on this machine. That is faithful
4
+ when `runs-on` matches the host and an approximation otherwise, and it cannot
5
+ run `uses:` actions at all. This engine hands a Linux job to `nektos/act`,
6
+ which runs it in a container using GitHub's own workflow semantics — real
7
+ actions included.
8
+
9
+ What that buys, measured on this repository from Windows: 3 of 15 jobs
10
+ runnable natively, 11 with containers. The remaining four are macOS cells, and
11
+ no amount of Docker fixes those — **there are no macOS containers**, so a full
12
+ local pass on a matrix containing them stays out of reach. Saying so is part of
13
+ the job.
14
+
15
+ Three things learned by probing rather than by reading, all load-bearing:
16
+
17
+ 1. **`--bind` is required on Windows.** act's default copies the workspace into
18
+ the container; against a Windows host path that silently produced an empty
19
+ directory and every step failed on missing files. `--bind` mounts the real
20
+ working tree and works even on a mapped drive whose path contains spaces and
21
+ parentheses. The trade-off is that the container writes into the real tree —
22
+ the same thing the native engine already does, so behaviour is consistent.
23
+
24
+ 2. **`-P <label>=<image>` must always be passed.** Without it act prompts
25
+ interactively on first use to choose an image size, which would hang any
26
+ automated run.
27
+
28
+ 3. **`-W <file>` is mandatory when two workflows share a job name.** act says so
29
+ itself on this repo, where `CI` and `Release` both define `build` — the same
30
+ collision the DAG scopes around.
31
+
32
+ Safety: this engine never passes real secrets or tokens. act reads `.secrets`
33
+ and `.env` from the repository by default, so both are explicitly pointed at
34
+ empty files. A publish step therefore executes and fails at authentication
35
+ instead of publishing, which is the intended outcome for a local run.
36
+ """
37
+ from __future__ import annotations
38
+
39
+ import os
40
+ import re
41
+ import shutil
42
+ import subprocess
43
+ import tempfile
44
+ import time
45
+ from pathlib import Path
46
+
47
+ # act's community runner images. These mirror a good deal of the GitHub
48
+ # runner's preinstalled toolchain; a bare `ubuntu:24.04` does not, and a job
49
+ # that assumes a preinstalled tool then fails locally while passing in CI. A
50
+ # false red is the safe direction, but it is still noise, so default to the
51
+ # image built for the purpose.
52
+ DEFAULT_IMAGE = "catthehacker/ubuntu:act-latest"
53
+ RUNNER_IMAGES = {
54
+ "ubuntu-latest": DEFAULT_IMAGE,
55
+ "ubuntu-24.04": "catthehacker/ubuntu:act-24.04",
56
+ "ubuntu-22.04": "catthehacker/ubuntu:act-22.04",
57
+ "ubuntu-20.04": "catthehacker/ubuntu:act-20.04",
58
+ }
59
+
60
+ # Pulling a runner image is ~1 GB on first use, so the act engine gets its own,
61
+ # larger default ceiling than a native shell step.
62
+ DEFAULT_ACT_TIMEOUT = 3600
63
+
64
+
65
+ def image_for(runs_on: str) -> str:
66
+ label = str(runs_on or "").strip().lower()
67
+ return RUNNER_IMAGES.get(label, DEFAULT_IMAGE)
68
+
69
+
70
+ def find_act() -> str:
71
+ """Path to the act binary. C3_ACT_PATH wins, then PATH."""
72
+ override = (os.environ.get("C3_ACT_PATH") or "").strip()
73
+ if override and Path(override).is_file():
74
+ return override
75
+ return shutil.which("act") or ""
76
+
77
+
78
+ def _probe(cmd: list, timeout: int = 30) -> tuple:
79
+ try:
80
+ out = subprocess.run(cmd, capture_output=True, text=True,
81
+ timeout=timeout, stdin=subprocess.DEVNULL,
82
+ **({"creationflags": subprocess.CREATE_NO_WINDOW}
83
+ if os.name == "nt" else {}))
84
+ return out.returncode, (out.stdout or "") + (out.stderr or "")
85
+ except Exception as exc:
86
+ return 127, f"{type(exc).__name__}: {exc}"
87
+
88
+
89
+ def availability() -> dict:
90
+ """Can this engine run at all? Reports WHY not, never just False.
91
+
92
+ Both halves are checked because they fail independently and a user with
93
+ act installed but Docker stopped deserves to be told which one to fix.
94
+ """
95
+ act = find_act()
96
+ info: dict = {"ok": False, "act": act, "act_version": "",
97
+ "docker": False, "docker_version": "", "reason": ""}
98
+ if not act:
99
+ info["reason"] = ("`act` is not installed. Install it with "
100
+ "`winget install nektos.act` (Windows), "
101
+ "`brew install act` (macOS), or see "
102
+ "https://nektosact.com — then re-run.")
103
+ return info
104
+ rc, out = _probe([act, "--version"])
105
+ if rc != 0:
106
+ info["reason"] = f"`act --version` failed: {out.strip()[:200]}"
107
+ return info
108
+ info["act_version"] = out.strip().splitlines()[0] if out.strip() else ""
109
+
110
+ rc, out = _probe(["docker", "version", "--format", "{{.Server.Version}}"])
111
+ if rc != 0:
112
+ info["reason"] = ("Docker is not reachable — the act engine needs a "
113
+ "running daemon. Start Docker Desktop (or dockerd) "
114
+ f"and retry. Probe said: {out.strip()[:160]}")
115
+ return info
116
+ info["docker"] = True
117
+ info["docker_version"] = out.strip().splitlines()[-1] if out.strip() else ""
118
+ info["ok"] = True
119
+ return info
120
+
121
+
122
+ # ── Side-effect gate ────────────────────────────────────────────────────────
123
+ # With a real action runner, jobs that were previously unrunnable become
124
+ # runnable — including ones whose entire purpose is to publish. Capability no
125
+ # longer protects anybody, so policy has to.
126
+ PUBLISHING_ACTIONS = (
127
+ "pypa/gh-action-pypi-publish",
128
+ "softprops/action-gh-release",
129
+ "ncipollo/release-action",
130
+ "actions/create-release",
131
+ "docker/build-push-action",
132
+ "peaceiris/actions-gh-pages",
133
+ "actions/deploy-pages",
134
+ "js-devtools/npm-publish",
135
+ "gradle/gradle-publish",
136
+ "cycjimmy/semantic-release-action",
137
+ )
138
+ _PUBLISH_CMD = re.compile(
139
+ r"\b(twine\s+upload|npm\s+publish|cargo\s+publish|gh\s+release\s+create"
140
+ r"|docker\s+push|poetry\s+publish|aws\s+s3\s+(cp|sync)|kubectl\s+apply"
141
+ r"|terraform\s+apply|gcloud\s+.*deploy)\b", re.IGNORECASE)
142
+
143
+
144
+ def side_effects(inst) -> list:
145
+ """Reasons this job looks like it publishes or deploys something."""
146
+ found: list = []
147
+ for step in getattr(inst, "steps", []):
148
+ base = str(getattr(step, "uses", "") or "").split("@", 1)[0].lower()
149
+ if base in PUBLISHING_ACTIONS:
150
+ found.append(f"step {step.index} uses `{step.uses}`")
151
+ match = _PUBLISH_CMD.search(str(getattr(step, "run", "") or ""))
152
+ if match:
153
+ found.append(f"step {step.index} runs `{match.group(0)}`")
154
+ return found
155
+
156
+
157
+ # ── Command construction ────────────────────────────────────────────────────
158
+
159
+ def build_command(inst, project, act_path: str, event: str = "",
160
+ image: str = "", network: str = "",
161
+ secret_file: str = "", env_file: str = "",
162
+ artifact_dir: str = "", pull: bool = True) -> list:
163
+ """The exact argv handed to act. Pure, so it is unit-testable."""
164
+ cmd: list = [act_path]
165
+ if event:
166
+ cmd.append(event)
167
+
168
+ workflow_path = getattr(inst, "workflow_path", "") or ""
169
+ if workflow_path:
170
+ try:
171
+ rel = Path(workflow_path).resolve().relative_to(Path(project).resolve())
172
+ cmd += ["-W", str(rel).replace("\\", "/")]
173
+ except ValueError:
174
+ cmd += ["-W", str(workflow_path)]
175
+
176
+ cmd += ["-j", inst.job_id]
177
+
178
+ for key, value in sorted((inst.matrix or {}).items()):
179
+ cmd += ["--matrix", f"{key}:{value}"]
180
+
181
+ cmd += ["-P", f"{inst.runs_on}={image or image_for(inst.runs_on)}"]
182
+
183
+ # See the module docstring: without --bind the workspace arrives empty on
184
+ # Windows and every step fails on missing files.
185
+ cmd.append("--bind")
186
+ cmd.append(f"--pull={'true' if pull else 'false'}")
187
+
188
+ if network:
189
+ cmd += ["--network", network]
190
+ # Never let act source the repository's real .secrets / .env.
191
+ cmd += ["--secret-file", secret_file or os.devnull]
192
+ cmd += ["--env-file", env_file or os.devnull]
193
+ if artifact_dir:
194
+ cmd += ["--artifact-server-path", artifact_dir]
195
+ return cmd
196
+
197
+
198
+ # act prefixes every line it emits with `[Workflow/job]`, and marks actual
199
+ # program output with a `|` gutter:
200
+ #
201
+ # [CI/lint] ⭐ Run Main ruff check . <- act's own narration
202
+ # [CI/lint] | app/x.py:3:1: F401 ... <- what the command printed
203
+ #
204
+ # Handing the raw log to the failure parsers captures the prefix as part of the
205
+ # filename — `file=[CI/lint] ⭐ Run Main echo "app/x.py` — which makes the
206
+ # structured failure useless. Parse the gutter lines only.
207
+ _ACT_OUTPUT_LINE = re.compile(r"^\[[^\]]*\]\s{0,3}\|\s?(.*)$")
208
+
209
+
210
+ def program_output(text: str) -> str:
211
+ """Just what the commands printed, with act's prefixes removed.
212
+
213
+ Falls back to the raw text when nothing matches, so an act failure that
214
+ never reached a command (a bad image, a daemon error) still reaches the
215
+ parsers and the reader rather than becoming an empty log.
216
+ """
217
+ lines = [m.group(1) for m in
218
+ (_ACT_OUTPUT_LINE.match(ln) for ln in (text or "").splitlines())
219
+ if m]
220
+ return "\n".join(lines) if lines else (text or "")
221
+
222
+
223
+ def run_job(inst, project, timeout: int = DEFAULT_ACT_TIMEOUT,
224
+ event: str = "", network: str = "", artifact_dir: str = "",
225
+ act_path: str = "") -> dict:
226
+ """Run one job through act. Returns {exit_code, output, timed_out, command}."""
227
+ act_path = act_path or find_act()
228
+ if not act_path:
229
+ return {"exit_code": 127, "output": "act is not installed",
230
+ "timed_out": False, "command": ""}
231
+
232
+ tmpdir = tempfile.mkdtemp(prefix="c3act-")
233
+ empty = Path(tmpdir) / "empty"
234
+ empty.write_text("", encoding="utf-8")
235
+
236
+ cmd = build_command(inst, project, act_path, event=event, network=network,
237
+ secret_file=str(empty), env_file=str(empty),
238
+ artifact_dir=artifact_dir)
239
+
240
+ from cli.tools.shell import _kill_tree, _popen_kwargs
241
+
242
+ start = time.time()
243
+ timed_out = False
244
+ try:
245
+ proc = subprocess.Popen(
246
+ cmd, cwd=str(project), stdout=subprocess.PIPE,
247
+ stderr=subprocess.STDOUT, text=True, encoding="utf-8",
248
+ errors="replace", **_popen_kwargs(),
249
+ )
250
+ except (OSError, ValueError) as exc:
251
+ shutil.rmtree(tmpdir, ignore_errors=True)
252
+ return {"exit_code": 126, "output": f"{type(exc).__name__}: {exc}",
253
+ "timed_out": False, "command": " ".join(cmd)}
254
+
255
+ try:
256
+ output, _ = proc.communicate(timeout=timeout)
257
+ except subprocess.TimeoutExpired:
258
+ _kill_tree(proc)
259
+ try:
260
+ output, _ = proc.communicate(timeout=5)
261
+ except Exception:
262
+ output = ""
263
+ timed_out = True
264
+ output = (output or "") + (
265
+ f"\n[c3:ci] act exceeded {timeout}s and its container tree was killed\n")
266
+
267
+ shutil.rmtree(tmpdir, ignore_errors=True)
268
+ return {
269
+ "exit_code": -1 if timed_out else (proc.returncode or 0),
270
+ "output": output or "",
271
+ "timed_out": timed_out,
272
+ "duration_ms": round((time.time() - start) * 1000),
273
+ "command": " ".join(cmd),
274
+ }