code-context-control 2.73.0__tar.gz → 2.75.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 (413) hide show
  1. {code_context_control-2.73.0/code_context_control.egg-info → code_context_control-2.75.0}/PKG-INFO +2 -2
  2. {code_context_control-2.73.0 → code_context_control-2.75.0}/README.md +1 -1
  3. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/c3.py +1 -1
  4. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_access_guard.py +46 -4
  5. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/mcp_server.py +13 -5
  6. code_context_control-2.75.0/cli/tools/edit_verify.py +267 -0
  7. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/edits.py +18 -2
  8. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/project.py +5 -1
  9. {code_context_control-2.73.0 → code_context_control-2.75.0/code_context_control.egg-info}/PKG-INFO +2 -2
  10. {code_context_control-2.73.0 → code_context_control-2.75.0}/code_context_control.egg-info/SOURCES.txt +2 -0
  11. {code_context_control-2.73.0 → code_context_control-2.75.0}/pyproject.toml +1 -1
  12. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/access_guard.py +38 -10
  13. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/notifications.py +22 -1
  14. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/override_requests.py +6 -0
  15. code_context_control-2.75.0/tests/test_edit_verify.py +342 -0
  16. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_hook_access_guard.py +62 -0
  17. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_override_requests.py +125 -0
  18. {code_context_control-2.73.0 → code_context_control-2.75.0}/LICENSE +0 -0
  19. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/__init__.py +0 -0
  20. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/_hook_utils.py +0 -0
  21. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/commands/__init__.py +0 -0
  22. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/commands/common.py +0 -0
  23. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/commands/parser.py +0 -0
  24. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/docs.html +0 -0
  25. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/edits.html +0 -0
  26. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/guide/access.html +0 -0
  27. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/guide/bitbucket.html +0 -0
  28. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/guide/credentials.html +0 -0
  29. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/guide/getting-started.html +0 -0
  30. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/guide/index.html +0 -0
  31. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/guide/masking.html +0 -0
  32. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/guide/oracle.html +0 -0
  33. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/guide/shared.css +0 -0
  34. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/guide/tools.html +0 -0
  35. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/guide/workflow.html +0 -0
  36. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_artifact.py +0 -0
  37. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_auto_snapshot.py +0 -0
  38. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_c3_signal.py +0 -0
  39. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_c3read.py +0 -0
  40. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_dispatch.py +0 -0
  41. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_edit_ledger.py +0 -0
  42. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_edit_unlock.py +0 -0
  43. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_filter.py +0 -0
  44. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_ghost_files.py +0 -0
  45. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_pretool_enforce.py +0 -0
  46. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_prompt_recall.py +0 -0
  47. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_read.py +0 -0
  48. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_session_stats.py +0 -0
  49. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hook_terse_advisor.py +0 -0
  50. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_server.py +0 -0
  51. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/app.js +0 -0
  52. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/add_project.js +0 -0
  53. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/config_editor.js +0 -0
  54. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/drill_artifacts.js +0 -0
  55. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/drill_health.js +0 -0
  56. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/drill_panel.js +0 -0
  57. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/drill_subprojects.js +0 -0
  58. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/drill_tasks.js +0 -0
  59. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/drill_views.js +0 -0
  60. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/global_search.js +0 -0
  61. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/hub_credentials.js +0 -0
  62. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/hub_enforcement.js +0 -0
  63. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/hub_locks.js +0 -0
  64. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/mcp_manager.js +0 -0
  65. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/modals.js +0 -0
  66. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/project_card.js +0 -0
  67. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/project_tree.js +0 -0
  68. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/session_drawer.js +0 -0
  69. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/settings_modal.js +0 -0
  70. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/sidebar.js +0 -0
  71. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/summary_bar.js +0 -0
  72. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/task_board.js +0 -0
  73. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/toasts.js +0 -0
  74. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/components/topbar.js +0 -0
  75. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui/state.js +0 -0
  76. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/hub_ui.html +0 -0
  77. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/mcp_proxy.py +0 -0
  78. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/progress.py +0 -0
  79. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/server.py +0 -0
  80. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/__init__.py +0 -0
  81. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/_grants.py +0 -0
  82. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/_helpers.py +0 -0
  83. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/agent.py +0 -0
  84. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/artifacts.py +0 -0
  85. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/bitbucket.py +0 -0
  86. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/compress.py +0 -0
  87. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/credentials.py +0 -0
  88. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/delegate.py +0 -0
  89. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/edit.py +0 -0
  90. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/federate.py +0 -0
  91. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/filter.py +0 -0
  92. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/impact.py +0 -0
  93. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/jira.py +0 -0
  94. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/locks.py +0 -0
  95. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/memory.py +0 -0
  96. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/override.py +0 -0
  97. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/read.py +0 -0
  98. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/search.py +0 -0
  99. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/session.py +0 -0
  100. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/shell.py +0 -0
  101. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/status.py +0 -0
  102. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/tasks.py +0 -0
  103. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/tools/validate.py +0 -0
  104. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/api.js +0 -0
  105. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/app.js +0 -0
  106. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/components/access.js +0 -0
  107. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/components/bitbucket.js +0 -0
  108. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/components/chat.js +0 -0
  109. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/components/credentials.js +0 -0
  110. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/components/dashboard.js +0 -0
  111. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/components/edits.js +0 -0
  112. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/components/enforcement.js +0 -0
  113. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/components/instructions.js +0 -0
  114. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/components/jira.js +0 -0
  115. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/components/memory.js +0 -0
  116. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/components/sessions.js +0 -0
  117. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/components/settings.js +0 -0
  118. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/components/sidebar.js +0 -0
  119. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/components/tasks.js +0 -0
  120. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/icons.js +0 -0
  121. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/pm_shared.js +0 -0
  122. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/shared.js +0 -0
  123. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui/theme.js +0 -0
  124. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui.html +0 -0
  125. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui_legacy.html +0 -0
  126. {code_context_control-2.73.0 → code_context_control-2.75.0}/cli/ui_nano.html +0 -0
  127. {code_context_control-2.73.0 → code_context_control-2.75.0}/code_context_control.egg-info/dependency_links.txt +0 -0
  128. {code_context_control-2.73.0 → code_context_control-2.75.0}/code_context_control.egg-info/entry_points.txt +0 -0
  129. {code_context_control-2.73.0 → code_context_control-2.75.0}/code_context_control.egg-info/requires.txt +0 -0
  130. {code_context_control-2.73.0 → code_context_control-2.75.0}/code_context_control.egg-info/top_level.txt +0 -0
  131. {code_context_control-2.73.0 → code_context_control-2.75.0}/core/__init__.py +0 -0
  132. {code_context_control-2.73.0 → code_context_control-2.75.0}/core/config.py +0 -0
  133. {code_context_control-2.73.0 → code_context_control-2.75.0}/core/ide.py +0 -0
  134. {code_context_control-2.73.0 → code_context_control-2.75.0}/core/mcp_toml.py +0 -0
  135. {code_context_control-2.73.0 → code_context_control-2.75.0}/core/web_security.py +0 -0
  136. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/__init__.py +0 -0
  137. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/config.py +0 -0
  138. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/mcp_oracle.py +0 -0
  139. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/oracle_server.py +0 -0
  140. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/oracle_ui.html +0 -0
  141. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/__init__.py +0 -0
  142. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/activity_reporter.py +0 -0
  143. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/api_auth.py +0 -0
  144. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/c3_bridge.py +0 -0
  145. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/chat_engine.py +0 -0
  146. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/chat_store.py +0 -0
  147. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/cross_memory.py +0 -0
  148. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/discovery_audit.py +0 -0
  149. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/federated_graph.py +0 -0
  150. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/health_checker.py +0 -0
  151. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/insight_engine.py +0 -0
  152. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/local_session.py +0 -0
  153. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/memory_reader.py +0 -0
  154. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/memory_writer.py +0 -0
  155. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/mobile_api.py +0 -0
  156. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/project_scanner.py +0 -0
  157. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/review_agent.py +0 -0
  158. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/tool_executor.py +0 -0
  159. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/services/tool_registry.py +0 -0
  160. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/activity.js +0 -0
  161. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/agents.js +0 -0
  162. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/app.js +0 -0
  163. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/busy.js +0 -0
  164. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/chat/conversations.js +0 -0
  165. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/chat/input.js +0 -0
  166. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/chat/markdown.js +0 -0
  167. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/chat/send.js +0 -0
  168. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/chat/stream_renderer.js +0 -0
  169. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/chat/toolbar.js +0 -0
  170. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/core.js +0 -0
  171. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/crossgraph.js +0 -0
  172. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/header.js +0 -0
  173. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/insights.js +0 -0
  174. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/projects.js +0 -0
  175. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/qrcode.js +0 -0
  176. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/settings.js +0 -0
  177. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/suggestions.js +0 -0
  178. {code_context_control-2.73.0 → code_context_control-2.75.0}/oracle/ui/theme_tabs.js +0 -0
  179. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/__init__.py +0 -0
  180. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/access_telemetry.py +0 -0
  181. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/activity_log.py +0 -0
  182. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/agent_base.py +0 -0
  183. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/agent_locks.py +0 -0
  184. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/agents.py +0 -0
  185. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/artifact_defs.py +0 -0
  186. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/artifact_store.py +0 -0
  187. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/auto_memory.py +0 -0
  188. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/bench/__init__.py +0 -0
  189. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/bench/external/__init__.py +0 -0
  190. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/bench/external/aider_polyglot.py +0 -0
  191. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/bench/external/swe_bench.py +0 -0
  192. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/benchmark_dashboard.py +0 -0
  193. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/bitbucket_client.py +0 -0
  194. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/bitbucket_credentials.py +0 -0
  195. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/circuit_breaker.py +0 -0
  196. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/claude_md.py +0 -0
  197. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/compressor.py +0 -0
  198. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/context_snapshot.py +0 -0
  199. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/conversation_store.py +0 -0
  200. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/credential_store.py +0 -0
  201. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/doc_index.py +0 -0
  202. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/e2e_benchmark.py +0 -0
  203. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/e2e_evaluator.py +0 -0
  204. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/e2e_tasks.py +0 -0
  205. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/edit_ledger.py +0 -0
  206. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/embedding_index.py +0 -0
  207. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/enforcement_policy.py +0 -0
  208. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/error_reporting.py +0 -0
  209. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/file_memory.py +0 -0
  210. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/git_context.py +0 -0
  211. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/hub_service.py +0 -0
  212. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/indexer.py +0 -0
  213. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/jira_client.py +0 -0
  214. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/jira_cloud.py +0 -0
  215. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/jira_credentials.py +0 -0
  216. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/jira_data_center.py +0 -0
  217. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/jira_links.py +0 -0
  218. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/mask_activation.py +0 -0
  219. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/mask_mirror.py +0 -0
  220. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/mask_presets.py +0 -0
  221. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/memory.py +0 -0
  222. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/memory_consolidator.py +0 -0
  223. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/memory_distiller.py +0 -0
  224. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/memory_graph.py +0 -0
  225. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/memory_grounder.py +0 -0
  226. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/memory_queue.py +0 -0
  227. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/memory_scorer.py +0 -0
  228. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/metrics.py +0 -0
  229. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/ollama_bridge.py +0 -0
  230. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/ollama_client.py +0 -0
  231. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/ollama_credentials.py +0 -0
  232. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/output_filter.py +0 -0
  233. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/override_grants.py +0 -0
  234. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/override_policy.py +0 -0
  235. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/override_wake.py +0 -0
  236. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/parser.py +0 -0
  237. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/project_manager.py +0 -0
  238. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/project_runtime.py +0 -0
  239. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/protocol.py +0 -0
  240. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/proxy_state.py +0 -0
  241. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/repo_map.py +0 -0
  242. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/retention.py +0 -0
  243. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/retrieval_broker.py +0 -0
  244. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/router.py +0 -0
  245. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/runtime.py +0 -0
  246. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/scanner.py +0 -0
  247. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/session_benchmark.py +0 -0
  248. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/session_manager.py +0 -0
  249. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/session_preloader.py +0 -0
  250. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/subprojects.py +0 -0
  251. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/task_store.py +0 -0
  252. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/telemetry.py +0 -0
  253. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/text_index.py +0 -0
  254. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/time_tracker.py +0 -0
  255. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/tool_classifier.py +0 -0
  256. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/transcript_index.py +0 -0
  257. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/validation_cache.py +0 -0
  258. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/vector_store.py +0 -0
  259. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/watcher.py +0 -0
  260. {code_context_control-2.73.0 → code_context_control-2.75.0}/services/win_subprocess.py +0 -0
  261. {code_context_control-2.73.0 → code_context_control-2.75.0}/setup.cfg +0 -0
  262. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_access_builtin_optout.py +0 -0
  263. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_access_guard.py +0 -0
  264. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_access_guard_meta.py +0 -0
  265. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_access_guard_shell_project.py +0 -0
  266. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_access_guard_surfaces.py +0 -0
  267. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_access_guard_wiring.py +0 -0
  268. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_activity_reporter.py +0 -0
  269. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_agent_locks.py +0 -0
  270. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_aider_polyglot.py +0 -0
  271. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_antigravity_transition.py +0 -0
  272. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_artifact_store.py +0 -0
  273. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_artifact_tool.py +0 -0
  274. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_bitbucket_cli_smoke.py +0 -0
  275. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_bitbucket_client.py +0 -0
  276. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_bitbucket_config_fallback.py +0 -0
  277. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_bitbucket_credentials.py +0 -0
  278. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_bitbucket_tool.py +0 -0
  279. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_c3_bridge_project_artifacts.py +0 -0
  280. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_c3_shell.py +0 -0
  281. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_circuit_breaker.py +0 -0
  282. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_claude_md_merge.py +0 -0
  283. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_cli_smoke.py +0 -0
  284. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_compressor_large_file.py +0 -0
  285. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_credential_store.py +0 -0
  286. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_credentials_routes.py +0 -0
  287. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_credentials_tool.py +0 -0
  288. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_creds_cli_smoke.py +0 -0
  289. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_delegate_cascade.py +0 -0
  290. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_delegate_version_probe.py +0 -0
  291. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_e2e_benchmark.py +0 -0
  292. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_edit_ledger_hook.py +0 -0
  293. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_edit_locking.py +0 -0
  294. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_edit_normalization.py +0 -0
  295. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_embedding_index_deadlock.py +0 -0
  296. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_enforcement_flip.py +0 -0
  297. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_enforcement_policy.py +0 -0
  298. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_enforcement_routes.py +0 -0
  299. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_federated_graph.py +0 -0
  300. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_filter_backoff.py +0 -0
  301. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_ghost_files.py +0 -0
  302. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_ghost_generation.py +0 -0
  303. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_git_branch_awareness.py +0 -0
  304. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_hook_dispatch.py +0 -0
  305. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_hook_pretool_enforce.py +0 -0
  306. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_hook_prompt_recall.py +0 -0
  307. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_hook_smoke.py +0 -0
  308. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_hook_state.py +0 -0
  309. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_hook_utf8_stdio.py +0 -0
  310. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_hub_credentials_route.py +0 -0
  311. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_hub_credentials_routes_write.py +0 -0
  312. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_hub_enforcement_routes.py +0 -0
  313. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_hub_inspect_api.py +0 -0
  314. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_hub_locks_routes.py +0 -0
  315. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_hub_server_smoke.py +0 -0
  316. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_hub_subproject_links.py +0 -0
  317. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_ide_workflow_adaptation.py +0 -0
  318. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_init_scan.py +0 -0
  319. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_install_mcp_entrypoint.py +0 -0
  320. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_jira_cli_smoke.py +0 -0
  321. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_jira_client.py +0 -0
  322. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_jira_config_fallback.py +0 -0
  323. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_jira_credentials.py +0 -0
  324. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_jira_links.py +0 -0
  325. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_jira_routes.py +0 -0
  326. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_jira_tool.py +0 -0
  327. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_lazy_store_init.py +0 -0
  328. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_mask_guard.py +0 -0
  329. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_mask_routes.py +0 -0
  330. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_mask_surfaces.py +0 -0
  331. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_mcp_host_guard.py +0 -0
  332. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_mcp_memory_timeout.py +0 -0
  333. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_mcp_server_smoke.py +0 -0
  334. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_mcp_toml.py +0 -0
  335. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_memory_distiller.py +0 -0
  336. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_memory_graph_api.py +0 -0
  337. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_memory_queue.py +0 -0
  338. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_memory_system.py +0 -0
  339. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_mobile_api.py +0 -0
  340. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_mobile_override_routes.py +0 -0
  341. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_mobile_security_api.py +0 -0
  342. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_no_stale_model_pins.py +0 -0
  343. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_notification_dedup.py +0 -0
  344. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_notification_discipline.py +0 -0
  345. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_ollama_credentials.py +0 -0
  346. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_oracle_api_auth.py +0 -0
  347. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_oracle_apikey_api.py +0 -0
  348. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_oracle_c3_bridge.py +0 -0
  349. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_oracle_chat_engine.py +0 -0
  350. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_oracle_chat_store.py +0 -0
  351. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_oracle_discovery_api.py +0 -0
  352. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_oracle_discovery_audit.py +0 -0
  353. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_oracle_local_auth.py +0 -0
  354. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_oracle_ollama_bridge.py +0 -0
  355. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_oracle_scanner_cache.py +0 -0
  356. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_oracle_security_fixes.py +0 -0
  357. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_oracle_session_bootstrap.py +0 -0
  358. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_oracle_subproject_awareness.py +0 -0
  359. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_oracle_ui_bundle.py +0 -0
  360. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_output_filter.py +0 -0
  361. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_override_grants.py +0 -0
  362. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_override_grants_mcp.py +0 -0
  363. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_override_wake.py +0 -0
  364. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_permissions.py +0 -0
  365. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_pm_api.py +0 -0
  366. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_project_manager.py +0 -0
  367. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_project_manager_merge.py +0 -0
  368. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_project_tool.py +0 -0
  369. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_read_coercion.py +0 -0
  370. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_read_edit_parity.py +0 -0
  371. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_related_facts.py +0 -0
  372. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_repo_map.py +0 -0
  373. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_response_boilerplate.py +0 -0
  374. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_retention.py +0 -0
  375. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_review_digest.py +0 -0
  376. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_service_durability.py +0 -0
  377. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_session_benchmark.py +0 -0
  378. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_session_budget.py +0 -0
  379. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_shell_creds.py +0 -0
  380. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_shell_robustness.py +0 -0
  381. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_subproject_exclusion.py +0 -0
  382. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_subproject_federation.py +0 -0
  383. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_subprojects.py +0 -0
  384. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_swe_bench.py +0 -0
  385. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_task_store.py +0 -0
  386. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_task_tool.py +0 -0
  387. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_time_tracker.py +0 -0
  388. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_token_telemetry.py +0 -0
  389. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_tool_registry.py +0 -0
  390. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_upgrade_and_version.py +0 -0
  391. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_validate.py +0 -0
  392. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_vault_write_guard.py +0 -0
  393. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_version_sync.py +0 -0
  394. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_web_security.py +0 -0
  395. {code_context_control-2.73.0 → code_context_control-2.75.0}/tests/test_windows_reliability.py +0 -0
  396. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/__init__.py +0 -0
  397. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/backend.py +0 -0
  398. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/main.py +0 -0
  399. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/screens/__init__.py +0 -0
  400. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/screens/benchmark_view.py +0 -0
  401. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/screens/claudemd_view.py +0 -0
  402. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/screens/compress_view.py +0 -0
  403. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/screens/index_view.py +0 -0
  404. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/screens/init_view.py +0 -0
  405. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/screens/mcp_view.py +0 -0
  406. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/screens/optimize_view.py +0 -0
  407. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/screens/pipe_view.py +0 -0
  408. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/screens/projects_view.py +0 -0
  409. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/screens/search_view.py +0 -0
  410. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/screens/session_view.py +0 -0
  411. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/screens/stats.py +0 -0
  412. {code_context_control-2.73.0 → code_context_control-2.75.0}/tui/screens/ui_view.py +0 -0
  413. {code_context_control-2.73.0 → code_context_control-2.75.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.73.0
3
+ Version: 2.75.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
@@ -204,7 +204,7 @@ C3 exposes **21 tools** as a native MCP server. Your IDE calls them directly:
204
204
  | `c3_locks` | Agent leases — who is working on which file, so two agents don't collide (v2.65.0) |
205
205
  | `c3_delegate` | Offload heavy work to local Ollama / Codex / Gemini |
206
206
  | `c3_agent` | Workflows: `review_changes`, `investigate`, `preflight`, `prepare_context`, `validate_compress` |
207
- | `c3_edits` | Edit-ledger queries, version diffs, restore points, per-branch filter |
207
+ | `c3_edits` | Edit-ledger queries, version diffs, restore points, per-branch filter, `verify` (did a failed `c3_edit` still land?) |
208
208
  | `c3_task` | Per-project PM — tasks, dependencies, milestones, time tracking (v2.53.0) |
209
209
  | `c3_artifacts` | Agent-config version history, diff & restore (v2.46.0) |
210
210
  | `c3_credentials` | Named-secret vault; values never enter model context (v2.58.0) |
@@ -141,7 +141,7 @@ C3 exposes **21 tools** as a native MCP server. Your IDE calls them directly:
141
141
  | `c3_locks` | Agent leases — who is working on which file, so two agents don't collide (v2.65.0) |
142
142
  | `c3_delegate` | Offload heavy work to local Ollama / Codex / Gemini |
143
143
  | `c3_agent` | Workflows: `review_changes`, `investigate`, `preflight`, `prepare_context`, `validate_compress` |
144
- | `c3_edits` | Edit-ledger queries, version diffs, restore points, per-branch filter |
144
+ | `c3_edits` | Edit-ledger queries, version diffs, restore points, per-branch filter, `verify` (did a failed `c3_edit` still land?) |
145
145
  | `c3_task` | Per-project PM — tasks, dependencies, milestones, time tracking (v2.53.0) |
146
146
  | `c3_artifacts` | Agent-config version history, diff & restore (v2.46.0) |
147
147
  | `c3_credentials` | Named-secret vault; values never enter model context (v2.58.0) |
@@ -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.73.0"
95
+ __version__ = "2.75.0"
96
96
 
97
97
 
98
98
  def _compress_file_cli(compressor, path, mode="smart", **kw):
@@ -48,12 +48,49 @@ _IPV6_RE = re.compile(
48
48
  re.IGNORECASE,
49
49
  )
50
50
 
51
+ # Characters that make a token a compound expression rather than a path
52
+ # argument: what Windows forbids in a filename outright (< > " | ? *) plus the
53
+ # bracketing and quoting that marks structure ({} [] () ' ` &).
54
+ #
55
+ # NOT in this set, deliberately: ':' is the thing under test; '$' and '=' are
56
+ # legal in a real path AND in a real ADS spelling — './notes.txt:$DATA' is the
57
+ # canonical default-stream syntax, and dropping it here silently deleted an
58
+ # existing negative control the first time this was written. Every genuinely
59
+ # dangerous shell use of '$' arrives with '(' or '{' anyway, which are covered.
60
+ #
61
+ # This is the general form of the fix #50 attempted with _is_network_token, and
62
+ # the reason that one was not enough: it whitelisted two *shapes* — a token
63
+ # STARTING with a scheme, or a bare IPv6 — so a URL that was not the whole token
64
+ # still tripped, and syntax carrying no URL at all was never covered:
65
+ #
66
+ # echo "see [#1](https://example.com/a/b)" -> token '[#1](https://…' , scheme not at position 0
67
+ # echo '{a:.x,b:("/"+.y)}' -> a jq filter, no URL anywhere
68
+ # python -c "… ['src/a.py','src/b.py'] …" -> a list literal
69
+ #
70
+ # All three contain '/', so the token gate admitted them; all three canonicalize
71
+ # to something with a residual colon, and <ads> is exempt from existence-gating,
72
+ # so each became a hard deny on text that names no file at all.
73
+ _SYNTAX_CHARS = frozenset("{}[]()<>\"'`|&*?")
74
+
51
75
 
52
76
  def _is_network_token(tok: str) -> bool:
53
77
  """True for URLs and IPv6 literals/CIDRs — never for an ADS spelling."""
54
78
  return bool(_SCHEME_RE.match(tok) or _IPV6_RE.match(tok))
55
79
 
56
80
 
81
+ def _looks_like_a_path(tok: str) -> bool:
82
+ """False for tokens that are shell syntax rather than a path argument.
83
+
84
+ Deliberately asymmetric, because the two errors are not equal. Skipping a
85
+ token that WAS a path costs a missed advisory flag in a best-effort scanner
86
+ — the real gates (the Read/Write/Edit path check above, and every c3 tool)
87
+ still refuse it. Flagging a token that was NOT a path costs a hard,
88
+ unappealable deny on a command that touches nothing, which is what #50 was.
89
+ So this errs toward skipping.
90
+ """
91
+ return not (_SYNTAX_CHARS & set(tok))
92
+
93
+
57
94
  def _deny(reason: str) -> dict:
58
95
  return {
59
96
  "hookSpecificOutput": {
@@ -113,9 +150,10 @@ def _scan_shell(cmd: str, base: str):
113
150
  covers (existence-gating keeps regex/pattern arguments like '\\.env'
114
151
  from false-denying), or the evaluator flags the spelling itself.
115
152
 
116
- URLs and IPv6 literals are skipped outright: they trip the ADS spelling
117
- check, which is exempt from existence-gating, so a token naming nothing
118
- on disk would otherwise hard-deny (#50).
153
+ Tokens that are shell syntax rather than path arguments are skipped
154
+ outright, as are URLs and IPv6 literals: all of them trip the ADS spelling
155
+ check, which is exempt from existence-gating, so a token naming nothing on
156
+ disk would otherwise hard-deny (#50).
119
157
  """
120
158
  for raw in _TOKEN_SPLIT.split(cmd)[:_MAX_TOKENS]:
121
159
  tok = raw.strip("\"'`;,()")
@@ -123,7 +161,11 @@ def _scan_shell(cmd: str, base: str):
123
161
  continue
124
162
  if "/" not in tok and "\\" not in tok and not tok.startswith("."):
125
163
  continue
126
- if _is_network_token(tok):
164
+ # Order matters only for readability: a URL is also syntax-free, so
165
+ # either check alone would skip 'https://x'. Both are kept because they
166
+ # answer different questions — "is this a network literal" and "is this
167
+ # a path at all" — and the second is the one that generalizes.
168
+ if not _looks_like_a_path(tok) or _is_network_token(tok):
127
169
  continue
128
170
  if re.match(r"^/[a-z]/", tok): # MSYS /c/foo → C:/foo
129
171
  tok = f"{tok[1]}:{tok[2:]}"
@@ -708,7 +708,9 @@ async def c3_edit(file_path: str, old_string: str = "", new_string: str = "",
708
708
  """EDIT — read+patch+write+log in one step. Primary code-change tool; always prefer over native Edit.
709
709
  old_string: text to replace. new_string: replacement. summary: ledger description.
710
710
  edits: JSON list of {old_string, new_string, summary?} for multi-hunk batch on one file.
711
- Parallel across files. Create new file: non-existent file_path + old_string='' + new_string=<content>."""
711
+ Parallel across files. Create new file: non-existent file_path + old_string='' + new_string=<content>.
712
+ If this call ERRORS OR TIMES OUT, do not retry blind — a failed c3_edit may still have
713
+ written the file. Re-send the same args to c3_edits(action='verify') for a verdict."""
712
714
  path_err = validate_file_path(file_path)
713
715
  if path_err:
714
716
  return f"[c3_edit:error] {path_err}"
@@ -725,10 +727,15 @@ async def c3_edit(file_path: str, old_string: str = "", new_string: str = "",
725
727
  async def c3_edits(action: str, file: str = "", change_type: str = "modified",
726
728
  summary: str = "", lines_changed: str = "", tags: str = "",
727
729
  limit: int = 50, since: str = "", edit_id: str = "",
728
- tag: str = "", branch: str = "", ctx: Context = None) -> str:
730
+ tag: str = "", branch: str = "", old_string: str = "",
731
+ new_string: str = "", edits: str = "", ctx: Context = None) -> str:
729
732
  """EDIT HISTORY — inspect the ledger. Different from c3_edit (which writes); this one reads.
730
- actions: log (append entry), history (recent edits), versions (per-file), stats, tag (mark edit_id).
731
- branch: filter history to edits stamped with a given git branch."""
733
+ actions: log (append entry), history (recent edits), versions (per-file), stats, tag (mark edit_id),
734
+ verify (did an edit land?).
735
+ branch: filter history to edits stamped with a given git branch.
736
+ verify: pass the SAME file/old_string/new_string (or edits) you gave the c3_edit that errored or
737
+ timed out. Answers APPLIED (do not retry) / NOT_APPLIED (safe to retry) / INCONCLUSIVE (read the
738
+ file) — a failed c3_edit may still have written, so retrying blind can apply an edit twice."""
732
739
  svc = _svc(ctx)
733
740
 
734
741
  def finalize(name, args, resp, summ, **kw):
@@ -737,7 +744,8 @@ async def c3_edits(action: str, file: str = "", change_type: str = "modified",
737
744
  from cli.tools.edits import handle_edits
738
745
  return await asyncio.to_thread(handle_edits, action, file, change_type, summary,
739
746
  lines_changed, tags, limit, since, edit_id, tag,
740
- svc, finalize, branch)
747
+ svc, finalize, branch, old_string, new_string,
748
+ edits)
741
749
 
742
750
 
743
751
  @mcp.tool()
@@ -0,0 +1,267 @@
1
+ """`c3_edits(action='verify')` — did that edit land?
2
+
3
+ Written for #74. A `c3_edit` call can fail *after* doing its work: one call in a
4
+ logged session hung until the harness aborted it at the 1800s MCP idle timeout,
5
+ and the write had fully landed; another dropped the MCP connection and had
6
+ written nothing. Both reached the caller as an error, and nothing distinguished
7
+ them.
8
+
9
+ That ambiguity is the defect, separately from whatever causes the stall. The
10
+ retry that is safe after the second incident is a double-apply after the first,
11
+ and the case where that actually corrupts a file is not exotic — any edit whose
12
+ ``new_string`` contains its ``old_string`` (appending a line, wrapping a call)
13
+ matches again on a retry and applies twice. `c3_edit`'s own not-found error is
14
+ what saves the ordinary case, which is luck rather than a property.
15
+
16
+ **The file is the primary evidence; the ledger corroborates.** In that order,
17
+ because they answer different questions: the file says whether the intended text
18
+ is there now, the ledger says whether *this* edit is what put it there. Neither
19
+ alone is enough —
20
+
21
+ - text present, no ledger entry → it may have been there all along;
22
+ - ledger entry, text absent → something wrote and something else overwrote it.
23
+
24
+ So there are three verdicts, and the third one is the honest half:
25
+
26
+ - ``NOT_APPLIED`` — ``new_string`` is absent from the file. Definitive: had the
27
+ edit landed, its own replacement text would be there. Safe to retry.
28
+ - ``APPLIED`` — ``new_string`` is present *and* a ledger entry for this file
29
+ records this exact old/new pair. Definitive: do not retry.
30
+ - ``INCONCLUSIVE`` — everything else, with the specific check that failed named
31
+ in the output. Never collapsed into either of the above; a verifier that
32
+ guesses is worth less than one that says it cannot tell, because the caller
33
+ can always fall back to reading the file.
34
+
35
+ Batch (`edits=`) verification reports per patch, since `c3_edit`'s batch mode
36
+ writes the file once after applying every hunk in memory — so "half written" is
37
+ not a state it can leave behind, but "patch 2 missed while 1 and 3 applied" very
38
+ much is, and that is what the caller needs itemized.
39
+ """
40
+ from __future__ import annotations
41
+
42
+ import json
43
+ from pathlib import Path
44
+
45
+ from cli.tools import _grants
46
+ from services import access_guard
47
+
48
+ VERDICT_APPLIED = "APPLIED"
49
+ VERDICT_NOT_APPLIED = "NOT_APPLIED"
50
+ VERDICT_INCONCLUSIVE = "INCONCLUSIVE"
51
+
52
+ #: How far back to look for a corroborating ledger entry. Generous on purpose:
53
+ #: the failure this exists for is a call that hung for half an hour, so a window
54
+ #: measured in a handful of entries would miss the case it was built for.
55
+ _DEFAULT_LOOKBACK = 200
56
+
57
+ #: The ledger caps stored old/new strings (``_DETAIL_CAP`` in cli/tools/edit.py).
58
+ #: A long ``new_string`` is therefore recorded truncated, and comparing it whole
59
+ #: would report INCONCLUSIVE for every large edit — which is to say, for exactly
60
+ #: the edits most likely to have timed out. Compare on the stored prefix.
61
+ _DETAIL_CAP = 2000
62
+
63
+
64
+ def _rel(file_path: str, project_path: str) -> str:
65
+ """The ledger's spelling of ``file_path``.
66
+
67
+ The caller should be able to paste the same ``file_path`` they gave
68
+ ``c3_edit`` — which is frequently absolute, and which the ledger stores
69
+ project-relative with forward slashes. Making them convert by hand is how a
70
+ verification tool goes unused at the moment it is needed.
71
+ """
72
+ p = Path(file_path)
73
+ if not p.is_absolute():
74
+ p = Path(project_path) / p
75
+ try:
76
+ p = p.resolve()
77
+ except OSError:
78
+ pass
79
+ try:
80
+ return str(p.relative_to(Path(project_path).resolve())).replace("\\", "/")
81
+ except (ValueError, OSError):
82
+ return str(file_path).replace("\\", "/")
83
+
84
+
85
+ def _read(path: Path):
86
+ """File text with EOLs normalized the way ``c3_edit`` normalizes them.
87
+
88
+ Without this, an edit to a CRLF file verifies as NOT_APPLIED: c3_edit
89
+ matches against LF-normalized content, so a multi-line ``new_string`` the
90
+ caller passed never appears verbatim in the bytes on disk.
91
+ """
92
+ try:
93
+ raw = path.read_bytes()
94
+ except OSError:
95
+ return None
96
+ text = raw.decode("utf-8", errors="surrogateescape")
97
+ return text.replace("\r\n", "\n").replace("\r", "\n")
98
+
99
+
100
+ def _pairs_from_ledger(entry: dict) -> list:
101
+ """Every (old, new) pair an entry records — single edits and batches alike."""
102
+ detail = entry.get("detail") or {}
103
+ if not isinstance(detail, dict):
104
+ return []
105
+ patches = detail.get("patches")
106
+ if isinstance(patches, list):
107
+ return [
108
+ (str(p.get("old_string", "")), str(p.get("new_string", "")))
109
+ for p in patches if isinstance(p, dict)
110
+ ]
111
+ if "old_string" in detail or "new_string" in detail:
112
+ return [(str(detail.get("old_string", "")),
113
+ str(detail.get("new_string", "")))]
114
+ return []
115
+
116
+
117
+ def _ledger_match(ledger, rel: str, old: str, new: str, limit: int):
118
+ """The most recent ledger entry recording this exact old/new pair, or None.
119
+
120
+ Compared against the ledger's truncated prefixes, not the full strings — see
121
+ ``_DETAIL_CAP``. Truncation means a *prefix* match is the strongest claim
122
+ available here, so two edits sharing a 2000-character prefix would be
123
+ indistinguishable. That is a real limit, and it is why the file check leads
124
+ and this one only corroborates.
125
+ """
126
+ if ledger is None:
127
+ return None
128
+ try:
129
+ entries = ledger.get_history(file=rel, limit=limit)
130
+ except Exception:
131
+ return None
132
+ want = (old[:_DETAIL_CAP], new[:_DETAIL_CAP])
133
+ for entry in reversed(entries or []):
134
+ for pair in _pairs_from_ledger(entry):
135
+ if pair == want:
136
+ return entry
137
+ return None
138
+
139
+
140
+ def _verify_one(content, old: str, new: str, ledger, rel: str,
141
+ limit: int) -> dict:
142
+ """Verdict for one (old, new) pair. Pure apart from the ledger read."""
143
+ if content is None:
144
+ return {
145
+ "verdict": VERDICT_NOT_APPLIED,
146
+ "why": "the file does not exist or could not be read, so nothing "
147
+ "was written to it",
148
+ "entry": None,
149
+ }
150
+ if not new:
151
+ # A deletion: new_string is empty. "Is the replacement present" has no
152
+ # answer, so the ledger is the only positive evidence available.
153
+ entry = _ledger_match(ledger, rel, old, new, limit)
154
+ if old and old in content:
155
+ return {"verdict": VERDICT_NOT_APPLIED,
156
+ "why": "the text this edit removes is still present",
157
+ "entry": None}
158
+ if entry is not None:
159
+ return {"verdict": VERDICT_APPLIED,
160
+ "why": f"the removed text is gone and ledger entry "
161
+ f"{entry.get('id', '')} records this deletion",
162
+ "entry": entry}
163
+ return {"verdict": VERDICT_INCONCLUSIVE,
164
+ "why": "the text is absent but no ledger entry records this "
165
+ "deletion — it may never have been there",
166
+ "entry": None}
167
+
168
+ if new not in content:
169
+ return {
170
+ "verdict": VERDICT_NOT_APPLIED,
171
+ "why": "new_string is not in the file; had this edit landed, its "
172
+ "own replacement text would be there",
173
+ "entry": None,
174
+ }
175
+
176
+ entry = _ledger_match(ledger, rel, old, new, limit)
177
+ if entry is not None:
178
+ return {
179
+ "verdict": VERDICT_APPLIED,
180
+ "why": f"new_string is present and ledger entry "
181
+ f"{entry.get('id', '')} ({entry.get('timestamp', '')[:19]}) "
182
+ f"records this exact old/new pair",
183
+ "entry": entry,
184
+ }
185
+ return {
186
+ "verdict": VERDICT_INCONCLUSIVE,
187
+ "why": "new_string is present but no ledger entry records this edit — "
188
+ "the text may have been there already, or been written by "
189
+ "something other than c3_edit. Read the file before retrying",
190
+ "entry": None,
191
+ }
192
+
193
+
194
+ def verify(file_path: str, old_string: str, new_string: str, edits: str,
195
+ svc, limit: int = _DEFAULT_LOOKBACK):
196
+ """Verify one edit or a batch. Returns (body, summary) for ``finalize``.
197
+
198
+ Takes the same arguments the original ``c3_edit`` call took, so recovering
199
+ from an aborted edit means re-sending it to a different action rather than
200
+ reconstructing anything.
201
+ """
202
+ if not file_path:
203
+ return "file is required", "missing file"
204
+
205
+ project_path = str(getattr(svc, "project_path", "") or "")
206
+ rel = _rel(file_path, project_path)
207
+ path = Path(file_path)
208
+ if not path.is_absolute():
209
+ path = Path(project_path) / path
210
+
211
+ # Access Guard, read verdict, before a single byte is read. Caught by
212
+ # tests/test_access_guard_meta.py rather than by review — and it was right
213
+ # to catch it. This tool returns a verdict rather than content, which is
214
+ # exactly why it is worth guarding: "does .env contain X" answered one
215
+ # substring at a time is a content read with extra steps.
216
+ denial = access_guard.check(str(path), "read", project_path)
217
+ if denial and not _grants.allow(svc, denial, tool="c3_edits", op="read",
218
+ path=str(path)):
219
+ return (access_guard.refusal(denial, file_path, "read"),
220
+ "access-denied")
221
+
222
+ content = _read(path)
223
+ ledger = getattr(svc, "edit_ledger", None)
224
+
225
+ if edits:
226
+ try:
227
+ edit_list = json.loads(edits) if isinstance(edits, str) else edits
228
+ except json.JSONDecodeError as exc:
229
+ return f"edits must be a valid JSON list: {exc}", "bad edits param"
230
+ if not isinstance(edit_list, list) or not edit_list:
231
+ return "edits must be a non-empty JSON list", "bad edits param"
232
+
233
+ lines = [f"[edits:verify] {rel} — {len(edit_list)} patch(es)"]
234
+ verdicts = []
235
+ for i, patch in enumerate(edit_list):
236
+ if not isinstance(patch, dict):
237
+ lines.append(f" patch[{i}]: INCONCLUSIVE — not an object")
238
+ verdicts.append(VERDICT_INCONCLUSIVE)
239
+ continue
240
+ r = _verify_one(content, str(patch.get("old_string", "")),
241
+ str(patch.get("new_string", "")), ledger, rel, limit)
242
+ verdicts.append(r["verdict"])
243
+ lines.append(f" patch[{i}]: {r['verdict']} — {r['why']}")
244
+ # A batch writes once, so a mixed result means individual hunks did not
245
+ # match — not that the file was left half-written. Say so, rather than
246
+ # letting the caller infer a torn write that cannot happen.
247
+ lines.append(" note: c3_edit's batch mode writes the file once after "
248
+ "applying every hunk in memory, so a mixed result means "
249
+ "some hunks did not match — not a partial write.")
250
+ counts = {v: verdicts.count(v) for v in sorted(set(verdicts))}
251
+ summary = ", ".join(f"{n} {v.lower()}" for v, n in counts.items())
252
+ return "\n".join(lines), f"{rel}: {summary}"
253
+
254
+ if not old_string and not new_string:
255
+ return ("old_string and/or new_string are required — verify answers "
256
+ "'did THIS edit land', which needs the edit"), "missing params"
257
+
258
+ r = _verify_one(content, old_string, new_string, ledger, rel, limit)
259
+ lines = [f"[edits:verify] {rel} — {r['verdict']}", f" {r['why']}"]
260
+ if r["verdict"] == VERDICT_NOT_APPLIED:
261
+ lines.append(" → safe to re-send the original c3_edit call.")
262
+ elif r["verdict"] == VERDICT_APPLIED:
263
+ lines.append(" → do NOT retry; re-sending would either fail to match "
264
+ "or apply a second time.")
265
+ else:
266
+ lines.append(" → read the file before deciding; do not retry blind.")
267
+ return "\n".join(lines), f"{rel}: {r['verdict']}"
@@ -1,11 +1,26 @@
1
1
  """c3_edits — AI-tracked edit ledger: log, query, and version file changes."""
2
2
 
3
+ from cli.tools.edit_verify import verify as _verify
4
+
3
5
 
4
6
  def handle_edits(action: str, file: str, change_type: str, summary: str,
5
7
  lines_changed: str, tags: str, limit: int, since: str,
6
- edit_id: str, tag: str, svc, finalize, branch: str = "") -> str:
8
+ edit_id: str, tag: str, svc, finalize, branch: str = "",
9
+ old_string: str = "", new_string: str = "",
10
+ edits: str = "") -> str:
7
11
  """Route c3_edits actions."""
8
12
  ledger = svc.edit_ledger
13
+
14
+ # verify runs BEFORE the ledger-availability gate. It leads on the file, and
15
+ # the file is readable whether or not the ledger is: answering "did my edit
16
+ # land" with "ledger disabled" would withhold the evidence that matters most
17
+ # at the one moment a caller has no other way to find out (#74).
18
+ if action == "verify":
19
+ body, summ = _verify(file, old_string, new_string, edits, svc,
20
+ limit=limit or 200)
21
+ return finalize("c3_edits", {"action": "verify", "file": file},
22
+ body, summ)
23
+
9
24
  if ledger is None:
10
25
  return finalize("c3_edits", {"action": action}, "Edit ledger not available", "ledger disabled")
11
26
 
@@ -119,5 +134,6 @@ def handle_edits(action: str, file: str, change_type: str, summary: str,
119
134
 
120
135
  else:
121
136
  return finalize("c3_edits", {"action": action},
122
- f"Unknown action: {action}. Use: log, history, versions, stats, tag",
137
+ f"Unknown action: {action}. "
138
+ "Use: log, history, versions, stats, tag, verify",
123
139
  "unknown action")
@@ -337,8 +337,12 @@ def _proxy(action, fsvc, *, query, file_path, symbols, lines, mode, view, top_k,
337
337
  if action == "edits":
338
338
  from cli.tools.edits import handle_edits
339
339
 
340
+ # old_string/new_string/edits are threaded through for edits_action=
341
+ # 'verify'. A cross-project edit that times out is the same problem as a
342
+ # local one, and the proxy carries those params already (#74).
340
343
  return handle_edits(edits_action, file, "", "", "", tags, limit, "", "",
341
- tag, fsvc, _foreign_finalize)
344
+ tag, fsvc, _foreign_finalize, "",
345
+ old_string, new_string, edits)
342
346
  if action == "validate":
343
347
  from cli.tools.validate import handle_validate
344
348
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.73.0
3
+ Version: 2.75.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
@@ -204,7 +204,7 @@ C3 exposes **21 tools** as a native MCP server. Your IDE calls them directly:
204
204
  | `c3_locks` | Agent leases — who is working on which file, so two agents don't collide (v2.65.0) |
205
205
  | `c3_delegate` | Offload heavy work to local Ollama / Codex / Gemini |
206
206
  | `c3_agent` | Workflows: `review_changes`, `investigate`, `preflight`, `prepare_context`, `validate_compress` |
207
- | `c3_edits` | Edit-ledger queries, version diffs, restore points, per-branch filter |
207
+ | `c3_edits` | Edit-ledger queries, version diffs, restore points, per-branch filter, `verify` (did a failed `c3_edit` still land?) |
208
208
  | `c3_task` | Per-project PM — tasks, dependencies, milestones, time tracking (v2.53.0) |
209
209
  | `c3_artifacts` | Agent-config version history, diff & restore (v2.46.0) |
210
210
  | `c3_credentials` | Named-secret vault; values never enter model context (v2.58.0) |
@@ -78,6 +78,7 @@ cli/tools/compress.py
78
78
  cli/tools/credentials.py
79
79
  cli/tools/delegate.py
80
80
  cli/tools/edit.py
81
+ cli/tools/edit_verify.py
81
82
  cli/tools/edits.py
82
83
  cli/tools/federate.py
83
84
  cli/tools/filter.py
@@ -286,6 +287,7 @@ tests/test_e2e_benchmark.py
286
287
  tests/test_edit_ledger_hook.py
287
288
  tests/test_edit_locking.py
288
289
  tests/test_edit_normalization.py
290
+ tests/test_edit_verify.py
289
291
  tests/test_embedding_index_deadlock.py
290
292
  tests/test_enforcement_flip.py
291
293
  tests/test_enforcement_policy.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code-context-control"
7
- version = "2.73.0"
7
+ version = "2.75.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"
@@ -709,11 +709,12 @@ def _override_offer(denial: Denial, path, operation: str, tool: str) -> str:
709
709
  that can never be escalated: an agent must not learn from a refusal that
710
710
  a request surface exists for the credential vault (§6).
711
711
 
712
- Restricted to the hook surface on purpose. The offer promises that a human
713
- 'yes' makes the retry work, and today only the PreToolUse gates consult
714
- grants — the ``c3_*`` content surfaces do not (see §13, phase P2a). An
715
- offer on a surface that would still refuse after approval is worse than
716
- no offer.
712
+ Restricted to the hook surface on purpose see ``refusal``. The rule was
713
+ written when only the PreToolUse gates consulted grants; P2a taught the
714
+ ``c3_*`` content surfaces to consult them too, so extending the offer to
715
+ the MCP surface is now defensible. It is deliberately NOT done here: that
716
+ is a separate, wider change (every c3_* refusal grows a line) and belongs
717
+ in its own review, not smuggled in beside a fix for the branch ordering.
717
718
  """
718
719
  try:
719
720
  from services import override_policy as _op # noqa: PLC0415 — cycle
@@ -738,7 +739,26 @@ def refusal(denial: Denial, path, operation: str, *, surface: str = "mcp",
738
739
  the agent it may ASK (docs/override-requests.md §6). The pinned strings
739
740
  above are unchanged: the append is absent unless the project opted in,
740
741
  which is off by default.
742
+
743
+ The append happens HERE, once, for every kind — not inside a branch. It
744
+ used to live at the tail of the deny branch, which sits below the mask and
745
+ read-only early returns, so those two kinds could never carry an offer on
746
+ any surface even though `rule_class_for_denial` marks both escalatable.
747
+ `access_readonly` was the only layer anyone had turned on, so in practice
748
+ the invitation half of Override Requests had never fired once.
741
749
  """
750
+ body = _refusal_body(denial, path, operation, surface=surface,
751
+ tool=tool, project=project)
752
+ # Hook only, still: the offer promises that a 'yes' makes the retry work,
753
+ # and the proxy surface answers for a DIFFERENT project's policy.
754
+ if surface == "hook":
755
+ return body + _override_offer(denial, path, operation, tool)
756
+ return body
757
+
758
+
759
+ def _refusal_body(denial: Denial, path, operation: str, *, surface: str,
760
+ tool: str, project: str) -> str:
761
+ """The pinned string itself, with no override append."""
742
762
  p, glob, scope = _cap(path), denial.rule, denial.scope
743
763
  if denial.kind == _KIND_MASK:
744
764
  if operation in ("write", "create", "delete"):
@@ -762,12 +782,20 @@ def refusal(denial: Denial, path, operation: str, *, surface: str = "mcp",
762
782
  "`c3 access list` or the Access tab."
763
783
  )
764
784
  if denial.kind == _KIND_READ_ONLY:
785
+ # `{operation}`, not a hardcoded "write". A read-only rule blocks the
786
+ # whole write class, and for a file that does not exist yet the tools
787
+ # call that operation `create`. Saying "write" told the agent to ask
788
+ # the user to approve `op='write'`, the approval was minted for
789
+ # `write`, and the grant matcher — which compares op exactly — then
790
+ # refused the `create` the tool actually attempted. A real approval
791
+ # was spent and silently discarded (§4 near_miss).
765
792
  return (
766
- f"{TAG_READ_ONLY} write denied for {p} by Access Guard rule "
793
+ f"{TAG_READ_ONLY} {operation} denied for {p} by Access Guard rule "
767
794
  f"'{glob}' ({scope} scope). The effective policy is read-only; "
768
- "reads are evaluated separately. Do not retry the write. Mark "
769
- "the affected step blocked and continue with unaffected files; "
770
- "report the skip. Rules: `c3 access list` or the Access tab."
795
+ f"reads are evaluated separately. Do not retry the {operation}. "
796
+ "Mark the affected step blocked and continue with unaffected "
797
+ "files; report the skip. Rules: `c3 access list` or the Access "
798
+ "tab."
771
799
  )
772
800
  if surface == "hook":
773
801
  return (
@@ -776,7 +804,7 @@ def refusal(denial: Denial, path, operation: str, *, surface: str = "mcp",
776
804
  "decision, not a transient error — do not retry through another "
777
805
  "tool or the shell. Mark the affected step blocked and continue "
778
806
  "with unaffected files. Rules: `c3 access list`."
779
- ) + _override_offer(denial, path, operation, tool)
807
+ )
780
808
  if surface == "proxy":
781
809
  return (
782
810
  f"{TAG_DENIED} {operation} denied for {p} through project "
@@ -43,7 +43,8 @@ class NotificationStore:
43
43
  self._collapsed = False
44
44
 
45
45
  def add(self, agent: str, severity: str, title: str, message: str,
46
- ai_enhanced: bool = False, replace_if_unacked: bool = False) -> dict | None:
46
+ ai_enhanced: bool = False, replace_if_unacked: bool = False,
47
+ kind: str = "", ref_id: str = "") -> dict | None:
47
48
  """Append a notification. Dedup: an identical (agent, title, message)
48
49
  that is still unacknowledged collapses into the existing record — its
49
50
  ``count`` is bumped and ``last_seen`` refreshed instead of appending a
@@ -54,6 +55,13 @@ class NotificationStore:
54
55
  already exists, update its message/severity in-place (bumping
55
56
  count/last_seen) instead of appending a new entry. Use for
56
57
  high-frequency agents (budget, index, key-file drift) to prevent pile-up.
58
+ kind: machine-readable class for clients that ROUTE on a tap rather
59
+ than parse the title — 'override' for an approval request. Omitted
60
+ from the record when empty, so every existing producer and every
61
+ stored line is unchanged.
62
+ ref_id: the id of the thing the notification is ABOUT (e.g. the
63
+ override request), which is not the notification's own ``id``.
64
+ A client needs both: ``id`` to acknowledge, ``ref_id`` to navigate.
57
65
  Returns the entry if written/updated, None if deduped.
58
66
  """
59
67
  with self._lock:
@@ -75,6 +83,13 @@ class NotificationStore:
75
83
  existing["last_seen"] = now.isoformat()
76
84
  existing["count"] = _entry_count(existing) + 1
77
85
  existing["ai_enhanced"] = ai_enhanced
86
+ # Refresh the routing fields too: an in-place update
87
+ # keeps the record but the thing it points AT may have
88
+ # changed (a new request id under the same title).
89
+ if kind:
90
+ existing["kind"] = kind
91
+ if ref_id:
92
+ existing["ref_id"] = ref_id
78
93
  self._write_all(entries)
79
94
  return existing
80
95
  # Same notification still pending — collapse into the
@@ -108,6 +123,12 @@ class NotificationStore:
108
123
  "acknowledged": False,
109
124
  "ai_enhanced": ai_enhanced,
110
125
  }
126
+ # Additive only: absent keys rather than empty ones, so a client
127
+ # can test presence and old lines stay byte-identical in shape.
128
+ if kind:
129
+ entry["kind"] = kind
130
+ if ref_id:
131
+ entry["ref_id"] = ref_id
111
132
  with open(self._file, "a", encoding="utf-8") as f:
112
133
  f.write(json.dumps(entry) + "\n")
113
134
  return entry