code-context-control 2.96.1__tar.gz → 2.102.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 (470) hide show
  1. {code_context_control-2.96.1/code_context_control.egg-info → code_context_control-2.102.0}/PKG-INFO +1 -1
  2. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/c3.py +128 -15
  3. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/commands/parser.py +22 -3
  4. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_access_guard.py +133 -9
  5. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_dispatch.py +91 -1
  6. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_pretool_enforce.py +24 -5
  7. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_server.py +207 -0
  8. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/app.js +3 -1
  9. code_context_control-2.102.0/cli/hub_ui/components/hub_access.js +353 -0
  10. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/topbar.js +1 -1
  11. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/mcp_server.py +1 -1
  12. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/server.py +26 -1
  13. code_context_control-2.102.0/cli/tools/_grants.py +103 -0
  14. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/artifacts.py +33 -2
  15. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/edit.py +7 -1
  16. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/override.py +24 -5
  17. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/read.py +7 -1
  18. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/shell.py +107 -2
  19. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/status.py +2 -1
  20. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/components/access.js +63 -6
  21. {code_context_control-2.96.1 → code_context_control-2.102.0/code_context_control.egg-info}/PKG-INFO +1 -1
  22. {code_context_control-2.96.1 → code_context_control-2.102.0}/code_context_control.egg-info/SOURCES.txt +5 -0
  23. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/mobile_api.py +2 -1
  24. {code_context_control-2.96.1 → code_context_control-2.102.0}/pyproject.toml +1 -1
  25. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/access_guard.py +422 -95
  26. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/artifact_store.py +37 -6
  27. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/claude_md.py +22 -1
  28. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/override_grants.py +54 -10
  29. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/override_policy.py +45 -7
  30. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/override_requests.py +36 -4
  31. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/session_manager.py +18 -0
  32. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_access_guard_meta.py +40 -0
  33. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_artifact_tool.py +41 -4
  34. code_context_control-2.102.0/tests/test_builtin_modes.py +358 -0
  35. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_c3_shell.py +77 -0
  36. code_context_control-2.102.0/tests/test_confirm_guard.py +426 -0
  37. code_context_control-2.102.0/tests/test_confirm_guard_wiring.py +482 -0
  38. code_context_control-2.102.0/tests/test_hub_override_routes.py +222 -0
  39. code_context_control-2.102.0/tests/test_ide_workflow_adaptation.py +235 -0
  40. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_override_grants.py +11 -2
  41. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_override_requests.py +14 -0
  42. code_context_control-2.96.1/cli/tools/_grants.py +0 -61
  43. code_context_control-2.96.1/tests/test_ide_workflow_adaptation.py +0 -121
  44. {code_context_control-2.96.1 → code_context_control-2.102.0}/LICENSE +0 -0
  45. {code_context_control-2.96.1 → code_context_control-2.102.0}/README.md +0 -0
  46. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/__init__.py +0 -0
  47. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/_hook_utils.py +0 -0
  48. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/_shell_writes.py +0 -0
  49. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/commands/__init__.py +0 -0
  50. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/commands/common.py +0 -0
  51. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/docs.html +0 -0
  52. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/edits.html +0 -0
  53. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/guide/access.html +0 -0
  54. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/guide/bitbucket.html +0 -0
  55. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/guide/credentials.html +0 -0
  56. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/guide/getting-started.html +0 -0
  57. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/guide/index.html +0 -0
  58. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/guide/masking.html +0 -0
  59. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/guide/oracle.html +0 -0
  60. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/guide/shared.css +0 -0
  61. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/guide/tools.html +0 -0
  62. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/guide/workflow.html +0 -0
  63. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_artifact.py +0 -0
  64. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_auto_snapshot.py +0 -0
  65. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_c3_signal.py +0 -0
  66. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_c3read.py +0 -0
  67. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_edit_ledger.py +0 -0
  68. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_edit_unlock.py +0 -0
  69. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_filter.py +0 -0
  70. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_ghost_files.py +0 -0
  71. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_prompt_recall.py +0 -0
  72. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_read.py +0 -0
  73. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_session_stats.py +0 -0
  74. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hook_terse_advisor.py +0 -0
  75. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/add_project.js +0 -0
  76. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/config_editor.js +0 -0
  77. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/drill_artifacts.js +0 -0
  78. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/drill_health.js +0 -0
  79. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/drill_panel.js +0 -0
  80. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/drill_subprojects.js +0 -0
  81. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/drill_tasks.js +0 -0
  82. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/drill_views.js +0 -0
  83. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/global_search.js +0 -0
  84. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/hub_ci.js +0 -0
  85. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/hub_cred_audit.js +0 -0
  86. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/hub_credentials.js +0 -0
  87. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/hub_enforcement.js +0 -0
  88. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/hub_locks.js +0 -0
  89. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/hub_tokens.js +0 -0
  90. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/mcp_manager.js +0 -0
  91. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/modals.js +0 -0
  92. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/project_card.js +0 -0
  93. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/project_tree.js +0 -0
  94. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/session_drawer.js +0 -0
  95. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/settings_modal.js +0 -0
  96. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/sidebar.js +0 -0
  97. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/summary_bar.js +0 -0
  98. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/task_board.js +0 -0
  99. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/components/toasts.js +0 -0
  100. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui/state.js +0 -0
  101. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/hub_ui.html +0 -0
  102. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/mcp_proxy.py +0 -0
  103. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/progress.py +0 -0
  104. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/__init__.py +0 -0
  105. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/_helpers.py +0 -0
  106. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/agent.py +0 -0
  107. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/bitbucket.py +0 -0
  108. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/ci.py +0 -0
  109. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/compress.py +0 -0
  110. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/credentials.py +0 -0
  111. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/delegate.py +0 -0
  112. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/edit_verify.py +0 -0
  113. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/edits.py +0 -0
  114. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/federate.py +0 -0
  115. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/filter.py +0 -0
  116. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/impact.py +0 -0
  117. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/jira.py +0 -0
  118. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/locks.py +0 -0
  119. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/memory.py +0 -0
  120. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/project.py +0 -0
  121. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/search.py +0 -0
  122. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/session.py +0 -0
  123. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/tasks.py +0 -0
  124. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/tools/validate.py +0 -0
  125. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/api.js +0 -0
  126. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/app.js +0 -0
  127. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/components/bitbucket.js +0 -0
  128. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/components/chat.js +0 -0
  129. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/components/credentials.js +0 -0
  130. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/components/dashboard.js +0 -0
  131. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/components/edits.js +0 -0
  132. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/components/enforcement.js +0 -0
  133. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/components/instructions.js +0 -0
  134. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/components/jira.js +0 -0
  135. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/components/memory.js +0 -0
  136. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/components/sessions.js +0 -0
  137. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/components/settings.js +0 -0
  138. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/components/sidebar.js +0 -0
  139. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/components/tasks.js +0 -0
  140. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/icons.js +0 -0
  141. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/pm_shared.js +0 -0
  142. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/shared.js +0 -0
  143. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui/theme.js +0 -0
  144. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui.html +0 -0
  145. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui_legacy.html +0 -0
  146. {code_context_control-2.96.1 → code_context_control-2.102.0}/cli/ui_nano.html +0 -0
  147. {code_context_control-2.96.1 → code_context_control-2.102.0}/code_context_control.egg-info/dependency_links.txt +0 -0
  148. {code_context_control-2.96.1 → code_context_control-2.102.0}/code_context_control.egg-info/entry_points.txt +0 -0
  149. {code_context_control-2.96.1 → code_context_control-2.102.0}/code_context_control.egg-info/requires.txt +0 -0
  150. {code_context_control-2.96.1 → code_context_control-2.102.0}/code_context_control.egg-info/top_level.txt +0 -0
  151. {code_context_control-2.96.1 → code_context_control-2.102.0}/core/__init__.py +0 -0
  152. {code_context_control-2.96.1 → code_context_control-2.102.0}/core/config.py +0 -0
  153. {code_context_control-2.96.1 → code_context_control-2.102.0}/core/ide.py +0 -0
  154. {code_context_control-2.96.1 → code_context_control-2.102.0}/core/mcp_toml.py +0 -0
  155. {code_context_control-2.96.1 → code_context_control-2.102.0}/core/web_security.py +0 -0
  156. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/__init__.py +0 -0
  157. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/config.py +0 -0
  158. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/mcp_oracle.py +0 -0
  159. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/oracle_server.py +0 -0
  160. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/oracle_ui.html +0 -0
  161. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/__init__.py +0 -0
  162. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/activity_reporter.py +0 -0
  163. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/api_auth.py +0 -0
  164. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/c3_bridge.py +0 -0
  165. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/chat_engine.py +0 -0
  166. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/chat_poll.py +0 -0
  167. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/chat_store.py +0 -0
  168. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/cross_memory.py +0 -0
  169. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/discovery_audit.py +0 -0
  170. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/federated_graph.py +0 -0
  171. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/health_checker.py +0 -0
  172. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/insight_engine.py +0 -0
  173. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/local_session.py +0 -0
  174. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/memory_reader.py +0 -0
  175. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/memory_writer.py +0 -0
  176. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/project_scanner.py +0 -0
  177. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/review_agent.py +0 -0
  178. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/tool_executor.py +0 -0
  179. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/services/tool_registry.py +0 -0
  180. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/activity.js +0 -0
  181. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/agents.js +0 -0
  182. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/app.js +0 -0
  183. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/busy.js +0 -0
  184. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/chat/conversations.js +0 -0
  185. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/chat/input.js +0 -0
  186. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/chat/markdown.js +0 -0
  187. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/chat/send.js +0 -0
  188. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/chat/stream_renderer.js +0 -0
  189. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/chat/toolbar.js +0 -0
  190. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/core.js +0 -0
  191. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/crossgraph.js +0 -0
  192. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/header.js +0 -0
  193. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/insights.js +0 -0
  194. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/projects.js +0 -0
  195. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/qrcode.js +0 -0
  196. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/settings.js +0 -0
  197. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/suggestions.js +0 -0
  198. {code_context_control-2.96.1 → code_context_control-2.102.0}/oracle/ui/theme_tabs.js +0 -0
  199. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/__init__.py +0 -0
  200. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/access_telemetry.py +0 -0
  201. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/activity_log.py +0 -0
  202. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/agent_base.py +0 -0
  203. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/agent_locks.py +0 -0
  204. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/agents.py +0 -0
  205. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/artifact_defs.py +0 -0
  206. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/auto_memory.py +0 -0
  207. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/background_service.py +0 -0
  208. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/bench/__init__.py +0 -0
  209. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/bench/external/__init__.py +0 -0
  210. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/bench/external/aider_polyglot.py +0 -0
  211. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/bench/external/swe_bench.py +0 -0
  212. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/benchmark_dashboard.py +0 -0
  213. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/bitbucket_client.py +0 -0
  214. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/bitbucket_credentials.py +0 -0
  215. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/ci_act.py +0 -0
  216. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/ci_cache.py +0 -0
  217. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/ci_expr.py +0 -0
  218. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/ci_failures.py +0 -0
  219. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/ci_github.py +0 -0
  220. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/ci_impact.py +0 -0
  221. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/ci_intel.py +0 -0
  222. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/ci_runner.py +0 -0
  223. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/ci_workflow.py +0 -0
  224. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/circuit_breaker.py +0 -0
  225. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/compressor.py +0 -0
  226. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/context_snapshot.py +0 -0
  227. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/conversation_store.py +0 -0
  228. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/cred_audit.py +0 -0
  229. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/cred_telemetry.py +0 -0
  230. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/credential_store.py +0 -0
  231. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/doc_index.py +0 -0
  232. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/e2e_benchmark.py +0 -0
  233. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/e2e_evaluator.py +0 -0
  234. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/e2e_tasks.py +0 -0
  235. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/edit_ledger.py +0 -0
  236. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/embedding_index.py +0 -0
  237. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/enforcement_policy.py +0 -0
  238. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/error_reporting.py +0 -0
  239. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/file_memory.py +0 -0
  240. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/git_context.py +0 -0
  241. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/hub_service.py +0 -0
  242. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/indexer.py +0 -0
  243. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/jira_client.py +0 -0
  244. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/jira_cloud.py +0 -0
  245. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/jira_credentials.py +0 -0
  246. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/jira_data_center.py +0 -0
  247. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/jira_links.py +0 -0
  248. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/mask_activation.py +0 -0
  249. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/mask_mirror.py +0 -0
  250. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/mask_presets.py +0 -0
  251. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/memory.py +0 -0
  252. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/memory_consolidator.py +0 -0
  253. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/memory_distiller.py +0 -0
  254. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/memory_graph.py +0 -0
  255. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/memory_grounder.py +0 -0
  256. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/memory_queue.py +0 -0
  257. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/memory_scorer.py +0 -0
  258. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/metrics.py +0 -0
  259. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/notifications.py +0 -0
  260. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/ollama_bridge.py +0 -0
  261. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/ollama_client.py +0 -0
  262. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/ollama_credentials.py +0 -0
  263. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/oracle_service.py +0 -0
  264. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/output_filter.py +0 -0
  265. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/override_wake.py +0 -0
  266. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/parser.py +0 -0
  267. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/project_manager.py +0 -0
  268. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/project_runtime.py +0 -0
  269. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/protocol.py +0 -0
  270. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/proxy_state.py +0 -0
  271. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/repo_map.py +0 -0
  272. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/repo_shape.py +0 -0
  273. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/retention.py +0 -0
  274. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/retrieval_broker.py +0 -0
  275. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/router.py +0 -0
  276. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/runtime.py +0 -0
  277. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/scanner.py +0 -0
  278. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/session_benchmark.py +0 -0
  279. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/session_preloader.py +0 -0
  280. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/subprojects.py +0 -0
  281. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/task_store.py +0 -0
  282. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/telemetry.py +0 -0
  283. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/text_index.py +0 -0
  284. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/time_tracker.py +0 -0
  285. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/tool_classifier.py +0 -0
  286. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/transcript_index.py +0 -0
  287. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/validation_cache.py +0 -0
  288. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/vector_store.py +0 -0
  289. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/watcher.py +0 -0
  290. {code_context_control-2.96.1 → code_context_control-2.102.0}/services/win_subprocess.py +0 -0
  291. {code_context_control-2.96.1 → code_context_control-2.102.0}/setup.cfg +0 -0
  292. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_access_builtin_optout.py +0 -0
  293. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_access_guard.py +0 -0
  294. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_access_guard_shell_project.py +0 -0
  295. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_access_guard_surfaces.py +0 -0
  296. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_access_guard_wiring.py +0 -0
  297. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_activity_reporter.py +0 -0
  298. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_agent_locks.py +0 -0
  299. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_aider_polyglot.py +0 -0
  300. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_antigravity_transition.py +0 -0
  301. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_artifact_store.py +0 -0
  302. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_background_service.py +0 -0
  303. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_bitbucket_cli_smoke.py +0 -0
  304. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_bitbucket_client.py +0 -0
  305. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_bitbucket_config_fallback.py +0 -0
  306. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_bitbucket_credentials.py +0 -0
  307. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_bitbucket_tool.py +0 -0
  308. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_c3_bridge_project_artifacts.py +0 -0
  309. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_chat_poll.py +0 -0
  310. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_ci_act.py +0 -0
  311. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_ci_cache.py +0 -0
  312. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_ci_expr.py +0 -0
  313. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_ci_impact.py +0 -0
  314. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_ci_intel.py +0 -0
  315. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_ci_runner.py +0 -0
  316. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_ci_workflow.py +0 -0
  317. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_circuit_breaker.py +0 -0
  318. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_claude_md_merge.py +0 -0
  319. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_cli_smoke.py +0 -0
  320. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_compressor_large_file.py +0 -0
  321. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_cooccurrence_bounds.py +0 -0
  322. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_cred_audit.py +0 -0
  323. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_cred_telemetry.py +0 -0
  324. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_credential_login_kind.py +0 -0
  325. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_credential_store.py +0 -0
  326. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_credential_ui_parity.py +0 -0
  327. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_credentials_routes.py +0 -0
  328. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_credentials_tool.py +0 -0
  329. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_creds_cli_smoke.py +0 -0
  330. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_delegate_cascade.py +0 -0
  331. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_delegate_version_probe.py +0 -0
  332. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_e2e_benchmark.py +0 -0
  333. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_edit_ledger_deadline.py +0 -0
  334. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_edit_ledger_hook.py +0 -0
  335. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_edit_locking.py +0 -0
  336. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_edit_normalization.py +0 -0
  337. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_edit_verify.py +0 -0
  338. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_embedding_index_deadlock.py +0 -0
  339. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_enforcement_flip.py +0 -0
  340. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_enforcement_policy.py +0 -0
  341. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_enforcement_routes.py +0 -0
  342. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_env_parser.py +0 -0
  343. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_failed_calls_do_not_unlock.py +0 -0
  344. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_federated_graph.py +0 -0
  345. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_filter_backoff.py +0 -0
  346. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_ghost_files.py +0 -0
  347. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_ghost_generation.py +0 -0
  348. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_git_branch_awareness.py +0 -0
  349. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hook_access_guard.py +0 -0
  350. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hook_codex_compat.py +0 -0
  351. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hook_dispatch.py +0 -0
  352. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hook_pretool_enforce.py +0 -0
  353. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hook_prompt_recall.py +0 -0
  354. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hook_smoke.py +0 -0
  355. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hook_state.py +0 -0
  356. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hook_utf8_stdio.py +0 -0
  357. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hub_credentials_route.py +0 -0
  358. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hub_credentials_routes_write.py +0 -0
  359. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hub_enforcement_routes.py +0 -0
  360. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hub_inspect_api.py +0 -0
  361. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hub_locks_routes.py +0 -0
  362. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hub_oracle_service_routes.py +0 -0
  363. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hub_server_smoke.py +0 -0
  364. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_hub_subproject_links.py +0 -0
  365. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_init_clear_preserves_user_content.py +0 -0
  366. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_init_scan.py +0 -0
  367. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_install_mcp_entrypoint.py +0 -0
  368. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_jira_cli_smoke.py +0 -0
  369. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_jira_client.py +0 -0
  370. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_jira_config_fallback.py +0 -0
  371. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_jira_credentials.py +0 -0
  372. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_jira_links.py +0 -0
  373. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_jira_routes.py +0 -0
  374. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_jira_tool.py +0 -0
  375. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_lazy_store_init.py +0 -0
  376. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_mask_guard.py +0 -0
  377. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_mask_routes.py +0 -0
  378. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_mask_surfaces.py +0 -0
  379. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_mcp_host_guard.py +0 -0
  380. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_mcp_memory_timeout.py +0 -0
  381. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_mcp_server_smoke.py +0 -0
  382. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_mcp_toml.py +0 -0
  383. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_memory_distiller.py +0 -0
  384. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_memory_graph_api.py +0 -0
  385. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_memory_queue.py +0 -0
  386. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_memory_system.py +0 -0
  387. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_mobile_api.py +0 -0
  388. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_mobile_extras.py +0 -0
  389. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_mobile_override_routes.py +0 -0
  390. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_mobile_security_api.py +0 -0
  391. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_no_stale_model_pins.py +0 -0
  392. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_notification_dedup.py +0 -0
  393. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_notification_discipline.py +0 -0
  394. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_ollama_credentials.py +0 -0
  395. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_api_auth.py +0 -0
  396. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_apikey_api.py +0 -0
  397. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_c3_bridge.py +0 -0
  398. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_chat_engine.py +0 -0
  399. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_chat_store.py +0 -0
  400. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_discovery_api.py +0 -0
  401. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_discovery_audit.py +0 -0
  402. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_health_probe.py +0 -0
  403. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_local_auth.py +0 -0
  404. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_ollama_bridge.py +0 -0
  405. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_scanner_cache.py +0 -0
  406. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_security_fixes.py +0 -0
  407. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_session_bootstrap.py +0 -0
  408. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_subproject_awareness.py +0 -0
  409. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_oracle_ui_bundle.py +0 -0
  410. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_output_filter.py +0 -0
  411. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_override_grants_mcp.py +0 -0
  412. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_override_wake.py +0 -0
  413. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_permissions.py +0 -0
  414. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_pm_api.py +0 -0
  415. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_project_manager.py +0 -0
  416. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_project_manager_merge.py +0 -0
  417. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_project_tool.py +0 -0
  418. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_read_coercion.py +0 -0
  419. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_read_edit_parity.py +0 -0
  420. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_related_facts.py +0 -0
  421. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_repo_map.py +0 -0
  422. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_repo_shape.py +0 -0
  423. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_response_boilerplate.py +0 -0
  424. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_retention.py +0 -0
  425. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_review_digest.py +0 -0
  426. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_service_durability.py +0 -0
  427. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_session_benchmark.py +0 -0
  428. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_session_budget.py +0 -0
  429. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_shell_creds.py +0 -0
  430. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_shell_robustness.py +0 -0
  431. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_shell_write_ledger.py +0 -0
  432. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_shell_writes.py +0 -0
  433. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_subproject_clear_scope.py +0 -0
  434. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_subproject_depth_consumers.py +0 -0
  435. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_subproject_exclusion.py +0 -0
  436. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_subproject_federation.py +0 -0
  437. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_subproject_linkage_api.py +0 -0
  438. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_subprojects.py +0 -0
  439. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_swe_bench.py +0 -0
  440. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_task_store.py +0 -0
  441. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_task_tool.py +0 -0
  442. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_time_tracker.py +0 -0
  443. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_token_telemetry.py +0 -0
  444. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_token_tracking.py +0 -0
  445. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_tool_registry.py +0 -0
  446. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_ui_jsx_syntax.py +0 -0
  447. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_upgrade_and_version.py +0 -0
  448. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_validate.py +0 -0
  449. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_vault_write_guard.py +0 -0
  450. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_version_sync.py +0 -0
  451. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_web_security.py +0 -0
  452. {code_context_control-2.96.1 → code_context_control-2.102.0}/tests/test_windows_reliability.py +0 -0
  453. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/__init__.py +0 -0
  454. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/backend.py +0 -0
  455. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/main.py +0 -0
  456. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/screens/__init__.py +0 -0
  457. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/screens/benchmark_view.py +0 -0
  458. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/screens/claudemd_view.py +0 -0
  459. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/screens/compress_view.py +0 -0
  460. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/screens/index_view.py +0 -0
  461. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/screens/init_view.py +0 -0
  462. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/screens/mcp_view.py +0 -0
  463. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/screens/optimize_view.py +0 -0
  464. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/screens/pipe_view.py +0 -0
  465. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/screens/projects_view.py +0 -0
  466. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/screens/search_view.py +0 -0
  467. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/screens/session_view.py +0 -0
  468. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/screens/stats.py +0 -0
  469. {code_context_control-2.96.1 → code_context_control-2.102.0}/tui/screens/ui_view.py +0 -0
  470. {code_context_control-2.96.1 → code_context_control-2.102.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.96.1
3
+ Version: 2.102.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
@@ -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.96.1"
95
+ __version__ = "2.102.0"
96
96
 
97
97
 
98
98
  def _compress_file_cli(compressor, path, mode="smart", **kw):
@@ -4212,6 +4212,7 @@ def cmd_pipe(args):
4212
4212
 
4213
4213
 
4214
4214
  from services.claude_md import C3_COMPACT_WORKFLOW as _SHARED_C3_COMPACT_WORKFLOW
4215
+ from services.claude_md import VSCODE_INSTRUCTIONS_FILE
4215
4216
  from services.claude_md import VSCODE_SESSION_INIT as _VSCODE_SESSION_INIT
4216
4217
  from services.claude_md import adapt_workflow_for_ide as _adapt_workflow_for_ide
4217
4218
 
@@ -4433,14 +4434,27 @@ def _toml_section_bool_value(toml_path: Path, section: str, key: str) -> bool |
4433
4434
  return None
4434
4435
 
4435
4436
 
4436
- def _ensure_instruction_workflow(instructions_path: Path, template: str, required_markers: list[str]) -> str:
4437
- """Ensure an instructions file has required C3 workflow markers.
4437
+ def _ensure_instruction_workflow(instructions_path: Path, template: str,
4438
+ required_markers: list[str],
4439
+ project_path: str = "") -> str:
4440
+ """Ensure an instructions file carries the CURRENT C3 workflow.
4438
4441
 
4439
4442
  Returns one of: "written", "updated", "kept".
4443
+
4444
+ Marker PRESENCE used to mean "up to date", which is why this function
4445
+ could report `kept` on a doc generated four releases ago: the markers are
4446
+ stable words like "SEARCH FIRST" and "c3_edit", so they survive every
4447
+ template change. A doc carrying the managed sentinels is now refreshed
4448
+ through ``merge_c3_block`` — the same non-destructive regeneration
4449
+ CLAUDE.md and AGENTS.md get — and `kept` means the block already matches
4450
+ byte for byte (v2.102.0). A marker-less legacy doc keeps the old
4451
+ prepend-and-preserve path, which ``write_c3_instruction_doc`` handles.
4440
4452
  """
4453
+ from services.claude_md import C3_BLOCK_BEGIN, merge_c3_block, wrap_c3_block, write_c3_instruction_doc
4454
+
4441
4455
  if not instructions_path.exists():
4442
- instructions_path.parent.mkdir(parents=True, exist_ok=True)
4443
- instructions_path.write_text(template, encoding="utf-8")
4456
+ write_c3_instruction_doc(instructions_path, template,
4457
+ project_path=project_path or None)
4444
4458
  return "written"
4445
4459
 
4446
4460
  try:
@@ -4448,6 +4462,14 @@ def _ensure_instruction_workflow(instructions_path: Path, template: str, require
4448
4462
  except Exception:
4449
4463
  existing = ""
4450
4464
 
4465
+ # Managed doc → regenerate the block in place, preserving user content.
4466
+ if C3_BLOCK_BEGIN in existing:
4467
+ if merge_c3_block(existing, wrap_c3_block(template)) == existing:
4468
+ return "kept"
4469
+ write_c3_instruction_doc(instructions_path, template,
4470
+ project_path=project_path or None)
4471
+ return "updated"
4472
+
4451
4473
  if all(marker in existing for marker in required_markers):
4452
4474
  return "kept"
4453
4475
 
@@ -4460,7 +4482,8 @@ def _ensure_instruction_workflow(instructions_path: Path, template: str, require
4460
4482
  return "updated"
4461
4483
 
4462
4484
 
4463
- def _ensure_codex_agents_workflow(agents_md_path: Path) -> str:
4485
+ def _ensure_codex_agents_workflow(agents_md_path: Path,
4486
+ project_path: str = "") -> str:
4464
4487
  """Ensure AGENTS.md contains the mandatory C3 workflow for Codex sessions."""
4465
4488
  required_markers = [
4466
4489
  "C3 Tools",
@@ -4474,10 +4497,12 @@ def _ensure_codex_agents_workflow(agents_md_path: Path) -> str:
4474
4497
  "c3_memory",
4475
4498
  "c3_filter",
4476
4499
  ]
4477
- return _ensure_instruction_workflow(agents_md_path, _AGENTS_MD_CONTENT, required_markers)
4500
+ return _ensure_instruction_workflow(agents_md_path, _AGENTS_MD_CONTENT,
4501
+ required_markers, project_path)
4478
4502
 
4479
4503
 
4480
- def _ensure_vscode_instructions_workflow(instructions_path: Path) -> str:
4504
+ def _ensure_vscode_instructions_workflow(instructions_path: Path,
4505
+ project_path: str = "") -> str:
4481
4506
  """Ensure VS Code Copilot instructions include the latest C3 workflow markers."""
4482
4507
  required_markers = [
4483
4508
  "C3 Tools",
@@ -4491,7 +4516,9 @@ def _ensure_vscode_instructions_workflow(instructions_path: Path) -> str:
4491
4516
  "c3_memory",
4492
4517
  "c3_filter",
4493
4518
  ]
4494
- return _ensure_instruction_workflow(instructions_path, _COPILOT_INSTRUCTIONS_CONTENT, required_markers)
4519
+ return _ensure_instruction_workflow(instructions_path,
4520
+ _COPILOT_INSTRUCTIONS_CONTENT,
4521
+ required_markers, project_path)
4495
4522
 
4496
4523
 
4497
4524
  def _safe_read_json(path: Path, label: str = "") -> dict:
@@ -4957,6 +4984,7 @@ back to native tools as the task progresses.
4957
4984
  - **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.
4958
4985
  - **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`.
4959
4986
  - **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.
4987
+ - **Confirm holds** (v2.97.0+): `[c3-access:confirm]` is a PAUSE, not a block — a human must approve this exact write. Since v2.100.0 the agent-config files (`.mcp.json`, instruction docs, `.claude` hook/skill/agent/command bodies) hold by default in EVERY C3 project, reads stay open. Read the S8 tail: it names the filed request (`c3_override(action='wait', request_id='...', timeout_s=180)`, then retry the same call once if approved), or names a surface that files one, or gives a reason to obey. Never retry before a decision and never route around a hold via the shell.
4960
4988
 
4961
4989
  ## Self-Check
4962
4990
  If you haven't called a c3_* tool in several turns during active development, re-engage
@@ -5034,10 +5062,20 @@ def _instruction_documents_for_project() -> list[tuple[str, str]]:
5034
5062
 
5035
5063
  Used by uninstall/cleanup paths, so it must keep listing legacy docs
5036
5064
  (GEMINI.md — profile removed in v2.52; empty template, never generated).
5065
+
5066
+ ``.github/copilot-instructions.md`` was absent from this list until
5067
+ v2.102.0, and that absence was the whole bug: the managed-block sync
5068
+ that regenerates CLAUDE.md and AGENTS.md on every install-mcp never
5069
+ touched it, while ``_ensure_vscode_instructions_workflow`` reported
5070
+ `kept` on marker presence alone. In a vscode-primary project the third
5071
+ git-tracked instruction doc silently drifted a release behind on every
5072
+ template change — v2.100.0's agent-config confirm tier reached the
5073
+ Claude Code and Codex docs and never reached Copilot's.
5037
5074
  """
5038
5075
  return [
5039
5076
  ("CLAUDE.md", _CLAUDE_MD_CONTENT),
5040
5077
  ("AGENTS.md", _AGENTS_MD_CONTENT),
5078
+ (VSCODE_INSTRUCTIONS_FILE, _COPILOT_INSTRUCTIONS_CONTENT),
5041
5079
  ("GEMINI.md", ""),
5042
5080
  ]
5043
5081
 
@@ -5692,7 +5730,8 @@ def cmd_install_mcp(args):
5692
5730
  if profile.name == "vscode":
5693
5731
  # 1. Ensure copilot-instructions.md has the latest C3 workflow markers
5694
5732
  instructions_path = target / ".github" / "copilot-instructions.md"
5695
- vs_state = _ensure_vscode_instructions_workflow(instructions_path)
5733
+ vs_state = _ensure_vscode_instructions_workflow(instructions_path,
5734
+ str(target))
5696
5735
  if vs_state == "written":
5697
5736
  print(f"Wrote {instructions_path}")
5698
5737
  elif vs_state == "updated":
@@ -5731,7 +5770,8 @@ def cmd_install_mcp(args):
5731
5770
  # ── Codex AGENTS.md enforcement file ──────────────────────
5732
5771
  if profile.name == "codex":
5733
5772
  agents_md_path = target / "AGENTS.md"
5734
- agents_state = _ensure_codex_agents_workflow(agents_md_path)
5773
+ agents_state = _ensure_codex_agents_workflow(agents_md_path,
5774
+ str(target))
5735
5775
  if agents_state == "written":
5736
5776
  print(f"Wrote {agents_md_path}")
5737
5777
  elif agents_state == "updated":
@@ -7266,7 +7306,8 @@ def _access_cmd_list(args, project_path: str) -> None:
7266
7306
  "project": ".c3/config.json"}
7267
7307
  for scope in ("builtin", "global", "project"):
7268
7308
  sec = scopes.get(scope) or {}
7269
- rules = [(k, g) for k in ("deny", "read_only") for g in sec.get(k, [])]
7309
+ rules = [(k, g) for k in ("deny", "read_only", "confirm")
7310
+ for g in sec.get(k, [])]
7270
7311
  masks = sec.get("mask") or []
7271
7312
  print(f"[{scope}] ({notes[scope]}) — {len(rules) + len(masks)} rule(s)")
7272
7313
  for kind, glob in rules:
@@ -7286,12 +7327,22 @@ def _access_cmd_list(args, project_path: str) -> None:
7286
7327
  for glob in off:
7287
7328
  print(f" {'off':<10} {glob}")
7288
7329
  print(" re-enable: c3 access builtin enable <glob>")
7330
+ moded = {g: m for g, m in (sec.get("modes") or {}).items()
7331
+ if m not in ("default", "allow")}
7332
+ if moded:
7333
+ print(" MODE set by you (departs from the shipped default):")
7334
+ for glob, m in sorted(moded.items()):
7335
+ print(f" {m:<10} {glob}")
7336
+ print(" reset: c3 access builtin mode <glob> default")
7289
7337
  can = [g for g in (sec.get("disableable") or [])
7290
- if access_guard._norm_builtin(g) not in set(off)]
7338
+ if access_guard._norm_builtin(g) not in set(off)
7339
+ and access_guard._norm_builtin(g) not in moded]
7291
7340
  if can:
7292
7341
  print(" disableable (currently on): "
7293
7342
  + ", ".join(can))
7294
- print(" disable: c3 access builtin disable <glob>")
7343
+ print(" disable: c3 access builtin disable <glob> · "
7344
+ "granular: c3 access builtin mode <glob> "
7345
+ "{deny,confirm,allow}")
7295
7346
  if sec.get("absolute"):
7296
7347
  print(" absolute (credential vault, cannot be disabled): "
7297
7348
  + ", ".join(sec["absolute"]))
@@ -7318,12 +7369,17 @@ def _access_cmd_builtin(args, project_path: str) -> None:
7318
7369
 
7319
7370
  action = getattr(args, "builtin_cmd", None)
7320
7371
  glob = getattr(args, "glob", None)
7321
- if action not in ("disable", "enable") or not glob:
7372
+ if action not in ("disable", "enable", "mode") or not glob:
7322
7373
  print("Usage: c3 access builtin {disable,enable} <glob>")
7374
+ print(" c3 access builtin mode <glob> {deny,confirm,allow,default}")
7323
7375
  print(" disableable: "
7324
7376
  + ", ".join(access_guard.DISABLEABLE_BUILTINS))
7325
7377
  return
7326
7378
 
7379
+ if action == "mode":
7380
+ _access_cmd_builtin_mode(args, glob, project_path)
7381
+ return
7382
+
7327
7383
  disabling = action == "disable"
7328
7384
  if disabling and not getattr(args, "yes", False):
7329
7385
  print(f"About to STOP enforcing the builtin guard on: {glob}")
@@ -7359,6 +7415,63 @@ def _access_cmd_builtin(args, project_path: str) -> None:
7359
7415
  "builtin is enforced anyway (config entry removed).")
7360
7416
 
7361
7417
 
7418
+ _BUILTIN_MODE_HELP = {
7419
+ "deny": "full block — read, write and enumeration all refuse (tightens "
7420
+ "a write-only guard)",
7421
+ "confirm": "each governed operation pauses and files an approval request "
7422
+ "you answer from the Hub, phone, or `c3 override`",
7423
+ "allow": "the guard stops enforcing entirely (same as disable)",
7424
+ "default": "the shipped behaviour",
7425
+ }
7426
+
7427
+
7428
+ def _access_cmd_builtin_mode(args, glob: str, project_path: str) -> None:
7429
+ """`c3 access builtin mode <glob> <mode>` — granular builtin control.
7430
+
7431
+ `deny` and `default` never prompt (tightening / reset). `confirm` and
7432
+ `allow` weaken the guard, so they get the same typed-glob beat of
7433
+ friction as `builtin disable`, unless --yes.
7434
+ """
7435
+ from services import access_guard
7436
+
7437
+ mode = getattr(args, "mode", None)
7438
+ if mode in ("confirm", "allow") and not getattr(args, "yes", False):
7439
+ widening = ("STOP enforcing" if mode == "allow"
7440
+ else "PAUSE (not block) and ask you to approve")
7441
+ print(f"About to make the builtin guard on {glob} {widening}.")
7442
+ if mode == "allow":
7443
+ print(" While off, any agent with C3 tools can touch those paths.")
7444
+ else:
7445
+ print(" Each hit files an Override Request; one tap approves one "
7446
+ "single-use retry.")
7447
+ if glob.endswith("settings*.json") or ".claude" in glob:
7448
+ print(" This guards the file that configures the agent itself — "
7449
+ "hooks, permissions, model.")
7450
+ reply = input("Type the glob again to confirm: ").strip()
7451
+ if reply != glob:
7452
+ print("[abort] Not confirmed; nothing changed.")
7453
+ return
7454
+
7455
+ try:
7456
+ result = access_guard.set_builtin_mode(glob, mode)
7457
+ except ValueError as exc:
7458
+ print(f"[error] {exc}")
7459
+ return
7460
+
7461
+ _access_audit(f"builtin_mode_{result['mode']}", result["glob"],
7462
+ "builtin", "global", project_path)
7463
+ if not result["changed"]:
7464
+ print(f"[=] {result['glob']} — already {result['mode']}")
7465
+ return
7466
+ print(f"[OK] Builtin '{result['glob']}' mode = {result['mode']} "
7467
+ f"(global scope): {_BUILTIN_MODE_HELP[result['mode']]}.")
7468
+ if result["mode"] != "default":
7469
+ print(f" Reset with: c3 access builtin mode {result['glob']} default")
7470
+ if not result["attested"]:
7471
+ print(" [warn] keyring attestation could not be cleared; the "
7472
+ "default is enforced anyway (config entry removed).")
7473
+
7474
+
7362
7475
  def _access_cmd_add(args, project_path: str) -> None:
7363
7476
  from services import access_guard
7364
7477
 
@@ -751,15 +751,19 @@ def build_parser(version: str, parse_cli_ide_arg):
751
751
 
752
752
  ac_add = access_subs.add_parser("add", help="Add a rule to the project (or --global) scope")
753
753
  ac_add.add_argument("glob", help="POSIX glob (** crosses directories), e.g. 'secrets/**' or '*.pem'")
754
- ac_add.add_argument("--kind", choices=["deny", "read_only"], required=True,
755
- help="deny = no read/write/enumerate; read_only = no write")
754
+ ac_add.add_argument("--kind", choices=["deny", "read_only", "confirm"],
755
+ required=True,
756
+ help="deny = no read/write/enumerate; read_only = no "
757
+ "write; confirm = writes pause for your approval "
758
+ "(reads unaffected)")
756
759
  ac_add.add_argument("--global", dest="use_global", action="store_true",
757
760
  help="Store in the global scope (~/.c3) so every C3 project enforces it")
758
761
  ac_add.add_argument("--path", dest="project_path", default=".", help="Project directory (default: current)")
759
762
 
760
763
  ac_remove = access_subs.add_parser("remove", help="Remove a rule from the project (or --global) scope")
761
764
  ac_remove.add_argument("glob", help="The stored glob to remove (case-insensitive match)")
762
- ac_remove.add_argument("--kind", choices=["deny", "read_only"], required=True,
765
+ ac_remove.add_argument("--kind", choices=["deny", "read_only", "confirm"],
766
+ required=True,
763
767
  help="Which rule list the glob lives in")
764
768
  ac_remove.add_argument("--global", dest="use_global", action="store_true",
765
769
  help="Remove from the global scope (~/.c3)")
@@ -805,6 +809,21 @@ def build_parser(version: str, parse_cli_ide_arg):
805
809
  _p.add_argument("--yes", action="store_true",
806
810
  help="Skip the typed confirmation (scripts/CI)")
807
811
 
812
+ # Granular per-builtin mode (docs/confirm-guard.md §7): deny tightens a
813
+ # write-deny to a full deny, confirm turns the block into a pause that
814
+ # auto-files an approval request, allow switches it off (same two-key
815
+ # construction as disable), default restores the shipped behaviour.
816
+ bm = builtin_subs.add_parser(
817
+ "mode", help="Set a built-in guard to deny | confirm | allow | default")
818
+ bm.add_argument("glob", help="One of: **/.env*, **/.c3/**, **/.claude/settings*.json, **/.git/**")
819
+ bm.add_argument("mode", choices=["deny", "confirm", "allow", "default"],
820
+ help="deny = full block (tighten); confirm = pause for your "
821
+ "approval; allow = off (two-key); default = shipped behaviour")
822
+ bm.add_argument("--path", dest="project_path", default=".",
823
+ help="Project directory used for the audit log (default: current)")
824
+ bm.add_argument("--yes", action="store_true",
825
+ help="Skip the typed confirmation (scripts/CI)")
826
+
808
827
  # ── Mask Guard (v2.63.0, docs/mask-guard.md) ────────────────────────
809
828
  # Masking exposes a path but transforms what the agent sees. Rules are
810
829
  # human-only, like the rest of Access Guard.
@@ -6,9 +6,11 @@ dispatcher's deny-beats-allow merge makes a deny here final. hook_dispatch
6
6
  treats this module as _FAIL_CLOSED: if it fails to import or raises, write-
7
7
  class native tools are denied instead of sailing through.
8
8
 
9
- Bash coverage is a best-effort, existence-gated token scan — advisory by
10
- design (docs/access-guard.md §3/§6); it catches an agent naming a denied
11
- path, not an adversary hiding one.
9
+ Bash coverage is a best-effort token scan — advisory by design
10
+ (docs/access-guard.md §3/§6); it catches an agent naming a denied path, not
11
+ an adversary hiding one. Two passes: every path-shaped token as a READ
12
+ (existence-gated), then the command's WRITE targets as writes (v2.102.0), so
13
+ a redirect into a confirm-held or read-only file holds like any other write.
12
14
  """
13
15
  import os
14
16
  import re
@@ -29,6 +31,16 @@ try:
29
31
  except Exception: # pragma: no cover — telemetry is never load-bearing
30
32
  access_telemetry = None
31
33
 
34
+ # The same write-target extractor the edit ledger uses after the fact. Its
35
+ # absence degrades the shell branch to the read-only scan, never to a crash.
36
+ try:
37
+ from _shell_writes import shell_write_targets
38
+ except Exception: # pragma: no cover — package-style import (dispatcher, tests)
39
+ try:
40
+ from cli._shell_writes import shell_write_targets
41
+ except Exception:
42
+ shell_write_targets = None
43
+
32
44
  _WRITE_TOOLS = {"Edit", "Write", "MultiEdit", "NotebookEdit"}
33
45
  _READ_TOOLS = {"Read", "SearchText"}
34
46
  _SEARCH_TOOLS = {"Grep", "Glob", "FindFiles"}
@@ -206,21 +218,60 @@ def _record(denial, tool: str, operation: str, path: str,
206
218
 
207
219
 
208
220
  def _override_allows(base: str, denial, *, tool: str, op: str, path: str,
209
- session_id: str):
221
+ session_id: str, peek: bool = False):
210
222
  """A live override grant covering this exact call, or None (spec §5).
211
223
 
212
224
  Lazy import and policy-before-grants ordering keep the hot path free: a
213
225
  project that never turned overrides on pays one dict lookup and never
214
226
  opens the grants file. Any failure returns None — the denial stands.
227
+
228
+ ``peek`` looks without consuming; see ``_granted`` for why.
215
229
  """
216
230
  try:
217
231
  from services import override_grants as og # noqa: PLC0415 — lazy
218
232
  return og.gate_access(base, denial, tool=tool, op=op, path=path,
219
- session_id=session_id)
233
+ session_id=session_id, peek=peek)
220
234
  except Exception:
221
235
  return None # fail closed: no grant, ordinary denial
222
236
 
223
237
 
238
+ def _granted(line: str, defer: bool, consume) -> dict:
239
+ """The allow-by-grant output.
240
+
241
+ Under the dispatcher (``defer`` True) the use is NOT burned here: the
242
+ provisional line goes out with an ``_on_allow`` callable, and
243
+ hook_dispatch runs it only once every PreToolUse sub-hook has voted
244
+ allow. Before v2.102.0 this hook consumed first and a strict-mode
245
+ discipline deny from hook_pretool_enforce then won the merge — grant
246
+ spent, nothing written, and the user asked twice for one edit. Run
247
+ standalone (``defer`` False) the use was already consumed by the peek-
248
+ less lookup, so the line is final.
249
+ """
250
+ out = {"additionalContext": line}
251
+ if defer:
252
+ out["_on_allow"] = consume
253
+ return out
254
+
255
+
256
+ def _confirm_request(base: str, denial, *, tool: str, op: str, path: str,
257
+ session_id: str) -> tuple:
258
+ """Auto-file an Override Request for a ``confirm`` denial (S8).
259
+
260
+ ``(request_id, note)`` — one of them is always ''. Lazy import, never
261
+ raises: a broken request store degrades to S8's "could not be filed"
262
+ tail, and the hold stands either way (docs/confirm-guard.md §3).
263
+ """
264
+ try:
265
+ from services import override_requests as orq # noqa: PLC0415 — lazy
266
+ row, reason = orq.auto_file(base, denial=denial, tool=tool, op=op,
267
+ path=path, session_id=session_id)
268
+ if row:
269
+ return str(row.get("id") or ""), ""
270
+ return "", reason
271
+ except Exception as exc:
272
+ return "", f"request store unavailable ({exc})"
273
+
274
+
224
275
  def _target(tool_input: dict) -> str:
225
276
  return str(
226
277
  tool_input.get("file_path")
@@ -330,7 +381,68 @@ def _scan_shell(cmd: str, base: str):
330
381
  return None, ""
331
382
 
332
383
 
333
- def run(payload: dict, project_path: Path | None = None) -> dict | None:
384
+ def _scan_shell_writes(cmd: str, base: str, session_id: str,
385
+ defer: bool) -> dict | None:
386
+ """WRITE-class verdict for the files a shell command writes (v2.102.0).
387
+
388
+ ``_scan_shell`` evaluates every token as a READ. Confirm holds, read_only
389
+ rules and the builtin write-denies are all write-class, so ``cat >>
390
+ CLAUDE.md``, ``sed -i`` on .mcp.json or a heredoc into a hook body sailed
391
+ through with no hold — the one bypass docs/confirm-guard.md could only
392
+ ask the agent not to use. Targets come from the extractor the edit
393
+ ledger already trusts after the fact (cli/_shell_writes), with the same
394
+ best-effort caveat: a missed target costs a missed hold, a false one
395
+ costs a hold the human clears in one tap. Synthetic spelling denials are
396
+ skipped exactly as the read scan skips them (a token that is not a path
397
+ must not veto the command); the corrupt-config deny-all still refuses.
398
+
399
+ Grant identity is ``tool="Bash", op="write", path=<target>`` — bound to
400
+ the file, not the command text, the same stated limitation as the
401
+ shell_warn layer. Several held targets need several grants; the command
402
+ runs only when every one is covered, and the uses are burned together
403
+ (or not at all) by the dispatcher's settle step.
404
+ """
405
+ if shell_write_targets is None:
406
+ return None
407
+ lines, consumers = [], []
408
+ for target in shell_write_targets(cmd, base):
409
+ denial = ag.check(target, "write", base)
410
+ if denial is None:
411
+ continue
412
+ if denial.rule.startswith("<") and denial.rule != "<corrupt-config>":
413
+ continue
414
+ granted = _override_allows(base, denial, tool="Bash", op="write",
415
+ path=target, session_id=session_id,
416
+ peek=defer)
417
+ if granted:
418
+ lines.append(granted)
419
+ if defer:
420
+ consumers.append(lambda t=target, d=denial: _override_allows(
421
+ base, d, tool="Bash", op="write", path=t,
422
+ session_id=session_id))
423
+ continue
424
+ _record(denial, "Bash", "write", target, base, session_id)
425
+ if denial.kind == "confirm":
426
+ rid, note = _confirm_request(base, denial, tool="Bash", op="write",
427
+ path=target, session_id=session_id)
428
+ return _deny(ag.refusal(denial, target, "write", surface="hook",
429
+ tool="Bash", request_id=rid,
430
+ request_note=note)
431
+ + " (best-effort shell scan)")
432
+ return _deny(ag.refusal(denial, target, "write", surface="hook",
433
+ tool="Bash") + " (best-effort shell scan)")
434
+ if not lines:
435
+ return None
436
+
437
+ def _settle():
438
+ got = [fn() for fn in consumers]
439
+ return "\n".join(got) if all(got) else None
440
+
441
+ return _granted("\n".join(lines), bool(consumers), _settle)
442
+
443
+
444
+ def run(payload: dict, project_path: Path | None = None,
445
+ defer_consume: bool = False) -> dict | None:
334
446
  tool = normalize_tool_name(payload.get("tool_name", ""))
335
447
  tool_input = payload.get("tool_input", {}) or {}
336
448
  base = str(project_path if project_path is not None else Path.cwd())
@@ -344,10 +456,22 @@ def run(payload: dict, project_path: Path | None = None) -> dict | None:
344
456
  denial = ag.check(fp, op, base)
345
457
  if denial:
346
458
  granted = _override_allows(base, denial, tool=tool, op=op,
347
- path=fp, session_id=session_id)
459
+ path=fp, session_id=session_id,
460
+ peek=defer_consume)
348
461
  if granted:
349
- return {"additionalContext": granted}
462
+ return _granted(granted, defer_consume,
463
+ lambda: _override_allows(
464
+ base, denial, tool=tool, op=op, path=fp,
465
+ session_id=session_id))
350
466
  _record(denial, tool, op, fp, base, session_id)
467
+ if denial.kind == "confirm":
468
+ # A confirm hold files its own request (grants were consulted
469
+ # above, so an approved retry lands in the granted branch).
470
+ rid, note = _confirm_request(base, denial, tool=tool, op=op,
471
+ path=fp, session_id=session_id)
472
+ return _deny(ag.refusal(denial, fp, op, surface="hook",
473
+ tool=tool, request_id=rid,
474
+ request_note=note))
351
475
  return _deny(ag.refusal(denial, fp, op, surface="hook", tool=tool))
352
476
  return None
353
477
 
@@ -374,6 +498,6 @@ def run(payload: dict, project_path: Path | None = None) -> dict | None:
374
498
  return _deny(ag.refusal(denial, tok, "read",
375
499
  surface="hook", tool=tool)
376
500
  + " (best-effort shell scan)")
377
- return None
501
+ return _scan_shell_writes(cmd, base, session_id, defer_consume)
378
502
 
379
503
  return None
@@ -36,6 +36,7 @@ emit a short "[c3:hook-error] ..." additionalContext warning so enforcement
36
36
  cannot silently stop enforcing.
37
37
  """
38
38
  import importlib
39
+ import inspect
39
40
  import json
40
41
  import sys
41
42
  from pathlib import Path
@@ -166,6 +167,16 @@ def _routes(event: str, raw_tool: str, norm_tool: str, host: str = HOST_CLAUDE):
166
167
 
167
168
  _RUN_CACHE: dict = {}
168
169
 
170
+ # PreToolUse sub-hooks that consult override grants. Under the dispatcher
171
+ # they are asked to DEFER consumption (`defer_consume=True`): each returns a
172
+ # provisional allow carrying an `_on_allow` callable, and `_settle_grants`
173
+ # runs those only once every sub-hook has voted allow. Before v2.102.0 the
174
+ # access guard consumed a grant first and a strict-mode discipline deny from
175
+ # the next sub-hook then won the merge — grant spent, nothing written, the
176
+ # user asked twice for one edit. The same hole ran the other way for a
177
+ # discipline grant followed by a policy deny.
178
+ _DEFER_CONSUME = {"hook_access_guard", "hook_pretool_enforce"}
179
+
169
180
  # Modules whose failure must DENY rather than fall through (fail closed).
170
181
  # Scoped to write-class tools + shell: a broken guard must not let mutations
171
182
  # sail through, but read-class fail-open avoids bricking whole sessions.
@@ -189,6 +200,77 @@ def _fail_closed_deny(module_name: str, err: str) -> dict:
189
200
  }
190
201
 
191
202
 
203
+ def _is_deny(out) -> bool:
204
+ hso = out.get("hookSpecificOutput") if isinstance(out, dict) else None
205
+ return isinstance(hso, dict) and hso.get("permissionDecision") == "deny"
206
+
207
+
208
+ def _call_run(run_fn, module_name: str, event: str, payload: dict,
209
+ project_path):
210
+ """Invoke a sub-hook, asking the grant-aware ones to defer consumption.
211
+
212
+ Signature-checked rather than assumed: tests stub these modules with
213
+ two-argument lambdas, and a legacy single-file hook must keep working.
214
+ """
215
+ if event == "pretool" and module_name in _DEFER_CONSUME:
216
+ try:
217
+ accepts = "defer_consume" in inspect.signature(run_fn).parameters
218
+ except (TypeError, ValueError):
219
+ accepts = False
220
+ if accepts:
221
+ return run_fn(payload, project_path, defer_consume=True)
222
+ return run_fn(payload, project_path)
223
+
224
+
225
+ def _grant_lost_deny() -> dict:
226
+ return {
227
+ "hookSpecificOutput": {
228
+ "hookEventName": "PreToolUse",
229
+ "permissionDecision": "deny",
230
+ "permissionDecisionReason": (
231
+ "[c3-override:spent] the override grant that would have "
232
+ "allowed this call was used up between check and use (a "
233
+ "concurrent call spent it). The rule stands — ask again "
234
+ "with c3_override if it is still needed."
235
+ ),
236
+ }
237
+ }
238
+
239
+
240
+ def _settle_grants(outputs: list) -> list:
241
+ """Burn deferred grant uses — only when the merged verdict is allow.
242
+
243
+ Any deny among the outputs means the call will not run, so every
244
+ `_on_allow` is dropped unrun and the grants stay live for the retry. On
245
+ an allow each consumer runs; the line it returns replaces the
246
+ provisional one (it carries the post-consumption uses count). A consumer
247
+ that finds nothing left — the grant was spent by a concurrent call
248
+ between peek and settle — turns its allow into a deny: the call must
249
+ not proceed on a grant that no longer exists.
250
+ """
251
+ denied = any(_is_deny(o) for o in outputs)
252
+ settled = []
253
+ for out in outputs:
254
+ if not isinstance(out, dict):
255
+ settled.append(out)
256
+ continue
257
+ consume = out.pop("_on_allow", None)
258
+ if consume is None or denied:
259
+ settled.append(out)
260
+ continue
261
+ try:
262
+ line = consume()
263
+ except Exception as exc:
264
+ log_hook_error("hook_dispatch:settle", exc)
265
+ line = None
266
+ if line:
267
+ out["additionalContext"] = str(line)
268
+ settled.append(out)
269
+ else:
270
+ settled.append(_grant_lost_deny())
271
+ return settled
272
+
273
+
192
274
  def _load_run(module_name: str):
193
275
  """Import cli.<module_name> and return its run() (cached).
194
276
 
@@ -353,7 +435,7 @@ def dispatch(event: str, payload: dict, project_path: Path | None = None) -> dic
353
435
  outputs.append(_fail_closed_deny(module_name, err))
354
436
  continue
355
437
  try:
356
- out = run_fn(payload, project_path)
438
+ out = _call_run(run_fn, module_name, event, payload, project_path)
357
439
  if out:
358
440
  outputs.append(out)
359
441
  except Exception as exc:
@@ -368,7 +450,15 @@ def dispatch(event: str, payload: dict, project_path: Path | None = None) -> dic
368
450
  # Critical state-layer warnings (corrupt enforcement_state.json)
369
451
  # become visible instead of silently disabling enforcement.
370
452
  warnings.extend(_hook_utils.drain_state_warnings())
453
+ # A PreToolUse deny is final — merge keeps the first deny and no
454
+ # allow readmits it — so the sub-hooks after it have nothing to add,
455
+ # and running them could only spend a grant or log a block for a
456
+ # call that will not happen.
457
+ if event == "pretool" and outputs and _is_deny(outputs[-1]):
458
+ break
371
459
 
460
+ if event == "pretool":
461
+ outputs = _settle_grants(outputs)
372
462
  return merge_outputs(outputs, warnings, event=event, host=host)
373
463
 
374
464