code-context-control 2.77.0__tar.gz → 2.79.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 (425) hide show
  1. {code_context_control-2.77.0/code_context_control.egg-info → code_context_control-2.79.0}/PKG-INFO +3 -2
  2. {code_context_control-2.77.0 → code_context_control-2.79.0}/README.md +2 -1
  3. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/_hook_utils.py +42 -0
  4. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/c3.py +78 -2
  5. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/commands/parser.py +46 -0
  6. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/docs.html +9 -3
  7. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_dispatch.py +132 -20
  8. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_server.py +129 -0
  9. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/app.js +4 -2
  10. code_context_control-2.79.0/cli/hub_ui/components/hub_ci.js +361 -0
  11. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/topbar.js +1 -1
  12. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/mcp_server.py +29 -0
  13. code_context_control-2.79.0/cli/tools/ci.py +228 -0
  14. {code_context_control-2.77.0 → code_context_control-2.79.0/code_context_control.egg-info}/PKG-INFO +3 -2
  15. {code_context_control-2.77.0 → code_context_control-2.79.0}/code_context_control.egg-info/SOURCES.txt +8 -0
  16. {code_context_control-2.77.0 → code_context_control-2.79.0}/pyproject.toml +1 -1
  17. code_context_control-2.79.0/services/ci_failures.py +195 -0
  18. code_context_control-2.79.0/services/ci_runner.py +526 -0
  19. code_context_control-2.79.0/services/ci_workflow.py +620 -0
  20. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/claude_md.py +1 -0
  21. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_access_guard_shell_project.py +12 -1
  22. code_context_control-2.79.0/tests/test_ci_runner.py +529 -0
  23. code_context_control-2.79.0/tests/test_ci_workflow.py +294 -0
  24. code_context_control-2.79.0/tests/test_hook_codex_compat.py +500 -0
  25. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_hook_dispatch.py +6 -0
  26. {code_context_control-2.77.0 → code_context_control-2.79.0}/LICENSE +0 -0
  27. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/__init__.py +0 -0
  28. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/commands/__init__.py +0 -0
  29. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/commands/common.py +0 -0
  30. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/edits.html +0 -0
  31. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/guide/access.html +0 -0
  32. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/guide/bitbucket.html +0 -0
  33. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/guide/credentials.html +0 -0
  34. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/guide/getting-started.html +0 -0
  35. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/guide/index.html +0 -0
  36. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/guide/masking.html +0 -0
  37. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/guide/oracle.html +0 -0
  38. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/guide/shared.css +0 -0
  39. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/guide/tools.html +0 -0
  40. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/guide/workflow.html +0 -0
  41. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_access_guard.py +0 -0
  42. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_artifact.py +0 -0
  43. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_auto_snapshot.py +0 -0
  44. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_c3_signal.py +0 -0
  45. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_c3read.py +0 -0
  46. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_edit_ledger.py +0 -0
  47. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_edit_unlock.py +0 -0
  48. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_filter.py +0 -0
  49. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_ghost_files.py +0 -0
  50. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_pretool_enforce.py +0 -0
  51. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_prompt_recall.py +0 -0
  52. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_read.py +0 -0
  53. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_session_stats.py +0 -0
  54. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hook_terse_advisor.py +0 -0
  55. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/add_project.js +0 -0
  56. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/config_editor.js +0 -0
  57. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/drill_artifacts.js +0 -0
  58. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/drill_health.js +0 -0
  59. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/drill_panel.js +0 -0
  60. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/drill_subprojects.js +0 -0
  61. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/drill_tasks.js +0 -0
  62. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/drill_views.js +0 -0
  63. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/global_search.js +0 -0
  64. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/hub_credentials.js +0 -0
  65. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/hub_enforcement.js +0 -0
  66. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/hub_locks.js +0 -0
  67. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/mcp_manager.js +0 -0
  68. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/modals.js +0 -0
  69. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/project_card.js +0 -0
  70. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/project_tree.js +0 -0
  71. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/session_drawer.js +0 -0
  72. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/settings_modal.js +0 -0
  73. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/sidebar.js +0 -0
  74. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/summary_bar.js +0 -0
  75. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/task_board.js +0 -0
  76. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/components/toasts.js +0 -0
  77. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui/state.js +0 -0
  78. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/hub_ui.html +0 -0
  79. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/mcp_proxy.py +0 -0
  80. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/progress.py +0 -0
  81. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/server.py +0 -0
  82. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/__init__.py +0 -0
  83. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/_grants.py +0 -0
  84. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/_helpers.py +0 -0
  85. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/agent.py +0 -0
  86. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/artifacts.py +0 -0
  87. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/bitbucket.py +0 -0
  88. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/compress.py +0 -0
  89. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/credentials.py +0 -0
  90. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/delegate.py +0 -0
  91. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/edit.py +0 -0
  92. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/edit_verify.py +0 -0
  93. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/edits.py +0 -0
  94. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/federate.py +0 -0
  95. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/filter.py +0 -0
  96. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/impact.py +0 -0
  97. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/jira.py +0 -0
  98. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/locks.py +0 -0
  99. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/memory.py +0 -0
  100. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/override.py +0 -0
  101. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/project.py +0 -0
  102. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/read.py +0 -0
  103. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/search.py +0 -0
  104. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/session.py +0 -0
  105. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/shell.py +0 -0
  106. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/status.py +0 -0
  107. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/tasks.py +0 -0
  108. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/tools/validate.py +0 -0
  109. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/api.js +0 -0
  110. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/app.js +0 -0
  111. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/components/access.js +0 -0
  112. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/components/bitbucket.js +0 -0
  113. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/components/chat.js +0 -0
  114. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/components/credentials.js +0 -0
  115. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/components/dashboard.js +0 -0
  116. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/components/edits.js +0 -0
  117. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/components/enforcement.js +0 -0
  118. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/components/instructions.js +0 -0
  119. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/components/jira.js +0 -0
  120. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/components/memory.js +0 -0
  121. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/components/sessions.js +0 -0
  122. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/components/settings.js +0 -0
  123. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/components/sidebar.js +0 -0
  124. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/components/tasks.js +0 -0
  125. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/icons.js +0 -0
  126. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/pm_shared.js +0 -0
  127. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/shared.js +0 -0
  128. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui/theme.js +0 -0
  129. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui.html +0 -0
  130. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui_legacy.html +0 -0
  131. {code_context_control-2.77.0 → code_context_control-2.79.0}/cli/ui_nano.html +0 -0
  132. {code_context_control-2.77.0 → code_context_control-2.79.0}/code_context_control.egg-info/dependency_links.txt +0 -0
  133. {code_context_control-2.77.0 → code_context_control-2.79.0}/code_context_control.egg-info/entry_points.txt +0 -0
  134. {code_context_control-2.77.0 → code_context_control-2.79.0}/code_context_control.egg-info/requires.txt +0 -0
  135. {code_context_control-2.77.0 → code_context_control-2.79.0}/code_context_control.egg-info/top_level.txt +0 -0
  136. {code_context_control-2.77.0 → code_context_control-2.79.0}/core/__init__.py +0 -0
  137. {code_context_control-2.77.0 → code_context_control-2.79.0}/core/config.py +0 -0
  138. {code_context_control-2.77.0 → code_context_control-2.79.0}/core/ide.py +0 -0
  139. {code_context_control-2.77.0 → code_context_control-2.79.0}/core/mcp_toml.py +0 -0
  140. {code_context_control-2.77.0 → code_context_control-2.79.0}/core/web_security.py +0 -0
  141. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/__init__.py +0 -0
  142. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/config.py +0 -0
  143. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/mcp_oracle.py +0 -0
  144. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/oracle_server.py +0 -0
  145. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/oracle_ui.html +0 -0
  146. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/__init__.py +0 -0
  147. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/activity_reporter.py +0 -0
  148. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/api_auth.py +0 -0
  149. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/c3_bridge.py +0 -0
  150. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/chat_engine.py +0 -0
  151. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/chat_poll.py +0 -0
  152. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/chat_store.py +0 -0
  153. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/cross_memory.py +0 -0
  154. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/discovery_audit.py +0 -0
  155. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/federated_graph.py +0 -0
  156. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/health_checker.py +0 -0
  157. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/insight_engine.py +0 -0
  158. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/local_session.py +0 -0
  159. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/memory_reader.py +0 -0
  160. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/memory_writer.py +0 -0
  161. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/mobile_api.py +0 -0
  162. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/project_scanner.py +0 -0
  163. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/review_agent.py +0 -0
  164. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/tool_executor.py +0 -0
  165. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/services/tool_registry.py +0 -0
  166. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/activity.js +0 -0
  167. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/agents.js +0 -0
  168. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/app.js +0 -0
  169. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/busy.js +0 -0
  170. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/chat/conversations.js +0 -0
  171. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/chat/input.js +0 -0
  172. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/chat/markdown.js +0 -0
  173. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/chat/send.js +0 -0
  174. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/chat/stream_renderer.js +0 -0
  175. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/chat/toolbar.js +0 -0
  176. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/core.js +0 -0
  177. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/crossgraph.js +0 -0
  178. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/header.js +0 -0
  179. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/insights.js +0 -0
  180. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/projects.js +0 -0
  181. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/qrcode.js +0 -0
  182. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/settings.js +0 -0
  183. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/suggestions.js +0 -0
  184. {code_context_control-2.77.0 → code_context_control-2.79.0}/oracle/ui/theme_tabs.js +0 -0
  185. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/__init__.py +0 -0
  186. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/access_guard.py +0 -0
  187. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/access_telemetry.py +0 -0
  188. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/activity_log.py +0 -0
  189. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/agent_base.py +0 -0
  190. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/agent_locks.py +0 -0
  191. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/agents.py +0 -0
  192. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/artifact_defs.py +0 -0
  193. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/artifact_store.py +0 -0
  194. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/auto_memory.py +0 -0
  195. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/bench/__init__.py +0 -0
  196. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/bench/external/__init__.py +0 -0
  197. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/bench/external/aider_polyglot.py +0 -0
  198. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/bench/external/swe_bench.py +0 -0
  199. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/benchmark_dashboard.py +0 -0
  200. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/bitbucket_client.py +0 -0
  201. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/bitbucket_credentials.py +0 -0
  202. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/circuit_breaker.py +0 -0
  203. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/compressor.py +0 -0
  204. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/context_snapshot.py +0 -0
  205. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/conversation_store.py +0 -0
  206. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/credential_store.py +0 -0
  207. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/doc_index.py +0 -0
  208. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/e2e_benchmark.py +0 -0
  209. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/e2e_evaluator.py +0 -0
  210. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/e2e_tasks.py +0 -0
  211. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/edit_ledger.py +0 -0
  212. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/embedding_index.py +0 -0
  213. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/enforcement_policy.py +0 -0
  214. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/error_reporting.py +0 -0
  215. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/file_memory.py +0 -0
  216. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/git_context.py +0 -0
  217. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/hub_service.py +0 -0
  218. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/indexer.py +0 -0
  219. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/jira_client.py +0 -0
  220. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/jira_cloud.py +0 -0
  221. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/jira_credentials.py +0 -0
  222. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/jira_data_center.py +0 -0
  223. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/jira_links.py +0 -0
  224. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/mask_activation.py +0 -0
  225. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/mask_mirror.py +0 -0
  226. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/mask_presets.py +0 -0
  227. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/memory.py +0 -0
  228. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/memory_consolidator.py +0 -0
  229. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/memory_distiller.py +0 -0
  230. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/memory_graph.py +0 -0
  231. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/memory_grounder.py +0 -0
  232. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/memory_queue.py +0 -0
  233. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/memory_scorer.py +0 -0
  234. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/metrics.py +0 -0
  235. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/notifications.py +0 -0
  236. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/ollama_bridge.py +0 -0
  237. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/ollama_client.py +0 -0
  238. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/ollama_credentials.py +0 -0
  239. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/output_filter.py +0 -0
  240. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/override_grants.py +0 -0
  241. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/override_policy.py +0 -0
  242. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/override_requests.py +0 -0
  243. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/override_wake.py +0 -0
  244. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/parser.py +0 -0
  245. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/project_manager.py +0 -0
  246. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/project_runtime.py +0 -0
  247. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/protocol.py +0 -0
  248. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/proxy_state.py +0 -0
  249. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/repo_map.py +0 -0
  250. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/retention.py +0 -0
  251. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/retrieval_broker.py +0 -0
  252. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/router.py +0 -0
  253. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/runtime.py +0 -0
  254. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/scanner.py +0 -0
  255. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/session_benchmark.py +0 -0
  256. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/session_manager.py +0 -0
  257. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/session_preloader.py +0 -0
  258. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/subprojects.py +0 -0
  259. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/task_store.py +0 -0
  260. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/telemetry.py +0 -0
  261. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/text_index.py +0 -0
  262. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/time_tracker.py +0 -0
  263. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/tool_classifier.py +0 -0
  264. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/transcript_index.py +0 -0
  265. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/validation_cache.py +0 -0
  266. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/vector_store.py +0 -0
  267. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/watcher.py +0 -0
  268. {code_context_control-2.77.0 → code_context_control-2.79.0}/services/win_subprocess.py +0 -0
  269. {code_context_control-2.77.0 → code_context_control-2.79.0}/setup.cfg +0 -0
  270. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_access_builtin_optout.py +0 -0
  271. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_access_guard.py +0 -0
  272. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_access_guard_meta.py +0 -0
  273. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_access_guard_surfaces.py +0 -0
  274. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_access_guard_wiring.py +0 -0
  275. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_activity_reporter.py +0 -0
  276. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_agent_locks.py +0 -0
  277. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_aider_polyglot.py +0 -0
  278. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_antigravity_transition.py +0 -0
  279. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_artifact_store.py +0 -0
  280. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_artifact_tool.py +0 -0
  281. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_bitbucket_cli_smoke.py +0 -0
  282. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_bitbucket_client.py +0 -0
  283. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_bitbucket_config_fallback.py +0 -0
  284. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_bitbucket_credentials.py +0 -0
  285. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_bitbucket_tool.py +0 -0
  286. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_c3_bridge_project_artifacts.py +0 -0
  287. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_c3_shell.py +0 -0
  288. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_chat_poll.py +0 -0
  289. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_circuit_breaker.py +0 -0
  290. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_claude_md_merge.py +0 -0
  291. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_cli_smoke.py +0 -0
  292. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_compressor_large_file.py +0 -0
  293. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_credential_store.py +0 -0
  294. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_credentials_routes.py +0 -0
  295. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_credentials_tool.py +0 -0
  296. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_creds_cli_smoke.py +0 -0
  297. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_delegate_cascade.py +0 -0
  298. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_delegate_version_probe.py +0 -0
  299. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_e2e_benchmark.py +0 -0
  300. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_edit_ledger_deadline.py +0 -0
  301. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_edit_ledger_hook.py +0 -0
  302. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_edit_locking.py +0 -0
  303. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_edit_normalization.py +0 -0
  304. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_edit_verify.py +0 -0
  305. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_embedding_index_deadlock.py +0 -0
  306. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_enforcement_flip.py +0 -0
  307. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_enforcement_policy.py +0 -0
  308. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_enforcement_routes.py +0 -0
  309. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_federated_graph.py +0 -0
  310. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_filter_backoff.py +0 -0
  311. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_ghost_files.py +0 -0
  312. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_ghost_generation.py +0 -0
  313. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_git_branch_awareness.py +0 -0
  314. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_hook_access_guard.py +0 -0
  315. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_hook_pretool_enforce.py +0 -0
  316. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_hook_prompt_recall.py +0 -0
  317. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_hook_smoke.py +0 -0
  318. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_hook_state.py +0 -0
  319. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_hook_utf8_stdio.py +0 -0
  320. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_hub_credentials_route.py +0 -0
  321. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_hub_credentials_routes_write.py +0 -0
  322. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_hub_enforcement_routes.py +0 -0
  323. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_hub_inspect_api.py +0 -0
  324. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_hub_locks_routes.py +0 -0
  325. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_hub_server_smoke.py +0 -0
  326. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_hub_subproject_links.py +0 -0
  327. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_ide_workflow_adaptation.py +0 -0
  328. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_init_scan.py +0 -0
  329. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_install_mcp_entrypoint.py +0 -0
  330. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_jira_cli_smoke.py +0 -0
  331. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_jira_client.py +0 -0
  332. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_jira_config_fallback.py +0 -0
  333. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_jira_credentials.py +0 -0
  334. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_jira_links.py +0 -0
  335. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_jira_routes.py +0 -0
  336. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_jira_tool.py +0 -0
  337. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_lazy_store_init.py +0 -0
  338. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_mask_guard.py +0 -0
  339. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_mask_routes.py +0 -0
  340. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_mask_surfaces.py +0 -0
  341. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_mcp_host_guard.py +0 -0
  342. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_mcp_memory_timeout.py +0 -0
  343. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_mcp_server_smoke.py +0 -0
  344. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_mcp_toml.py +0 -0
  345. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_memory_distiller.py +0 -0
  346. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_memory_graph_api.py +0 -0
  347. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_memory_queue.py +0 -0
  348. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_memory_system.py +0 -0
  349. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_mobile_api.py +0 -0
  350. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_mobile_extras.py +0 -0
  351. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_mobile_override_routes.py +0 -0
  352. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_mobile_security_api.py +0 -0
  353. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_no_stale_model_pins.py +0 -0
  354. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_notification_dedup.py +0 -0
  355. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_notification_discipline.py +0 -0
  356. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_ollama_credentials.py +0 -0
  357. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_oracle_api_auth.py +0 -0
  358. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_oracle_apikey_api.py +0 -0
  359. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_oracle_c3_bridge.py +0 -0
  360. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_oracle_chat_engine.py +0 -0
  361. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_oracle_chat_store.py +0 -0
  362. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_oracle_discovery_api.py +0 -0
  363. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_oracle_discovery_audit.py +0 -0
  364. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_oracle_local_auth.py +0 -0
  365. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_oracle_ollama_bridge.py +0 -0
  366. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_oracle_scanner_cache.py +0 -0
  367. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_oracle_security_fixes.py +0 -0
  368. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_oracle_session_bootstrap.py +0 -0
  369. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_oracle_subproject_awareness.py +0 -0
  370. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_oracle_ui_bundle.py +0 -0
  371. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_output_filter.py +0 -0
  372. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_override_grants.py +0 -0
  373. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_override_grants_mcp.py +0 -0
  374. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_override_requests.py +0 -0
  375. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_override_wake.py +0 -0
  376. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_permissions.py +0 -0
  377. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_pm_api.py +0 -0
  378. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_project_manager.py +0 -0
  379. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_project_manager_merge.py +0 -0
  380. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_project_tool.py +0 -0
  381. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_read_coercion.py +0 -0
  382. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_read_edit_parity.py +0 -0
  383. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_related_facts.py +0 -0
  384. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_repo_map.py +0 -0
  385. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_response_boilerplate.py +0 -0
  386. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_retention.py +0 -0
  387. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_review_digest.py +0 -0
  388. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_service_durability.py +0 -0
  389. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_session_benchmark.py +0 -0
  390. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_session_budget.py +0 -0
  391. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_shell_creds.py +0 -0
  392. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_shell_robustness.py +0 -0
  393. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_subproject_exclusion.py +0 -0
  394. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_subproject_federation.py +0 -0
  395. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_subprojects.py +0 -0
  396. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_swe_bench.py +0 -0
  397. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_task_store.py +0 -0
  398. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_task_tool.py +0 -0
  399. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_time_tracker.py +0 -0
  400. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_token_telemetry.py +0 -0
  401. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_tool_registry.py +0 -0
  402. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_upgrade_and_version.py +0 -0
  403. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_validate.py +0 -0
  404. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_vault_write_guard.py +0 -0
  405. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_version_sync.py +0 -0
  406. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_web_security.py +0 -0
  407. {code_context_control-2.77.0 → code_context_control-2.79.0}/tests/test_windows_reliability.py +0 -0
  408. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/__init__.py +0 -0
  409. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/backend.py +0 -0
  410. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/main.py +0 -0
  411. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/screens/__init__.py +0 -0
  412. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/screens/benchmark_view.py +0 -0
  413. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/screens/claudemd_view.py +0 -0
  414. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/screens/compress_view.py +0 -0
  415. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/screens/index_view.py +0 -0
  416. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/screens/init_view.py +0 -0
  417. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/screens/mcp_view.py +0 -0
  418. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/screens/optimize_view.py +0 -0
  419. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/screens/pipe_view.py +0 -0
  420. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/screens/projects_view.py +0 -0
  421. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/screens/search_view.py +0 -0
  422. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/screens/session_view.py +0 -0
  423. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/screens/stats.py +0 -0
  424. {code_context_control-2.77.0 → code_context_control-2.79.0}/tui/screens/ui_view.py +0 -0
  425. {code_context_control-2.77.0 → code_context_control-2.79.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.77.0
3
+ Version: 2.79.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,6 +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
206
  | `c3_delegate` | Offload heavy work to local Ollama / Codex / Gemini |
206
207
  | `c3_agent` | Workflows: `review_changes`, `investigate`, `preflight`, `prepare_context`, `validate_compress` |
207
208
  | `c3_edits` | Edit-ledger queries, version diffs, restore points, per-branch filter, `verify` (did a failed `c3_edit` still land?) |
@@ -212,7 +213,7 @@ C3 exposes **21 tools** as a native MCP server. Your IDE calls them directly:
212
213
  | `c3_jira` | Jira Cloud + Data Center — JQL, issues, transitions (v2.56.0) |
213
214
  | `c3_project` | Cross-project discovery & operations; guarded writes (v2.31.0) |
214
215
 
215
- Every tool is **read-only safe in plan mode** except `c3_edit`, `c3_shell`, `c3_artifacts(action='restore')`, `c3_delegate` with write delegation enabled, and write actions on `c3_bitbucket` / `c3_jira` / `c3_credentials` / `c3_project` / `c3_task` / `c3_locks`.
216
+ Every tool is **read-only safe in plan mode** except `c3_edit`, `c3_shell`, `c3_ci(action='run'|'rerun')`, `c3_artifacts(action='restore')`, `c3_delegate` with write delegation enabled, and write actions on `c3_bitbucket` / `c3_jira` / `c3_credentials` / `c3_project` / `c3_task` / `c3_locks`.
216
217
 
217
218
  On Windows `c3_shell` uses Git Bash when available. Git Bash bundles no `jq`; use `python -m json.tool` for portable JSON formatting.
218
219
 
@@ -139,6 +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
143
  | `c3_delegate` | Offload heavy work to local Ollama / Codex / Gemini |
143
144
  | `c3_agent` | Workflows: `review_changes`, `investigate`, `preflight`, `prepare_context`, `validate_compress` |
144
145
  | `c3_edits` | Edit-ledger queries, version diffs, restore points, per-branch filter, `verify` (did a failed `c3_edit` still land?) |
@@ -149,7 +150,7 @@ C3 exposes **21 tools** as a native MCP server. Your IDE calls them directly:
149
150
  | `c3_jira` | Jira Cloud + Data Center — JQL, issues, transitions (v2.56.0) |
150
151
  | `c3_project` | Cross-project discovery & operations; guarded writes (v2.31.0) |
151
152
 
152
- Every tool is **read-only safe in plan mode** except `c3_edit`, `c3_shell`, `c3_artifacts(action='restore')`, `c3_delegate` with write delegation enabled, and write actions on `c3_bitbucket` / `c3_jira` / `c3_credentials` / `c3_project` / `c3_task` / `c3_locks`.
153
+ Every tool is **read-only safe in plan mode** except `c3_edit`, `c3_shell`, `c3_ci(action='run'|'rerun')`, `c3_artifacts(action='restore')`, `c3_delegate` with write delegation enabled, and write actions on `c3_bitbucket` / `c3_jira` / `c3_credentials` / `c3_project` / `c3_task` / `c3_locks`.
153
154
 
154
155
  On Windows `c3_shell` uses Git Bash when available. Git Bash bundles no `jq`; use `python -m json.tool` for portable JSON formatting.
155
156
 
@@ -359,6 +359,48 @@ def record_unlocked_files(
359
359
  save_enforcement_state(state, project_path)
360
360
 
361
361
 
362
+ # ── Host (IDE runtime) detection ─────────────────────────────────────────────
363
+ # Three hosts drive C3 hooks, and their OUTPUT contracts differ enough that one
364
+ # shared payload shape cannot satisfy all of them:
365
+ #
366
+ # claude — top-level "tool_result" / "additionalContext" accepted.
367
+ # gemini — no tool_result; context nests under hookSpecificOutput.
368
+ # codex — every hook output schema is additionalProperties:false and
369
+ # hookSpecificOutput.hookEventName is REQUIRED. Unknown keys are a
370
+ # hard deserialization error, not a warning, so emitting the Claude
371
+ # shape makes Codex discard the whole hook response.
372
+ HOST_CLAUDE = "claude"
373
+ HOST_GEMINI = "gemini"
374
+ HOST_CODEX = "codex"
375
+
376
+
377
+ def detect_host(data: dict) -> str:
378
+ """Identify which agent runtime sent this hook payload.
379
+
380
+ Codex is detected by `turn_id`, which its wire schema marks required on
381
+ every turn-scoped event C3 hooks (pre/post tool use, user prompt submit,
382
+ stop) and documents as "Codex extension". Claude Code never sends it.
383
+
384
+ Codex MUST be checked before Gemini: Codex declares `tool_response` as
385
+ free-form, so an MCP tool that returns an object would otherwise trip the
386
+ isinstance-dict test and be mistaken for Gemini.
387
+
388
+ The CODEX_* env fallback covers a future Codex that drops `turn_id`. A
389
+ false positive there degrades gracefully — Claude Code also accepts
390
+ hookSpecificOutput.additionalContext — so guessing "codex" is the safe
391
+ direction to be wrong in.
392
+ """
393
+ if not isinstance(data, dict):
394
+ return HOST_CLAUDE
395
+ if data.get("turn_id"):
396
+ return HOST_CODEX
397
+ if isinstance(data.get("tool_response"), dict):
398
+ return HOST_GEMINI
399
+ if os.environ.get("CODEX_THREAD_ID") or os.environ.get("CODEX_MANAGED_BY_NPM"):
400
+ return HOST_CODEX
401
+ return HOST_CLAUDE
402
+
403
+
362
404
  # Map Gemini CLI built-in tool names → canonical Claude Code equivalents
363
405
  GEMINI_TOOL_MAP = {
364
406
  "run_shell_command": "Bash",
@@ -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.77.0"
95
+ __version__ = "2.79.0"
96
96
 
97
97
 
98
98
  def _compress_file_cli(compressor, path, mode="smart", **kw):
@@ -261,7 +261,7 @@ _C3_MCP_ALLOW = [
261
261
  "mcp__c3__c3_bitbucket",
262
262
  "mcp__c3__c3_jira", "mcp__c3__c3_credentials",
263
263
  "mcp__c3__c3_project", "mcp__c3__c3_task", "mcp__c3__c3_artifacts",
264
- "mcp__c3__c3_override",
264
+ "mcp__c3__c3_override", "mcp__c3__c3_ci",
265
265
  ]
266
266
 
267
267
  # Obsolete MCP tool names from earlier C3 versions. `c3 permissions clean`
@@ -4772,6 +4772,7 @@ back to native tools as the task progresses.
4772
4772
  - **Shell**: `c3_shell(cmd, timeout=60)` — structured shell exec (tests/git/build). Auto-filters output, logs git mutations to the ledger. Native Bash for interactive/TTY only
4773
4773
  - **Memory**: `c3_memory(action='recall')` — full recall. `index` + `fetch` for token-efficient two-step retrieval
4774
4774
  - **Delegate**: `c3_delegate(task, backend='ollama|codex|gemini|claude|auto')` — offload to other models
4775
+ - **Local CI** (v2.79.0+): `c3_ci(action='inspect|run|rerun|failures')` — run THIS repo's real `.github/workflows` here instead of pushing for feedback. `run` executes in `needs` order; `failures` gives {file,line,message}; `rerun` retries only what failed. Only `FULL_CI_PASS` means safe to push — `PARTIAL_PASS` means something did not run (other OS, unsupported action, or your selection).
4775
4776
  - **Bitbucket** (v2.30.0+, when `c3 bitbucket login` has run): `c3_bitbucket(action='list_prs|get_pr|merge_pr|...')` — self-hosted Bitbucket Data Center / Server. Token in OS keyring; mutating actions auto-log to the edit ledger.
4776
4777
  - **Cross-project** (v2.31.0+): `c3_project(action='list|scan|search|read|edit|...', project='<name|path>')` — discover and operate on OTHER c3-installed projects. Reads run freely; writes (edit/shell/memory) need `allow_write=true`.
4777
4778
  - **Masked paths** (v2.63.0+): content prefixed `[c3-mask:transformed]` is a policy-transformed VIEW, not the file. Values may be synthetic, rows withheld, bodies stripped — don't copy literals out of it or infer completeness from it. Masked paths are read-only and refuse shell/git/validate/impact/filter/delegate (`[c3-mask:unsupported]`). That's policy, not a transient error — report the block instead of routing around it.
@@ -6030,6 +6031,80 @@ def _creds_cmd_import(args, project_path: str) -> None:
6030
6031
  "(use --overwrite to replace)")
6031
6032
 
6032
6033
 
6034
+ def cmd_ci(args):
6035
+ """AgentCI entry point — exits with the finding.
6036
+
6037
+ The shared dispatcher below discards every command's return value, so
6038
+ `ci` exits for itself rather than changing exit-code behaviour for the
6039
+ other twenty commands. A pre-push hook or a script has to be able to read
6040
+ "not a full pass" from `$?`, which is the whole point of the verdict.
6041
+ """
6042
+ sys.exit(_ci_main(args))
6043
+
6044
+
6045
+ def _ci_main(args) -> int:
6046
+ """AgentCI — run this repo's real CI locally (docs/agent-ci.md).
6047
+
6048
+ The human mirror of the c3_ci tool. It deliberately reuses the same
6049
+ handler, so the CLI and the agent surface can never drift into disagreeing
6050
+ about what a verdict means. Returns the process exit code.
6051
+ """
6052
+ import json as _json
6053
+ from types import SimpleNamespace
6054
+
6055
+ from cli.tools.ci import handle_ci
6056
+
6057
+ sub = getattr(args, "ci_cmd", None) or "inspect"
6058
+ project_path = getattr(args, "project_path", ".") or "."
6059
+ as_json = bool(getattr(args, "json", False))
6060
+
6061
+ if as_json:
6062
+ # Structured mode answers from the services directly — the text
6063
+ # handler formats for reading, and reformatting prose into JSON would
6064
+ # be a second, lossier schema.
6065
+ from services import ci_runner as cr
6066
+ from services.ci_workflow import inspect_project
6067
+ if sub == "inspect":
6068
+ payload = inspect_project(project_path)
6069
+ elif sub in ("run", "rerun"):
6070
+ only = None
6071
+ if sub == "rerun":
6072
+ prior = cr.load_run(project_path, getattr(args, "run_id", ""))
6073
+ only = (prior or {}).get("failed") or []
6074
+ if not only:
6075
+ print(_json.dumps({"error": "no failed jobs to rerun"}))
6076
+ return 1
6077
+ payload = cr.run_ci(
6078
+ project_path, selector=getattr(args, "job", ""),
6079
+ allow_foreign=bool(getattr(args, "allow_foreign", False)),
6080
+ only=only, timeout=getattr(args, "timeout", 0) or cr.DEFAULT_STEP_TIMEOUT,
6081
+ workflow=getattr(args, "workflow", "")).to_dict()
6082
+ elif sub == "runs":
6083
+ payload = {"runs": cr.list_runs(project_path)}
6084
+ else:
6085
+ payload = cr.load_run(project_path, getattr(args, "run_id", ""))
6086
+ print(_json.dumps(payload, indent=2))
6087
+ verdict = payload.get("verdict") if isinstance(payload, dict) else ""
6088
+ return 0 if verdict in (cr.FULL_PASS, None, "") else 1
6089
+
6090
+ svc = SimpleNamespace(project_path=project_path)
6091
+
6092
+ def finalize(_name, _args, resp, _summ, **_kw):
6093
+ return resp
6094
+
6095
+ out = handle_ci(
6096
+ sub, getattr(args, "job", "") or "", getattr(args, "run_id", "") or "",
6097
+ bool(getattr(args, "allow_foreign", False)),
6098
+ getattr(args, "workflow", "") or "", getattr(args, "tail", 200) or 200,
6099
+ getattr(args, "timeout", 0) or 0, svc, finalize)
6100
+ print(out)
6101
+ # Exit code is the finding: a non-full verdict must not read as success to
6102
+ # a shell script or a pre-push hook.
6103
+ if sub in ("run", "rerun") and "FULL_CI_PASS" not in out:
6104
+ return 1
6105
+ return 0
6106
+
6107
+
6033
6108
  def cmd_locks(args):
6034
6109
  """Agent Locks — human surface. force-release lives here, never in c3_locks."""
6035
6110
  from services import agent_locks as al
@@ -8290,6 +8365,7 @@ def main():
8290
8365
  "enforce": cmd_enforce,
8291
8366
  "override": cmd_override,
8292
8367
  "locks": cmd_locks,
8368
+ "ci": cmd_ci,
8293
8369
  "oracle": cmd_oracle,
8294
8370
  "upgrade": cmd_upgrade,
8295
8371
  }
@@ -472,6 +472,52 @@ def build_parser(version: str, parse_cli_ide_arg):
472
472
  lk_sweep = locks_subs.add_parser("sweep", help="Drop expired leases now")
473
473
  lk_sweep.add_argument("--path", dest="project_path", default=".")
474
474
 
475
+ # ── AgentCI — local CI execution (docs/agent-ci.md) ─────────────────
476
+ # Reads .github/workflows/*.yml as the source of truth. The CLI mirrors
477
+ # the c3_ci tool so a human can drive the same loop an agent does.
478
+ p_ci = subparsers.add_parser(
479
+ "ci", help="Run this repo's real CI locally instead of pushing for feedback")
480
+ ci_subs = p_ci.add_subparsers(dest="ci_cmd")
481
+
482
+ def _ci_common(sub):
483
+ sub.add_argument("--path", dest="project_path", default=".",
484
+ help="Project directory (default: current)")
485
+ sub.add_argument("--json", action="store_true", help="Machine-readable output")
486
+ return sub
487
+
488
+ _ci_common(ci_subs.add_parser(
489
+ "inspect", help="Show workflows, the job DAG, and what runs on this host"))
490
+
491
+ ci_run = _ci_common(ci_subs.add_parser("run", help="Execute jobs locally"))
492
+ ci_run.add_argument("--job", default="",
493
+ help="Job id, matrix cell, or workflow::job (default: all)")
494
+ ci_run.add_argument("--workflow", default="", help="Limit to one workflow by name")
495
+ ci_run.add_argument("--allow-foreign", action="store_true",
496
+ help="Also run jobs targeting another OS (labelled cross-OS; "
497
+ "can never yield FULL_CI_PASS)")
498
+ ci_run.add_argument("--timeout", type=int, default=0,
499
+ help="Per-step timeout in seconds (default 900)")
500
+
501
+ ci_rerun = _ci_common(ci_subs.add_parser(
502
+ "rerun", help="Re-run only the jobs that failed in the last run"))
503
+ ci_rerun.add_argument("--run", dest="run_id", default="", help="Run id (default: latest)")
504
+ ci_rerun.add_argument("--allow-foreign", action="store_true")
505
+ ci_rerun.add_argument("--timeout", type=int, default=0)
506
+
507
+ ci_status = _ci_common(ci_subs.add_parser("status", help="Last run's verdict and jobs"))
508
+ ci_status.add_argument("--run", dest="run_id", default="")
509
+
510
+ ci_fail = _ci_common(ci_subs.add_parser(
511
+ "failures", help="Structured failures from the last run"))
512
+ ci_fail.add_argument("--run", dest="run_id", default="")
513
+
514
+ ci_logs = _ci_common(ci_subs.add_parser("logs", help="Tail one job's log"))
515
+ ci_logs.add_argument("job", nargs="?", default="", help="Job key")
516
+ ci_logs.add_argument("--run", dest="run_id", default="")
517
+ ci_logs.add_argument("--tail", type=int, default=200)
518
+
519
+ _ci_common(ci_subs.add_parser("runs", help="Recent local CI runs"))
520
+
475
521
  # ── Override Requests (v2.69.0, docs/override-requests.md) ──────────
476
522
  # Human-only approval surface. A grant is single-use, session-bound,
477
523
  # path-exact and TTL-capped, and it never edits policy: the rule that
@@ -1747,11 +1747,17 @@ Background agent system with periodic check loop and AI enhancement.
1747
1747
  ...</code></pre>
1748
1748
 
1749
1749
  <!-- ─── Hooks ──────────────────────── -->
1750
- <h3 id="hooks">PostToolUse Hooks <span class="badge badge-blue" style="font-size:10px">Claude Code only</span>
1750
+ <h3 id="hooks">PostToolUse Hooks <span class="badge badge-blue" style="font-size:10px">installed for Claude Code</span>
1751
1751
  </h3>
1752
- <p>C3 registers two PostToolUse hooks in <code>.claude/settings.local.json</code> that run after Claude executes
1753
- certain tools. These hooks are only installed for Claude Code &mdash; other IDEs rely on the workflow
1752
+ <p>C3 registers its PostToolUse hooks in <code>.claude/settings.local.json</code> that run after Claude executes
1753
+ certain tools. C3 only <em>installs</em> them for Claude Code &mdash; other IDEs rely on the workflow
1754
1754
  instructions in their instructions file instead.</p>
1755
+ <p>The dispatcher itself is not Claude-only. Since v2.78.0 it branches by host and speaks three wire formats:
1756
+ Claude Code, Gemini CLI, and Codex CLI &mdash; so a Codex session with a hand-authored
1757
+ <code>.codex/hooks.json</code> pointing at <code>cli/hook_dispatch.py</code> gets full enforcement and
1758
+ bookkeeping. Codex validates hook output strictly (<code>additionalProperties: false</code>), which is why it
1759
+ needs its own serializer rather than the Claude shape. Output filtering is skipped on Codex: its schema has no
1760
+ <code>tool_result</code> to replace, so tokenizing the payload would be wasted work.</p>
1755
1761
 
1756
1762
  <table>
1757
1763
  <tr>
@@ -14,12 +14,20 @@ Matcher granularity that previously lived in .claude/settings.local.json
14
14
  routing tables below — install-mcp registers the dispatcher once per matcher,
15
15
  and the dispatcher decides which sub-hook logic applies.
16
16
 
17
- Output composition follows Claude Code hook semantics:
17
+ Output composition merges sub-hook results the same way for every host:
18
18
  - a permissionDecision "deny" from any sub-hook wins over allows;
19
19
  - additionalContext strings from all sub-hooks are concatenated;
20
- - a tool_result replacement (hook_filter) is preserved;
21
- - plain-text (user-visible) messages are printed only when no structured
22
- JSON output exists, matching the old per-hook stdout behavior.
20
+ - a tool_result replacement (hook_filter) is captured.
21
+
22
+ Serialization then branches by host, because the three runtimes do NOT share
23
+ a wire contract (see _hook_utils.detect_host):
24
+ - Claude Code — top-level tool_result / additionalContext; plain-text
25
+ messages print raw when no structured JSON output exists;
26
+ - Gemini CLI — no tool_result; context nests under hookSpecificOutput;
27
+ - Codex CLI — every hook output schema is additionalProperties:false with a
28
+ required hookEventName, so output is built from a per-event whitelist and
29
+ Codex never sees a tool_result. Codex also skips hook_filter entirely:
30
+ it is the only sub-hook that tokenizes the full tool payload.
23
31
 
24
32
  Failure visibility: a sub-hook crash is logged to .c3/hook_errors.log and
25
33
  never kills the remaining sub-hooks. CRITICAL failures — a sub-hook module
@@ -44,10 +52,40 @@ from cli import _hook_utils # noqa: E402
44
52
  # spellings share ONE module instance (state warnings must not split).
45
53
  sys.modules.setdefault("_hook_utils", _hook_utils)
46
54
 
47
- from cli._hook_utils import log_hook_error, normalize_tool_name # noqa: E402
55
+ from cli._hook_utils import ( # noqa: E402
56
+ HOST_CLAUDE,
57
+ HOST_CODEX,
58
+ HOST_GEMINI,
59
+ detect_host,
60
+ log_hook_error,
61
+ normalize_tool_name,
62
+ )
48
63
 
49
64
  VALID_EVENTS = ("pretool", "posttool", "stop", "prompt")
50
65
 
66
+ # ── Codex wire contract ─────────────────────────────────────────────────────
67
+ # Codex validates hook output against additionalProperties:false schemas, so
68
+ # the Codex branch emits by WHITELIST rather than by "drop the keys we know
69
+ # about". A sub-hook that invents a new output key can then never break Codex
70
+ # — the key is simply not forwarded.
71
+ _CODEX_EVENT_NAMES = {
72
+ "pretool": "PreToolUse",
73
+ "posttool": "PostToolUse",
74
+ "prompt": "UserPromptSubmit",
75
+ "stop": "Stop",
76
+ }
77
+
78
+ # Keys each event's hookSpecificOutput accepts. "stop" is absent deliberately:
79
+ # stop.command.output has no hookSpecificOutput member at all.
80
+ _CODEX_HSO_KEYS = {
81
+ "pretool": {
82
+ "hookEventName", "additionalContext",
83
+ "permissionDecision", "permissionDecisionReason", "updatedInput",
84
+ },
85
+ "posttool": {"hookEventName", "additionalContext", "updatedMCPToolOutput"},
86
+ "prompt": {"hookEventName", "additionalContext"},
87
+ }
88
+
51
89
  # ── Routing tables (parity with the pre-v2.42 per-matcher registration) ─────
52
90
 
53
91
  # c3 tools whose completion refreshes the enforcement signal — exactly the
@@ -77,9 +115,15 @@ _GHOST_TOOLS = {
77
115
  }
78
116
 
79
117
 
80
- def _routes(event: str, raw_tool: str, norm_tool: str):
118
+ def _routes(event: str, raw_tool: str, norm_tool: str, host: str = HOST_CLAUDE):
81
119
  """Yield sub-hook module names applicable to this event + tool, in the
82
- same relative order the separate hook commands used to run."""
120
+ same relative order the separate hook commands used to run.
121
+
122
+ `host` gates hooks that cannot pay their way on a given runtime — see the
123
+ hook_filter carve-out below. Every other sub-hook is host-agnostic: they
124
+ do bookkeeping (ledger, artifact, signal, unlock, ghost sweep) without
125
+ parsing or tokenizing the tool payload, so they stay on for all hosts.
126
+ """
83
127
  if event == "pretool":
84
128
  # Access Guard runs FIRST: merge_outputs keeps the first deny, so a
85
129
  # policy denial here cannot be readmitted by sticky-unlock allows.
@@ -87,7 +131,13 @@ def _routes(event: str, raw_tool: str, norm_tool: str):
87
131
  # hook_pretool_enforce self-filters via its _PREREQS table.
88
132
  yield "hook_pretool_enforce"
89
133
  elif event == "posttool":
90
- if norm_tool == "Bash":
134
+ # hook_filter is the ONE sub-hook that reads the whole tool payload:
135
+ # it tiktoken-encodes the entire Bash output to measure savings, a
136
+ # Rust-side allocation proportional to output size. On Codex that work
137
+ # is also pointless — its schema has no tool_result, so the filtered
138
+ # text could never replace the output anyway. Skip it entirely rather
139
+ # than risk an allocation failure for a result we must discard.
140
+ if norm_tool == "Bash" and host != HOST_CODEX:
91
141
  yield "hook_filter"
92
142
  if norm_tool == "Read":
93
143
  yield "hook_read"
@@ -155,18 +205,66 @@ def _load_run(module_name: str):
155
205
  return result
156
206
 
157
207
 
208
+ def _codex_output(event: str, deny_hso, contexts: list, tool_result,
209
+ texts: list) -> dict | None:
210
+ """Serialize merged sub-hook results into Codex's wire contract.
211
+
212
+ Codex accepts neither a top-level `tool_result` nor a top-level
213
+ `additionalContext`, and requires `hookEventName` inside
214
+ hookSpecificOutput. Anything that has no Codex-legal home is folded into a
215
+ channel that does exist rather than dropped:
216
+
217
+ - a tool_result replacement becomes leading context (it cannot replace the
218
+ output on Codex, but the filtered text is still worth reading);
219
+ - "_text" becomes `systemMessage`, the only user-visible string field
220
+ present on every Codex hook output schema — including Stop, which has no
221
+ hookSpecificOutput at all, so its context lands there too.
222
+ """
223
+ if tool_result is not None:
224
+ contexts.insert(0, str(tool_result))
225
+
226
+ result: dict = {}
227
+ hso_keys = _CODEX_HSO_KEYS.get(event)
228
+ joined = "\n".join(c for c in contexts if c)
229
+
230
+ if hso_keys is not None:
231
+ hso = {k: v for k, v in (deny_hso or {}).items() if k in hso_keys}
232
+ if joined:
233
+ # A deny's own reason travels in permissionDecisionReason; `joined`
234
+ # is the non-deny context, so the two never collide.
235
+ hso["additionalContext"] = joined
236
+ if hso:
237
+ hso["hookEventName"] = _CODEX_EVENT_NAMES[event]
238
+ result["hookSpecificOutput"] = hso
239
+ elif joined:
240
+ texts.insert(0, joined)
241
+
242
+ if texts:
243
+ result["systemMessage"] = "\n".join(texts)
244
+ return result or None
245
+
246
+
158
247
  def merge_outputs(outputs: list, warnings: list, is_gemini: bool = False,
159
- event: str = "") -> dict | None:
160
- """Compose sub-hook outputs per Claude Code hook semantics.
248
+ event: str = "", host: str | None = None) -> dict | None:
249
+ """Compose sub-hook outputs into ONE host-appropriate hook response.
161
250
 
251
+ Collection is host-agnostic:
162
252
  - deny beats allow: the first hookSpecificOutput carrying a
163
- permissionDecision "deny" is kept verbatim;
253
+ permissionDecision "deny" is kept;
164
254
  - additionalContext strings are concatenated with newlines
165
255
  (critical warnings appended last);
166
- - tool_result replacement (hook_filter) is preserved;
167
- - "_text" plain messages ride along under "_text" — main() prints them
168
- raw only when no structured output exists.
256
+ - a tool_result replacement (hook_filter) is captured.
257
+
258
+ Emission is not. Each host gets the shape its parser accepts — Claude
259
+ takes top-level keys, Gemini nests context and has no tool_result, Codex
260
+ validates against additionalProperties:false schemas (see _codex_output).
261
+
262
+ `is_gemini` is retained for callers that predate `host`; `host` wins when
263
+ both are given.
169
264
  """
265
+ if host is None:
266
+ host = HOST_GEMINI if is_gemini else HOST_CLAUDE
267
+
170
268
  deny_hso = None
171
269
  contexts: list = []
172
270
  texts: list = []
@@ -192,6 +290,10 @@ def merge_outputs(outputs: list, warnings: list, is_gemini: bool = False,
192
290
 
193
291
  contexts.extend(warnings)
194
292
 
293
+ if host == HOST_CODEX:
294
+ return _codex_output(event, deny_hso, contexts, tool_result, texts)
295
+
296
+ is_gemini = host == HOST_GEMINI
195
297
  result: dict = {}
196
298
  if deny_hso is not None:
197
299
  result["hookSpecificOutput"] = deny_hso
@@ -229,14 +331,14 @@ def dispatch(event: str, payload: dict, project_path: Path | None = None) -> dic
229
331
  """Run all applicable sub-hooks in-process and merge their outputs."""
230
332
  raw_tool = str(payload.get("tool_name", ""))
231
333
  norm_tool = normalize_tool_name(raw_tool)
232
- is_gemini = isinstance(payload.get("tool_response", ""), dict)
334
+ host = detect_host(payload)
233
335
 
234
336
  outputs: list = []
235
337
  warnings: list = []
236
338
  # Drain any stale warnings from a previous dispatch in this process.
237
339
  _hook_utils.drain_state_warnings()
238
340
 
239
- for module_name in _routes(event, raw_tool, norm_tool):
341
+ for module_name in _routes(event, raw_tool, norm_tool, host):
240
342
  run_fn, err = _load_run(module_name)
241
343
  if run_fn is None:
242
344
  warnings.append(
@@ -263,7 +365,7 @@ def dispatch(event: str, payload: dict, project_path: Path | None = None) -> dic
263
365
  # become visible instead of silently disabling enforcement.
264
366
  warnings.extend(_hook_utils.drain_state_warnings())
265
367
 
266
- return merge_outputs(outputs, warnings, is_gemini=is_gemini, event=event)
368
+ return merge_outputs(outputs, warnings, event=event, host=host)
267
369
 
268
370
 
269
371
  def main() -> None:
@@ -288,14 +390,24 @@ def main() -> None:
288
390
  try:
289
391
  output = dispatch(event, payload)
290
392
  except Exception as exc:
291
- # The dispatcher itself failing is critical — say so.
393
+ # The dispatcher itself failing is critical — say so. Route the notice
394
+ # through merge_outputs so even the panic path speaks the host's
395
+ # dialect: a raw {"additionalContext": ...} is itself invalid on Codex,
396
+ # which would turn one crash into two error reports.
292
397
  log_hook_error("hook_dispatch", exc)
293
- print(json.dumps({
398
+ notice = {
294
399
  "additionalContext": (
295
400
  f"[c3:hook-error] hook_dispatch: {type(exc).__name__}: {exc}; "
296
401
  "see .c3/hook_errors.log"
297
402
  )
298
- }))
403
+ }
404
+ try:
405
+ output = merge_outputs([notice], [], event=event,
406
+ host=detect_host(payload))
407
+ except Exception:
408
+ output = notice
409
+ if output:
410
+ print(json.dumps(output))
299
411
  return
300
412
 
301
413
  if not output:
@@ -337,6 +337,7 @@ _HUB_JS_FILES = [
337
337
  "hub_ui/components/hub_credentials.js",
338
338
  "hub_ui/components/hub_locks.js",
339
339
  "hub_ui/components/hub_enforcement.js",
340
+ "hub_ui/components/hub_ci.js",
340
341
  "hub_ui/components/drill_subprojects.js",
341
342
  "hub_ui/components/drill_health.js",
342
343
  "hub_ui/components/drill_tasks.js",
@@ -1282,6 +1283,134 @@ def api_batch_cancel():
1282
1283
  return jsonify({"cancelled": True})
1283
1284
 
1284
1285
 
1286
+ # ── AgentCI: local CI execution (docs/agent-ci.md) ────────────────────────
1287
+ # A CI run is minutes long, so the POST starts a worker and returns; the UI
1288
+ # polls. One run per project at a time — two concurrent runs would interleave
1289
+ # their writes into the same .c3/ci run index and neither result would be
1290
+ # trustworthy.
1291
+
1292
+ _ci_runs: dict = {} # project path -> in-flight state
1293
+ _ci_lock = threading.Lock()
1294
+
1295
+
1296
+ def _ci_worker(project: str, job: str, allow_foreign: bool, workflow: str) -> None:
1297
+ from services import ci_runner as cr
1298
+ try:
1299
+ result = cr.run_ci(project, selector=job, allow_foreign=allow_foreign,
1300
+ workflow=workflow)
1301
+ payload = {"running": False, "done": True, "error": None,
1302
+ "run_id": result.run_id, "verdict": result.verdict,
1303
+ "note": result.note}
1304
+ except Exception as exc: # a crashed worker must not look idle
1305
+ payload = {"running": False, "done": True, "run_id": "",
1306
+ "error": f"{type(exc).__name__}: {exc}"}
1307
+ with _ci_lock:
1308
+ _ci_runs[project] = payload
1309
+
1310
+
1311
+ @app.route("/api/ci/inspect", methods=["GET"])
1312
+ def api_ci_inspect():
1313
+ """Workflows, the normalized job DAG, and what is runnable on this host."""
1314
+ from services.ci_workflow import inspect_project
1315
+ path = (request.args.get("path") or "").strip()
1316
+ if not path:
1317
+ return jsonify({"error": "path is required"}), 400
1318
+ try:
1319
+ resolved = _resolve_project_path(path)
1320
+ except ValueError as e:
1321
+ return jsonify({"error": str(e)}), 404
1322
+ try:
1323
+ return jsonify(inspect_project(resolved))
1324
+ except Exception as e:
1325
+ return jsonify({"error": f"{type(e).__name__}: {e}"}), 500
1326
+
1327
+
1328
+ @app.route("/api/ci/runs", methods=["GET"])
1329
+ def api_ci_runs():
1330
+ from services import ci_runner as cr
1331
+ path = (request.args.get("path") or "").strip()
1332
+ if not path:
1333
+ return jsonify({"error": "path is required"}), 400
1334
+ try:
1335
+ resolved = _resolve_project_path(path)
1336
+ except ValueError as e:
1337
+ return jsonify({"error": str(e)}), 404
1338
+ limit = max(1, min(100, int(request.args.get("limit") or 20)))
1339
+ return jsonify({"runs": cr.list_runs(resolved, limit=limit)})
1340
+
1341
+
1342
+ @app.route("/api/ci/run", methods=["GET"])
1343
+ def api_ci_run_detail():
1344
+ from services import ci_runner as cr
1345
+ path = (request.args.get("path") or "").strip()
1346
+ if not path:
1347
+ return jsonify({"error": "path is required"}), 400
1348
+ try:
1349
+ resolved = _resolve_project_path(path)
1350
+ except ValueError as e:
1351
+ return jsonify({"error": str(e)}), 404
1352
+ return jsonify(cr.load_run(resolved, (request.args.get("run_id") or "").strip())
1353
+ or {})
1354
+
1355
+
1356
+ @app.route("/api/ci/logs", methods=["GET"])
1357
+ def api_ci_logs():
1358
+ from services import ci_runner as cr
1359
+ path = (request.args.get("path") or "").strip()
1360
+ run_id = (request.args.get("run_id") or "").strip()
1361
+ job = (request.args.get("job") or "").strip()
1362
+ if not (path and run_id and job):
1363
+ return jsonify({"error": "path, run_id and job are required"}), 400
1364
+ try:
1365
+ resolved = _resolve_project_path(path)
1366
+ except ValueError as e:
1367
+ return jsonify({"error": str(e)}), 404
1368
+ tail = max(1, min(2000, int(request.args.get("tail") or 300)))
1369
+ return jsonify({"log": cr.read_log(resolved, run_id, job, tail=tail)})
1370
+
1371
+
1372
+ @app.route("/api/ci/run", methods=["POST"])
1373
+ def api_ci_run_start():
1374
+ """Start a local CI run in the background. Poll /api/ci/status."""
1375
+ data = request.get_json(force=True) or {}
1376
+ path = str(data.get("path") or "").strip()
1377
+ if not path:
1378
+ return jsonify({"error": "path is required"}), 400
1379
+ try:
1380
+ resolved = str(_resolve_project_path(path))
1381
+ except ValueError as e:
1382
+ return jsonify({"error": str(e)}), 404
1383
+
1384
+ with _ci_lock:
1385
+ state = _ci_runs.get(resolved) or {}
1386
+ if state.get("running"):
1387
+ return jsonify({"error": "a CI run is already in progress here"}), 409
1388
+ _ci_runs[resolved] = {"running": True, "done": False, "error": None,
1389
+ "run_id": "", "verdict": "", "note": ""}
1390
+
1391
+ threading.Thread(
1392
+ target=_ci_worker,
1393
+ args=(resolved, str(data.get("job") or ""),
1394
+ bool(data.get("allow_foreign")), str(data.get("workflow") or "")),
1395
+ daemon=True,
1396
+ ).start()
1397
+ return jsonify({"started": True})
1398
+
1399
+
1400
+ @app.route("/api/ci/status", methods=["GET"])
1401
+ def api_ci_status():
1402
+ path = (request.args.get("path") or "").strip()
1403
+ if not path:
1404
+ return jsonify({"error": "path is required"}), 400
1405
+ try:
1406
+ resolved = str(_resolve_project_path(path))
1407
+ except ValueError as e:
1408
+ return jsonify({"error": str(e)}), 404
1409
+ with _ci_lock:
1410
+ return jsonify(dict(_ci_runs.get(resolved)
1411
+ or {"running": False, "done": False}))
1412
+
1413
+
1285
1414
  # ── Sub-projects: linked child .c3 branches (v2.44.0) ─────────────────────
1286
1415
 
1287
1416
  def _sub_manager(parent: str):