code-context-control 2.61.0__tar.gz → 2.61.2__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 (359) hide show
  1. {code_context_control-2.61.0/code_context_control.egg-info → code_context_control-2.61.2}/PKG-INFO +1 -1
  2. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/c3.py +1 -1
  3. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hook_pretool_enforce.py +22 -0
  4. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/project_card.js +57 -9
  5. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/credentials.py +8 -0
  6. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/edit.py +8 -0
  7. {code_context_control-2.61.0 → code_context_control-2.61.2/code_context_control.egg-info}/PKG-INFO +1 -1
  8. {code_context_control-2.61.0 → code_context_control-2.61.2}/code_context_control.egg-info/SOURCES.txt +1 -0
  9. {code_context_control-2.61.0 → code_context_control-2.61.2}/pyproject.toml +1 -1
  10. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/credential_store.py +58 -0
  11. code_context_control-2.61.2/tests/test_vault_write_guard.py +284 -0
  12. {code_context_control-2.61.0 → code_context_control-2.61.2}/LICENSE +0 -0
  13. {code_context_control-2.61.0 → code_context_control-2.61.2}/README.md +0 -0
  14. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/__init__.py +0 -0
  15. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/_hook_utils.py +0 -0
  16. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/commands/__init__.py +0 -0
  17. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/commands/common.py +0 -0
  18. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/commands/parser.py +0 -0
  19. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/docs.html +0 -0
  20. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/edits.html +0 -0
  21. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/guide/bitbucket.html +0 -0
  22. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/guide/credentials.html +0 -0
  23. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/guide/getting-started.html +0 -0
  24. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/guide/index.html +0 -0
  25. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/guide/oracle.html +0 -0
  26. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/guide/shared.css +0 -0
  27. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/guide/tools.html +0 -0
  28. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/guide/workflow.html +0 -0
  29. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hook_artifact.py +0 -0
  30. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hook_auto_snapshot.py +0 -0
  31. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hook_c3_signal.py +0 -0
  32. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hook_c3read.py +0 -0
  33. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hook_dispatch.py +0 -0
  34. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hook_edit_ledger.py +0 -0
  35. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hook_edit_unlock.py +0 -0
  36. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hook_filter.py +0 -0
  37. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hook_ghost_files.py +0 -0
  38. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hook_prompt_recall.py +0 -0
  39. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hook_read.py +0 -0
  40. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hook_session_stats.py +0 -0
  41. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hook_terse_advisor.py +0 -0
  42. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub.html +0 -0
  43. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_server.py +0 -0
  44. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/app.js +0 -0
  45. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/add_project.js +0 -0
  46. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/config_editor.js +0 -0
  47. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/drill_artifacts.js +0 -0
  48. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/drill_health.js +0 -0
  49. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/drill_panel.js +0 -0
  50. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/drill_subprojects.js +0 -0
  51. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/drill_tasks.js +0 -0
  52. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/drill_views.js +0 -0
  53. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/global_search.js +0 -0
  54. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/hub_credentials.js +0 -0
  55. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/mcp_manager.js +0 -0
  56. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/modals.js +0 -0
  57. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/project_tree.js +0 -0
  58. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/session_drawer.js +0 -0
  59. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/settings_modal.js +0 -0
  60. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/sidebar.js +0 -0
  61. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/summary_bar.js +0 -0
  62. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/task_board.js +0 -0
  63. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/toasts.js +0 -0
  64. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/components/topbar.js +0 -0
  65. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui/state.js +0 -0
  66. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/hub_ui.html +0 -0
  67. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/mcp_proxy.py +0 -0
  68. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/mcp_server.py +0 -0
  69. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/progress.py +0 -0
  70. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/server.py +0 -0
  71. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/__init__.py +0 -0
  72. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/_helpers.py +0 -0
  73. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/agent.py +0 -0
  74. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/artifacts.py +0 -0
  75. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/bitbucket.py +0 -0
  76. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/compress.py +0 -0
  77. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/delegate.py +0 -0
  78. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/edits.py +0 -0
  79. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/federate.py +0 -0
  80. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/filter.py +0 -0
  81. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/impact.py +0 -0
  82. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/jira.py +0 -0
  83. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/memory.py +0 -0
  84. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/project.py +0 -0
  85. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/read.py +0 -0
  86. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/search.py +0 -0
  87. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/session.py +0 -0
  88. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/shell.py +0 -0
  89. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/status.py +0 -0
  90. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/tasks.py +0 -0
  91. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/tools/validate.py +0 -0
  92. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/api.js +0 -0
  93. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/app.js +0 -0
  94. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/components/bitbucket.js +0 -0
  95. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/components/chat.js +0 -0
  96. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/components/credentials.js +0 -0
  97. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/components/dashboard.js +0 -0
  98. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/components/edits.js +0 -0
  99. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/components/instructions.js +0 -0
  100. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/components/jira.js +0 -0
  101. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/components/memory.js +0 -0
  102. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/components/sessions.js +0 -0
  103. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/components/settings.js +0 -0
  104. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/components/sidebar.js +0 -0
  105. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/components/tasks.js +0 -0
  106. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/icons.js +0 -0
  107. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/pm_shared.js +0 -0
  108. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/shared.js +0 -0
  109. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui/theme.js +0 -0
  110. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui.html +0 -0
  111. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui_legacy.html +0 -0
  112. {code_context_control-2.61.0 → code_context_control-2.61.2}/cli/ui_nano.html +0 -0
  113. {code_context_control-2.61.0 → code_context_control-2.61.2}/code_context_control.egg-info/dependency_links.txt +0 -0
  114. {code_context_control-2.61.0 → code_context_control-2.61.2}/code_context_control.egg-info/entry_points.txt +0 -0
  115. {code_context_control-2.61.0 → code_context_control-2.61.2}/code_context_control.egg-info/requires.txt +0 -0
  116. {code_context_control-2.61.0 → code_context_control-2.61.2}/code_context_control.egg-info/top_level.txt +0 -0
  117. {code_context_control-2.61.0 → code_context_control-2.61.2}/core/__init__.py +0 -0
  118. {code_context_control-2.61.0 → code_context_control-2.61.2}/core/config.py +0 -0
  119. {code_context_control-2.61.0 → code_context_control-2.61.2}/core/ide.py +0 -0
  120. {code_context_control-2.61.0 → code_context_control-2.61.2}/core/mcp_toml.py +0 -0
  121. {code_context_control-2.61.0 → code_context_control-2.61.2}/core/web_security.py +0 -0
  122. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/__init__.py +0 -0
  123. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/config.py +0 -0
  124. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/mcp_oracle.py +0 -0
  125. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/oracle_server.py +0 -0
  126. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/oracle_ui.html +0 -0
  127. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/__init__.py +0 -0
  128. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/activity_reporter.py +0 -0
  129. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/api_auth.py +0 -0
  130. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/c3_bridge.py +0 -0
  131. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/chat_engine.py +0 -0
  132. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/chat_store.py +0 -0
  133. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/cross_memory.py +0 -0
  134. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/federated_graph.py +0 -0
  135. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/health_checker.py +0 -0
  136. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/insight_engine.py +0 -0
  137. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/local_session.py +0 -0
  138. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/memory_reader.py +0 -0
  139. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/memory_writer.py +0 -0
  140. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/project_scanner.py +0 -0
  141. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/review_agent.py +0 -0
  142. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/tool_executor.py +0 -0
  143. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/services/tool_registry.py +0 -0
  144. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/activity.js +0 -0
  145. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/agents.js +0 -0
  146. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/app.js +0 -0
  147. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/busy.js +0 -0
  148. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/chat/conversations.js +0 -0
  149. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/chat/input.js +0 -0
  150. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/chat/markdown.js +0 -0
  151. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/chat/send.js +0 -0
  152. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/chat/stream_renderer.js +0 -0
  153. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/chat/toolbar.js +0 -0
  154. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/core.js +0 -0
  155. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/crossgraph.js +0 -0
  156. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/header.js +0 -0
  157. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/insights.js +0 -0
  158. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/projects.js +0 -0
  159. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/settings.js +0 -0
  160. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/suggestions.js +0 -0
  161. {code_context_control-2.61.0 → code_context_control-2.61.2}/oracle/ui/theme_tabs.js +0 -0
  162. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/__init__.py +0 -0
  163. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/activity_log.py +0 -0
  164. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/agent_base.py +0 -0
  165. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/agents.py +0 -0
  166. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/artifact_defs.py +0 -0
  167. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/artifact_store.py +0 -0
  168. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/auto_memory.py +0 -0
  169. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/bench/__init__.py +0 -0
  170. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/bench/external/__init__.py +0 -0
  171. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/bench/external/aider_polyglot.py +0 -0
  172. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/bench/external/swe_bench.py +0 -0
  173. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/benchmark_dashboard.py +0 -0
  174. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/bitbucket_client.py +0 -0
  175. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/bitbucket_credentials.py +0 -0
  176. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/circuit_breaker.py +0 -0
  177. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/claude_md.py +0 -0
  178. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/compressor.py +0 -0
  179. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/context_snapshot.py +0 -0
  180. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/conversation_store.py +0 -0
  181. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/doc_index.py +0 -0
  182. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/e2e_benchmark.py +0 -0
  183. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/e2e_evaluator.py +0 -0
  184. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/e2e_tasks.py +0 -0
  185. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/edit_ledger.py +0 -0
  186. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/embedding_index.py +0 -0
  187. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/error_reporting.py +0 -0
  188. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/file_memory.py +0 -0
  189. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/git_context.py +0 -0
  190. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/hub_service.py +0 -0
  191. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/indexer.py +0 -0
  192. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/jira_client.py +0 -0
  193. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/jira_cloud.py +0 -0
  194. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/jira_credentials.py +0 -0
  195. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/jira_data_center.py +0 -0
  196. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/jira_links.py +0 -0
  197. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/memory.py +0 -0
  198. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/memory_consolidator.py +0 -0
  199. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/memory_distiller.py +0 -0
  200. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/memory_graph.py +0 -0
  201. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/memory_grounder.py +0 -0
  202. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/memory_queue.py +0 -0
  203. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/memory_scorer.py +0 -0
  204. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/metrics.py +0 -0
  205. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/notifications.py +0 -0
  206. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/ollama_bridge.py +0 -0
  207. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/ollama_client.py +0 -0
  208. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/ollama_credentials.py +0 -0
  209. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/output_filter.py +0 -0
  210. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/parser.py +0 -0
  211. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/project_manager.py +0 -0
  212. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/project_runtime.py +0 -0
  213. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/protocol.py +0 -0
  214. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/proxy_state.py +0 -0
  215. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/repo_map.py +0 -0
  216. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/retention.py +0 -0
  217. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/retrieval_broker.py +0 -0
  218. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/router.py +0 -0
  219. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/runtime.py +0 -0
  220. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/scanner.py +0 -0
  221. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/session_benchmark.py +0 -0
  222. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/session_manager.py +0 -0
  223. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/session_preloader.py +0 -0
  224. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/subprojects.py +0 -0
  225. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/task_store.py +0 -0
  226. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/telemetry.py +0 -0
  227. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/text_index.py +0 -0
  228. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/time_tracker.py +0 -0
  229. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/tool_classifier.py +0 -0
  230. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/transcript_index.py +0 -0
  231. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/validation_cache.py +0 -0
  232. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/vector_store.py +0 -0
  233. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/watcher.py +0 -0
  234. {code_context_control-2.61.0 → code_context_control-2.61.2}/services/win_subprocess.py +0 -0
  235. {code_context_control-2.61.0 → code_context_control-2.61.2}/setup.cfg +0 -0
  236. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_activity_reporter.py +0 -0
  237. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_aider_polyglot.py +0 -0
  238. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_antigravity_transition.py +0 -0
  239. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_artifact_store.py +0 -0
  240. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_artifact_tool.py +0 -0
  241. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_bitbucket_cli_smoke.py +0 -0
  242. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_bitbucket_client.py +0 -0
  243. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_bitbucket_config_fallback.py +0 -0
  244. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_bitbucket_credentials.py +0 -0
  245. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_bitbucket_tool.py +0 -0
  246. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_c3_bridge_project_artifacts.py +0 -0
  247. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_c3_shell.py +0 -0
  248. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_circuit_breaker.py +0 -0
  249. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_claude_md_merge.py +0 -0
  250. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_cli_smoke.py +0 -0
  251. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_compressor_large_file.py +0 -0
  252. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_credential_store.py +0 -0
  253. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_credentials_routes.py +0 -0
  254. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_credentials_tool.py +0 -0
  255. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_creds_cli_smoke.py +0 -0
  256. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_delegate_cascade.py +0 -0
  257. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_e2e_benchmark.py +0 -0
  258. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_edit_ledger_hook.py +0 -0
  259. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_edit_normalization.py +0 -0
  260. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_enforcement_flip.py +0 -0
  261. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_federated_graph.py +0 -0
  262. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_filter_backoff.py +0 -0
  263. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_ghost_files.py +0 -0
  264. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_ghost_generation.py +0 -0
  265. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_git_branch_awareness.py +0 -0
  266. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_hook_dispatch.py +0 -0
  267. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_hook_pretool_enforce.py +0 -0
  268. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_hook_prompt_recall.py +0 -0
  269. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_hook_smoke.py +0 -0
  270. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_hook_state.py +0 -0
  271. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_hook_utf8_stdio.py +0 -0
  272. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_hub_credentials_route.py +0 -0
  273. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_hub_credentials_routes_write.py +0 -0
  274. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_hub_inspect_api.py +0 -0
  275. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_hub_server_smoke.py +0 -0
  276. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_hub_subproject_links.py +0 -0
  277. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_init_scan.py +0 -0
  278. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_install_mcp_entrypoint.py +0 -0
  279. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_jira_cli_smoke.py +0 -0
  280. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_jira_client.py +0 -0
  281. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_jira_config_fallback.py +0 -0
  282. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_jira_credentials.py +0 -0
  283. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_jira_links.py +0 -0
  284. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_jira_routes.py +0 -0
  285. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_jira_tool.py +0 -0
  286. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_lazy_store_init.py +0 -0
  287. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_mcp_host_guard.py +0 -0
  288. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_mcp_memory_timeout.py +0 -0
  289. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_mcp_server_smoke.py +0 -0
  290. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_mcp_toml.py +0 -0
  291. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_memory_distiller.py +0 -0
  292. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_memory_graph_api.py +0 -0
  293. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_memory_queue.py +0 -0
  294. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_memory_system.py +0 -0
  295. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_no_stale_model_pins.py +0 -0
  296. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_notification_dedup.py +0 -0
  297. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_notification_discipline.py +0 -0
  298. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_ollama_credentials.py +0 -0
  299. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_oracle_api_auth.py +0 -0
  300. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_oracle_apikey_api.py +0 -0
  301. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_oracle_c3_bridge.py +0 -0
  302. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_oracle_chat_engine.py +0 -0
  303. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_oracle_discovery_api.py +0 -0
  304. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_oracle_local_auth.py +0 -0
  305. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_oracle_ollama_bridge.py +0 -0
  306. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_oracle_scanner_cache.py +0 -0
  307. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_oracle_security_fixes.py +0 -0
  308. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_oracle_subproject_awareness.py +0 -0
  309. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_oracle_ui_bundle.py +0 -0
  310. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_output_filter.py +0 -0
  311. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_permissions.py +0 -0
  312. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_pm_api.py +0 -0
  313. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_project_manager.py +0 -0
  314. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_project_manager_merge.py +0 -0
  315. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_project_tool.py +0 -0
  316. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_read_coercion.py +0 -0
  317. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_read_edit_parity.py +0 -0
  318. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_related_facts.py +0 -0
  319. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_repo_map.py +0 -0
  320. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_response_boilerplate.py +0 -0
  321. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_retention.py +0 -0
  322. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_review_digest.py +0 -0
  323. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_service_durability.py +0 -0
  324. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_session_benchmark.py +0 -0
  325. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_session_budget.py +0 -0
  326. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_shell_creds.py +0 -0
  327. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_shell_robustness.py +0 -0
  328. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_subproject_exclusion.py +0 -0
  329. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_subproject_federation.py +0 -0
  330. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_subprojects.py +0 -0
  331. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_swe_bench.py +0 -0
  332. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_task_store.py +0 -0
  333. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_task_tool.py +0 -0
  334. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_time_tracker.py +0 -0
  335. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_token_telemetry.py +0 -0
  336. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_tool_registry.py +0 -0
  337. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_upgrade_and_version.py +0 -0
  338. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_validate.py +0 -0
  339. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_version_sync.py +0 -0
  340. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_web_security.py +0 -0
  341. {code_context_control-2.61.0 → code_context_control-2.61.2}/tests/test_windows_reliability.py +0 -0
  342. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/__init__.py +0 -0
  343. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/backend.py +0 -0
  344. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/main.py +0 -0
  345. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/screens/__init__.py +0 -0
  346. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/screens/benchmark_view.py +0 -0
  347. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/screens/claudemd_view.py +0 -0
  348. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/screens/compress_view.py +0 -0
  349. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/screens/index_view.py +0 -0
  350. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/screens/init_view.py +0 -0
  351. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/screens/mcp_view.py +0 -0
  352. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/screens/optimize_view.py +0 -0
  353. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/screens/pipe_view.py +0 -0
  354. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/screens/projects_view.py +0 -0
  355. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/screens/search_view.py +0 -0
  356. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/screens/session_view.py +0 -0
  357. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/screens/stats.py +0 -0
  358. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/screens/ui_view.py +0 -0
  359. {code_context_control-2.61.0 → code_context_control-2.61.2}/tui/theme.tcss +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.61.0
3
+ Version: 2.61.2
4
4
  Summary: Local code-intelligence layer for AI coding tools (Claude Code, Codex, Copilot, Cursor, Antigravity). Retrieve less, read less, edit safer — version the configs that shape your agent (CLAUDE.md, skills, hooks, MCP), and manage multi-project + sub-project hierarchies from a local 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.61.0"
95
+ __version__ = "2.61.2"
96
96
 
97
97
 
98
98
  def _command_deps() -> CommandDeps:
@@ -78,6 +78,11 @@ _PREREQS = {
78
78
  _ADVISORY_TOOLS = {"Read", "Grep", "Glob", "FindFiles", "SearchText"}
79
79
  _BLOCKED_TOOLS = {"Edit", "Write", "MultiEdit"}
80
80
 
81
+ # Vault files no native write may touch, regardless of unlock state.
82
+ # Mirrors services.credential_store.VAULT_PROTECTED_FILES (parity-tested);
83
+ # duplicated because hooks must stay import-light.
84
+ _VAULT_FILES = frozenset({"config.json", "secrets.enc", "cred_state.json"})
85
+
81
86
  # Redirect messages per native tool
82
87
  _REDIRECTS = {
83
88
  "Read": (
@@ -283,6 +288,23 @@ def run(payload: dict, project_path: Path | None = None) -> dict | None:
283
288
  session_id = str(payload.get("session_id") or "")
284
289
  base = project_path if project_path is not None else Path.cwd()
285
290
 
291
+ # Vault write-guard: the credential registry/state may never be modified
292
+ # by the agent — even with a warm c3 signal or sticky unlock (v2.61.2).
293
+ if tool_name in _BLOCKED_TOOLS:
294
+ target = Path(str(tool_input.get("file_path") or ""))
295
+ if target.name.lower() in _VAULT_FILES and target.parent.name.lower() == ".c3":
296
+ return {
297
+ "hookSpecificOutput": {
298
+ "hookEventName": "PreToolUse",
299
+ "permissionDecision": "deny",
300
+ "permissionDecisionReason": (
301
+ f"[c3:vault-protected] {target.name} belongs to the "
302
+ "credential vault and cannot be modified by the agent. "
303
+ "Ask the user to use the Credentials UI or `c3 creds` CLI."
304
+ ),
305
+ }
306
+ }
307
+
286
308
  # Session-scoped load: state written by a different session comes back
287
309
  # empty, so stale unlocks degrade to the advisory path below.
288
310
  state = load_enforcement_state(base, session_id=session_id)
@@ -377,24 +377,63 @@ function ProjectCard({ p, isChild, rollup, expanded, onToggleExpand, onChanged,
377
377
  // Launch the UI server, then open its tab once the port shows up in the
378
378
  // registry. launch_session cannot return the port (the detached child
379
379
  // picks it), so open a placeholder tab synchronously — still inside the
380
- // click gesture, so popup blockers allow it and navigate it when
381
- // polling /api/projects finds the port.
380
+ // click gesture, so popup blockers allow it. The placeholder carries its
381
+ // own poll/redirect script (about:blank inherits this tab's origin, so
382
+ // its fetches are same-origin) and resolves even if this hub tab is
383
+ // closed, reloaded, or hung mid-launch — it must never depend on the
384
+ // opener surviving the launch window.
382
385
  const start = async (e) => {
383
386
  e.stopPropagation();
384
387
  if (starting) return;
385
388
  setStarting(true);
386
389
  const win = window.open('', '_blank');
390
+ // armed = the placeholder polls and navigates itself; when false
391
+ // (write failed / popup blocked) the hub-side poll navigates instead.
392
+ let armed = false;
387
393
  if (win) {
388
394
  try {
395
+ const escHtml = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;');
396
+ const jsStr = (s) => JSON.stringify(String(s)).replace(/</g, '\\u003c');
389
397
  win.document.write(
390
- `<title>Starting ${p.name}…</title>` +
398
+ `<title>Starting ${escHtml(p.name)}…</title>` +
391
399
  '<body style="background:#0d1117;color:#8b949e;font-family:sans-serif;' +
392
- 'display:flex;align-items:center;justify-content:center;height:100vh">' +
393
- `Starting ${p.name}…</body>`);
400
+ 'display:flex;align-items:center;justify-content:center;height:100vh;margin:0">' +
401
+ '<div style="text-align:center;max-width:560px;padding:0 20px">' +
402
+ '<div id="spin" style="margin:0 auto 14px;width:22px;height:22px;' +
403
+ 'border:2px solid #21262d;border-top-color:#58a6ff;border-radius:50%;' +
404
+ 'animation:s .8s linear infinite"></div>' +
405
+ '<style>@keyframes s{to{transform:rotate(360deg)}}</style>' +
406
+ `<div id="msg" style="color:#c9d1d9">Starting ${escHtml(p.name)}…</div>` +
407
+ '<div id="sub" style="margin-top:6px;font-size:12px">waiting for the UI server to report a port</div>' +
408
+ '</div><script>' +
409
+ `var ORIGIN=${jsStr(location.origin)},TARGET=${jsStr((p.path || '').toLowerCase())},NAME=${jsStr(p.name)},tries=0,dead=0;` +
410
+ 'function fail(why){dead=1;document.getElementById("spin").style.display="none";' +
411
+ 'document.getElementById("msg").textContent=NAME+": "+why;' +
412
+ 'document.getElementById("sub").textContent=' +
413
+ '"Check the project\'s .c3/ui.log — you can close this tab.";}' +
414
+ 'window.__c3fail=fail;' +
415
+ 'function poll(){if(dead)return;' +
416
+ 'fetch(ORIGIN+"/api/projects").then(function(r){return r.json()})' +
417
+ '.then(function(rows){if(dead)return;' +
418
+ 'var row=(Array.isArray(rows)?rows:[]).find(function(r){' +
419
+ 'return (r.path||"").toLowerCase()===TARGET});' +
420
+ 'if(row&&row.port){location.replace("http://127.0.0.1:"+row.port);return}' +
421
+ 'if(++tries>=20){fail("UI server did not report a port after 30s");return}' +
422
+ 'setTimeout(poll,1500)}).catch(function(){if(dead)return;' +
423
+ 'if(++tries>=20){fail("hub unreachable");return}setTimeout(poll,1500)})}' +
424
+ 'setTimeout(poll,1200);<\/script>');
425
+ win.document.close();
426
+ armed = true;
394
427
  } catch { }
395
428
  }
396
429
  const fail = (msg) => {
397
- if (win) win.close();
430
+ if (win) {
431
+ // Surface the error inside the tab the user is looking at; close
432
+ // it only when the placeholder script isn't there to show it.
433
+ let shown = false;
434
+ try { if (armed && win.__c3fail) { win.__c3fail(msg); shown = true; } } catch { }
435
+ if (!shown) { try { win.close(); } catch { } }
436
+ }
398
437
  notify(msg, 'err');
399
438
  setStarting(false);
400
439
  };
@@ -403,19 +442,28 @@ function ProjectCard({ p, isChild, rollup, expanded, onToggleExpand, onChanged,
403
442
  if (!d.launched) { fail('Launch failed'); return; }
404
443
  notify(`Starting ${p.name}…`);
405
444
  setTimeout(onChanged, 1500);
445
+ // Bookkeeping poll: refresh the card and clear the busy state. Only
446
+ // navigates or closes the tab when the placeholder isn't armed.
406
447
  const poll = async (tries) => {
407
448
  let rows = [];
408
449
  try { rows = await api.get('/api/projects'); } catch { }
409
450
  const row = (Array.isArray(rows) ? rows : []).find(r =>
410
451
  (r.path || '').toLowerCase() === (p.path || '').toLowerCase());
411
452
  if (row && row.port) {
412
- const url = 'http://127.0.0.1:' + row.port;
413
- if (win) { win.location = url; } else { window.open(url, '_blank'); }
453
+ if (!armed) {
454
+ const url = 'http://127.0.0.1:' + row.port;
455
+ if (win) { win.location = url; } else { window.open(url, '_blank'); }
456
+ }
414
457
  onChanged();
415
458
  setStarting(false);
416
459
  return;
417
460
  }
418
- if (tries >= 20) { fail(`${p.name}: UI server did not report a port — check .c3/ui.log`); return; }
461
+ if (tries >= 20) {
462
+ if (!armed && win) { try { win.close(); } catch { } }
463
+ notify(`${p.name}: UI server did not report a port — check .c3/ui.log`, 'err');
464
+ setStarting(false);
465
+ return;
466
+ }
419
467
  setTimeout(() => poll(tries + 1), 1500);
420
468
  };
421
469
  setTimeout(() => poll(0), 1200);
@@ -130,6 +130,14 @@ def _act_reveal(name: str, svc, project_path: str) -> str:
130
130
  "or ask the user to enable agent_readable in the Credentials UI / "
131
131
  f"`c3 creds set {name} --agent-readable`."
132
132
  )
133
+ if not cs.verify_agent_readable(name, scope=entry["scope"], project_path=project_path):
134
+ return (
135
+ f"[creds:integrity] {name!r} is marked agent_readable in the registry, "
136
+ "but the flag's keyring attestation is missing or disagrees — the "
137
+ "registry may have been modified outside the credentials API. Ask the "
138
+ "user to re-confirm the flag (toggle it in the Credentials UI or "
139
+ "`c3 creds`); re-saving it restores the attestation."
140
+ )
133
141
  value = cs.get_value(name, project_path=project_path, scope=entry["scope"])
134
142
  if value is None:
135
143
  return (
@@ -13,6 +13,8 @@ import json
13
13
  import threading
14
14
  from pathlib import Path
15
15
 
16
+ from services import credential_store as _cs
17
+
16
18
  # Per-file locks — keyed by resolved absolute path string.
17
19
  _file_locks: dict[str, threading.Lock] = {}
18
20
  _locks_lock = threading.Lock()
@@ -237,6 +239,12 @@ def handle_edit(file_path: str, old_string: str, new_string: str,
237
239
  path = Path(svc.project_path) / path
238
240
  path = path.resolve()
239
241
 
242
+ # Vault write-guard: the credential registry/state is never agent-writable
243
+ # (covers create, edit, and batch modes — and c3_project's edit proxy).
244
+ guard = _cs.vault_guard_reason(path)
245
+ if guard:
246
+ return finalize("c3_edit", {"file": file_path}, guard, "vault-protected")
247
+
240
248
  # Relative path for ledger + display (computed even for new files)
241
249
  try:
242
250
  rel = str(path.relative_to(Path(svc.project_path).resolve())).replace("\\", "/")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.61.0
3
+ Version: 2.61.2
4
4
  Summary: Local code-intelligence layer for AI coding tools (Claude Code, Codex, Copilot, Cursor, Antigravity). Retrieve less, read less, edit safer — version the configs that shape your agent (CLAUDE.md, skills, hooks, MCP), and manage multi-project + sub-project hierarchies from a local hub.
5
5
  Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -333,6 +333,7 @@ tests/test_token_telemetry.py
333
333
  tests/test_tool_registry.py
334
334
  tests/test_upgrade_and_version.py
335
335
  tests/test_validate.py
336
+ tests/test_vault_write_guard.py
336
337
  tests/test_version_sync.py
337
338
  tests/test_web_security.py
338
339
  tests/test_windows_reliability.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code-context-control"
7
- version = "2.61.0"
7
+ version = "2.61.2"
8
8
  description = "Local code-intelligence layer for AI coding tools (Claude Code, Codex, Copilot, Cursor, Antigravity). Retrieve less, read less, edit safer — version the configs that shape your agent (CLAUDE.md, skills, hooks, MCP), and manage multi-project + sub-project hierarchies from a local hub."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -135,6 +135,56 @@ def _account(realm_s: str, name: str) -> str:
135
135
  return f"{realm_s}|{name}"
136
136
 
137
137
 
138
+ def _flag_account(realm_s: str, name: str) -> str:
139
+ return f"{realm_s}|{name}::agent_readable"
140
+
141
+
142
+ # Files the agent must never write through c3 tool surfaces: the vault
143
+ # registry (config.json) and its sidecar state. Editing these outside the
144
+ # credentials API is how a prompt-injected agent would grant itself reveal
145
+ # access. Mirrored in cli/hook_pretool_enforce.py (parity-tested).
146
+ VAULT_PROTECTED_FILES = frozenset({"config.json", "secrets.enc", "cred_state.json"})
147
+
148
+
149
+ def vault_guard_reason(path) -> str:
150
+ """Non-empty refusal message when ``path`` is a vault file under .c3/."""
151
+ p = Path(path)
152
+ if p.name.lower() in VAULT_PROTECTED_FILES and p.parent.name.lower() == ".c3":
153
+ return (
154
+ f"[c3:vault-protected] {p.name} belongs to the credential vault "
155
+ "(registry/state) and cannot be modified by the agent. Changes go "
156
+ "through the Credentials UI or the `c3 creds` CLI — ask the user."
157
+ )
158
+ return ""
159
+
160
+
161
+ def _write_flag_attestation(realm_s: str, name: str, readable: bool) -> None:
162
+ """Keyring copy of agent_readable — reveal trusts registry AND keyring.
163
+
164
+ A registry flag flipped by editing config.json directly (outside this
165
+ API) disagrees with the attestation, and reveal fails closed.
166
+ """
167
+ try:
168
+ _keyring_module().set_password(
169
+ KEYRING_SERVICE, _flag_account(realm_s, name), "1" if readable else "0")
170
+ except Exception:
171
+ pass # keyring-less env: verify_agent_readable fails closed anyway
172
+
173
+
174
+ def verify_agent_readable(name: str, *, scope: str, project_path: str = ".") -> bool:
175
+ """True only when the keyring attestation agrees the flag is enabled.
176
+
177
+ Fails closed: a missing attestation (pre-v2.61.2 entry, keyring failure,
178
+ or a registry edited outside this API) reads as not-readable.
179
+ """
180
+ try:
181
+ realm_s = realm(_norm_scope(scope, project_path), project_path)
182
+ return _keyring_module().get_password(
183
+ KEYRING_SERVICE, _flag_account(realm_s, name)) == "1"
184
+ except Exception:
185
+ return False
186
+
187
+
138
188
  def _scope_dir(scope: str, project_path: str) -> Optional[Path]:
139
189
  """Base directory holding .c3/ for the scope; None when global is unresolvable."""
140
190
  if scope == "global":
@@ -376,6 +426,7 @@ def set_credential(
376
426
  section["entries"][name] = entry
377
427
  config["credentials"] = section
378
428
  _save_config(base, config)
429
+ _write_flag_attestation(realm_s, name, bool(agent_readable))
379
430
  return dict(entry)
380
431
 
381
432
 
@@ -407,6 +458,9 @@ def update_metadata(name: str, *, scope: str, project_path: str = ".", **fields)
407
458
  entry["updated"] = _utcnow()
408
459
  config["credentials"] = section
409
460
  _save_config(base, config)
461
+ if "agent_readable" in fields:
462
+ _write_flag_attestation(
463
+ realm(scope, project_path), name, bool(fields["agent_readable"]))
410
464
  return dict(entry)
411
465
 
412
466
 
@@ -467,6 +521,10 @@ def delete_credential(name: str, *, scope: str, project_path: str = ".") -> bool
467
521
  removed_value = True
468
522
  except Exception:
469
523
  pass
524
+ try:
525
+ _keyring_module().delete_password(KEYRING_SERVICE, _flag_account(realm_s, name))
526
+ except Exception:
527
+ pass
470
528
  config = _load_config(base)
471
529
  section = _creds_section(config)
472
530
  removed_entry = name in section["entries"]
@@ -0,0 +1,284 @@
1
+ """Vault write-guard hotfix (v2.61.2) — closes the agent_readable escalation.
2
+
3
+ Attack: the agent edits .c3/config.json (c3_edit, native Write, or shell),
4
+ flips a credential's agent_readable flag, then calls reveal. Three guards:
5
+ 1. c3_edit refuses vault files (registry + sidecar state), all modes.
6
+ 2. The PreToolUse hook denies native Edit/Write/MultiEdit on vault files
7
+ even when a warm c3 signal or sticky unlock would otherwise allow them.
8
+ 3. reveal requires a keyring attestation of agent_readable written only by
9
+ the credentials API — a registry edited outside the API fails closed.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import base64
14
+ import json
15
+ import sys
16
+ import tempfile
17
+ import unittest
18
+ from pathlib import Path
19
+ from unittest import mock
20
+
21
+ REPO_ROOT = Path(__file__).resolve().parents[1]
22
+ sys.path.insert(0, str(REPO_ROOT))
23
+
24
+ from cli import _hook_utils # noqa: E402
25
+
26
+ # hook_pretool_enforce imports plain `_hook_utils`; alias so both spellings
27
+ # resolve to the same module instance (mirrors cli/hook_dispatch.py).
28
+ sys.modules.setdefault("_hook_utils", _hook_utils)
29
+
30
+ import cli.hook_pretool_enforce as hook # noqa: E402
31
+ from cli.tools.credentials import handle_credentials # noqa: E402
32
+ from cli.tools.edit import handle_edit # noqa: E402
33
+ from services import credential_store as cs # noqa: E402
34
+
35
+
36
+ class _StubKeyring:
37
+ def __init__(self):
38
+ self.store: dict[tuple[str, str], str] = {}
39
+
40
+ def set_password(self, service, account, password):
41
+ self.store[(service, account)] = password
42
+
43
+ def get_password(self, service, account):
44
+ return self.store.get((service, account))
45
+
46
+ def delete_password(self, service, account):
47
+ if (service, account) not in self.store:
48
+ raise KeyError("not found")
49
+ del self.store[(service, account)]
50
+
51
+
52
+ class _StubFernet:
53
+ def __init__(self, key):
54
+ self._key = key
55
+
56
+ @staticmethod
57
+ def generate_key():
58
+ return base64.urlsafe_b64encode(b"0" * 32)
59
+
60
+ def encrypt(self, data):
61
+ return base64.urlsafe_b64encode(self._key + b"|" + data)
62
+
63
+ def decrypt(self, token):
64
+ raw = base64.urlsafe_b64decode(token)
65
+ key, _, data = raw.partition(b"|")
66
+ if key != self._key:
67
+ raise ValueError("bad key")
68
+ return data
69
+
70
+
71
+ class _StubLedger:
72
+ def __init__(self):
73
+ self.edits: list[dict] = []
74
+
75
+ def log_edit(self, **kw):
76
+ self.edits.append(kw)
77
+
78
+
79
+ class _StubActivity:
80
+ def __init__(self):
81
+ self.events: list[tuple] = []
82
+
83
+ def log(self, kind, data):
84
+ self.events.append((kind, data))
85
+
86
+
87
+ class _Svc:
88
+ def __init__(self, project_path):
89
+ self.project_path = project_path
90
+ self.edit_ledger = _StubLedger()
91
+ self.activity_log = _StubActivity()
92
+
93
+
94
+ def _finalize(tool, args, resp, summary, **kw):
95
+ return resp
96
+
97
+
98
+ class TestParity(unittest.TestCase):
99
+ def test_hook_set_matches_store_set(self):
100
+ self.assertEqual(hook._VAULT_FILES, cs.VAULT_PROTECTED_FILES)
101
+
102
+
103
+ class TestEditVaultGuard(unittest.TestCase):
104
+ def setUp(self):
105
+ self._tmp = tempfile.TemporaryDirectory()
106
+ self.proj = Path(self._tmp.name)
107
+ (self.proj / ".c3").mkdir()
108
+ (self.proj / ".c3" / "config.json").write_text("{}", encoding="utf-8")
109
+ self.svc = _Svc(str(self.proj))
110
+
111
+ def tearDown(self):
112
+ self._tmp.cleanup()
113
+
114
+ def _edit(self, rel, old, new, edits=""):
115
+ return handle_edit(rel, old, new, summary="", tags="",
116
+ replace_all=False, svc=self.svc,
117
+ finalize=_finalize, edits=edits)
118
+
119
+ def test_edit_config_json_refused(self):
120
+ resp = self._edit(".c3/config.json", "{}", '{"x": 1}')
121
+ self.assertIn("[c3:vault-protected]", resp)
122
+ text = (self.proj / ".c3" / "config.json").read_text(encoding="utf-8")
123
+ self.assertEqual(text, "{}")
124
+
125
+ def test_create_mode_refused_for_missing_vault_file(self):
126
+ resp = self._edit(".c3/cred_state.json", "", '{"pwned": true}')
127
+ self.assertIn("[c3:vault-protected]", resp)
128
+ self.assertFalse((self.proj / ".c3" / "cred_state.json").exists())
129
+
130
+ def test_batch_mode_refused(self):
131
+ edits = json.dumps([{"old_string": "{}", "new_string": "{}"}])
132
+ resp = self._edit(".c3/config.json", "", "", edits=edits)
133
+ self.assertIn("[c3:vault-protected]", resp)
134
+
135
+ def test_secrets_enc_refused(self):
136
+ resp = self._edit(".c3/secrets.enc", "", "AAAA")
137
+ self.assertIn("[c3:vault-protected]", resp)
138
+
139
+ def test_absolute_path_refused(self):
140
+ resp = self._edit(str(self.proj / ".c3" / "config.json"), "{}", "{ }")
141
+ self.assertIn("[c3:vault-protected]", resp)
142
+
143
+ def test_case_insensitive_match(self):
144
+ resp = self._edit(".C3/Config.JSON", "{}", "{ }")
145
+ self.assertIn("[c3:vault-protected]", resp)
146
+
147
+ def test_normal_file_still_editable(self):
148
+ (self.proj / "a.txt").write_text("hello", encoding="utf-8")
149
+ resp = self._edit("a.txt", "hello", "world")
150
+ self.assertIn("✓", resp)
151
+ self.assertEqual((self.proj / "a.txt").read_text(encoding="utf-8"), "world")
152
+
153
+ def test_config_json_outside_c3_dir_not_guarded(self):
154
+ (self.proj / "config.json").write_text("{}", encoding="utf-8")
155
+ resp = self._edit("config.json", "{}", '{"ok": 1}')
156
+ self.assertIn("✓", resp)
157
+
158
+
159
+ class TestHookVaultGuard(unittest.TestCase):
160
+ def setUp(self):
161
+ self._tmp = tempfile.TemporaryDirectory()
162
+ self.proj = Path(self._tmp.name)
163
+ (self.proj / ".c3").mkdir()
164
+
165
+ def tearDown(self):
166
+ self._tmp.cleanup()
167
+
168
+ def _run(self, tool_name, file_path):
169
+ payload = {"tool_name": tool_name, "tool_input": {"file_path": file_path}}
170
+ return hook.run(payload, project_path=self.proj)
171
+
172
+ def _assert_vault_deny(self, out):
173
+ self.assertIsNotNone(out)
174
+ decision = out.get("hookSpecificOutput", {})
175
+ self.assertEqual(decision.get("permissionDecision"), "deny")
176
+ self.assertIn("[c3:vault-protected]",
177
+ decision.get("permissionDecisionReason", ""))
178
+
179
+ def test_write_tools_denied_on_vault_files(self):
180
+ vault = str(self.proj / ".c3" / "config.json")
181
+ for tool in ("Edit", "Write", "MultiEdit"):
182
+ self._assert_vault_deny(self._run(tool, vault))
183
+
184
+ def test_denied_even_when_c3_prereq_satisfied(self):
185
+ # Force the unlock check to allow — the vault guard must fire first.
186
+ with mock.patch.object(hook, "_check_c3_used",
187
+ return_value=(True, "signal")) as checked:
188
+ out = self._run("Write", str(self.proj / ".c3" / "config.json"))
189
+ self._assert_vault_deny(out)
190
+ checked.assert_not_called()
191
+
192
+ def test_read_of_vault_file_not_vault_denied(self):
193
+ out = self._run("Read", str(self.proj / ".c3" / "config.json"))
194
+ reason = (out or {}).get("hookSpecificOutput", {}).get(
195
+ "permissionDecisionReason", "")
196
+ self.assertNotIn("[c3:vault-protected]", reason)
197
+
198
+ def test_non_vault_write_gets_normal_enforcement(self):
199
+ out = self._run("Write", str(self.proj / "notes.txt"))
200
+ decision = (out or {}).get("hookSpecificOutput", {})
201
+ self.assertEqual(decision.get("permissionDecision"), "deny")
202
+ self.assertIn("[c3:enforce]", decision.get("permissionDecisionReason", ""))
203
+
204
+
205
+ class TestRevealIntegrity(unittest.TestCase):
206
+ def setUp(self):
207
+ self._stub = _StubKeyring()
208
+ self._tmp_proj = tempfile.TemporaryDirectory()
209
+ self._tmp_home = tempfile.TemporaryDirectory()
210
+ self._patchers = [
211
+ mock.patch.object(cs, "_keyring_module", return_value=self._stub),
212
+ mock.patch.object(cs, "_crypto_module", return_value=_StubFernet),
213
+ mock.patch.object(cs, "_global_base", return_value=Path(self._tmp_home.name)),
214
+ ]
215
+ for p in self._patchers:
216
+ p.start()
217
+ cs._ACTIVE_SECRETS.clear()
218
+ self.svc = _Svc(self._tmp_proj.name)
219
+
220
+ def tearDown(self):
221
+ for p in self._patchers:
222
+ p.stop()
223
+ cs._ACTIVE_SECRETS.clear()
224
+ self._tmp_proj.cleanup()
225
+ self._tmp_home.cleanup()
226
+
227
+ def _creds(self, action, **kwargs):
228
+ return handle_credentials(action, self.svc, _finalize, **kwargs)
229
+
230
+ def _tamper_flag(self, name, value=True):
231
+ cfg_path = Path(self.svc.project_path) / ".c3" / "config.json"
232
+ config = json.loads(cfg_path.read_text(encoding="utf-8"))
233
+ config["credentials"]["entries"][name]["agent_readable"] = value
234
+ cfg_path.write_text(json.dumps(config), encoding="utf-8")
235
+
236
+ def test_tampered_registry_flag_fails_closed(self):
237
+ self._creds("set", name="API_KEY", value="s3cret")
238
+ self.assertIn("[creds:not-readable]", self._creds("reveal", name="API_KEY"))
239
+ self._tamper_flag("API_KEY")
240
+ resp = self._creds("reveal", name="API_KEY")
241
+ self.assertIn("[creds:integrity]", resp)
242
+ self.assertNotIn("s3cret", resp)
243
+
244
+ def test_creation_time_agent_readable_reveals(self):
245
+ self._creds("set", name="MINE", value="agent-made", agent_readable=True)
246
+ resp = self._creds("reveal", name="MINE")
247
+ self.assertIn("[creds:reveal]", resp)
248
+ self.assertIn("agent-made", resp)
249
+
250
+ def test_user_toggle_via_update_metadata_reveals(self):
251
+ self._creds("set", name="TOKEN", value="tok-1")
252
+ cs.update_metadata("TOKEN", scope="project",
253
+ project_path=self.svc.project_path,
254
+ agent_readable=True)
255
+ resp = self._creds("reveal", name="TOKEN")
256
+ self.assertIn("[creds:reveal]", resp)
257
+ self.assertIn("tok-1", resp)
258
+
259
+ def test_missing_attestation_fails_closed(self):
260
+ # Legacy entry: registry says readable but no keyring attestation.
261
+ self._creds("set", name="OLD", value="v", agent_readable=True)
262
+ flag_keys = [k for k in self._stub.store if k[1].endswith("::agent_readable")]
263
+ for k in flag_keys:
264
+ del self._stub.store[k]
265
+ self.assertIn("[creds:integrity]", self._creds("reveal", name="OLD"))
266
+
267
+ def test_delete_removes_attestation(self):
268
+ self._creds("set", name="GONE", value="v", agent_readable=True)
269
+ self.assertTrue(
270
+ any(k[1].endswith("::agent_readable") for k in self._stub.store))
271
+ self._creds("delete", name="GONE", scope="project")
272
+ self.assertFalse(
273
+ any(k[1].endswith("::agent_readable") for k in self._stub.store))
274
+
275
+ def test_verify_false_on_keyring_failure(self):
276
+ self._creds("set", name="KR", value="v", agent_readable=True)
277
+ with mock.patch.object(cs, "_keyring_module",
278
+ side_effect=RuntimeError("keyring down")):
279
+ self.assertFalse(cs.verify_agent_readable(
280
+ "KR", scope="project", project_path=self.svc.project_path))
281
+
282
+
283
+ if __name__ == "__main__":
284
+ unittest.main()