dotscope 1.2.2__tar.gz → 1.3.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 (264) hide show
  1. dotscope-1.3.0/.claude/settings.local.json +7 -0
  2. dotscope-1.3.0/.mcp.json +8 -0
  3. dotscope-1.3.0/.virtual/isolated-hubs.scope +14 -0
  4. {dotscope-1.2.2 → dotscope-1.3.0}/PKG-INFO +3 -3
  5. {dotscope-1.2.2 → dotscope-1.3.0}/docs/mcp-setup.md +80 -2
  6. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/__init__.py +1 -1
  7. dotscope-1.3.0/dotscope/cli/__init__.py +320 -0
  8. dotscope-1.3.0/dotscope/cli/core.py +214 -0
  9. dotscope-1.3.0/dotscope/cli/hooks.py +491 -0
  10. dotscope-1.3.0/dotscope/cli/ingest.py +243 -0
  11. dotscope-1.3.0/dotscope/cli/observability.py +342 -0
  12. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/composer.py +3 -1
  13. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/discovery.py +0 -21
  14. dotscope-1.3.0/dotscope/explain.py +189 -0
  15. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/health.py +76 -12
  16. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/help.py +2 -0
  17. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/ingest.py +3 -1
  18. dotscope-1.3.0/dotscope/mcp/__init__.py +139 -0
  19. dotscope-1.3.0/dotscope/mcp/core.py +459 -0
  20. dotscope-1.3.0/dotscope/mcp/hooks.py +8 -0
  21. dotscope-1.3.0/dotscope/mcp/ingest.py +99 -0
  22. dotscope-1.3.0/dotscope/mcp/observability.py +188 -0
  23. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/models/intent.py +2 -0
  24. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/models/state.py +1 -0
  25. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/convention_discovery.py +40 -0
  26. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/checks/antipattern.py +2 -0
  27. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/checks/boundary.py +2 -0
  28. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/checks/contracts.py +4 -0
  29. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/checks/convention.py +2 -0
  30. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/checks/direction.py +2 -0
  31. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/checks/intent.py +8 -0
  32. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/checks/network.py +2 -0
  33. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/checks/spatial.py +2 -0
  34. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/checks/stability.py +2 -0
  35. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/checks/voice.py +4 -0
  36. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/virtual.py +1 -1
  37. dotscope-1.3.0/dotscope/paths/repo.py +24 -0
  38. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/refresh.py +135 -4
  39. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/resolver.py +2 -2
  40. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/runtime_overlay.py +1 -1
  41. dotscope-1.3.0/dotscope/storage/mcp_config.py +216 -0
  42. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/tokens.py +1 -1
  43. dotscope-1.3.0/epic.yaml +75 -0
  44. dotscope-1.3.0/intent.yaml +42 -0
  45. {dotscope-1.2.2 → dotscope-1.3.0}/pyproject.toml +3 -3
  46. dotscope-1.3.0/run_epic.py +108 -0
  47. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_health.py +1 -1
  48. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_ingest.py +1 -1
  49. dotscope-1.3.0/tests/test_mcp_config.py +153 -0
  50. dotscope-1.3.0/tests/test_mcp_server.py +67 -0
  51. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_parser.py +1 -1
  52. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_virtual.py +1 -1
  53. dotscope-1.3.0/virtual/_treesitter_lifecycle/.scope +23 -0
  54. dotscope-1.3.0/virtual/absorber_lifecycle/.scope +33 -0
  55. dotscope-1.3.0/virtual/acknowledge_lifecycle/.scope +52 -0
  56. dotscope-1.3.0/virtual/ast_analyzer_lifecycle/.scope +30 -0
  57. dotscope-1.3.0/virtual/budget_lifecycle/.scope +25 -0
  58. dotscope-1.3.0/virtual/constants_lifecycle/.scope +32 -0
  59. dotscope-1.3.0/virtual/constraints_lifecycle/.scope +64 -0
  60. dotscope-1.3.0/virtual/context_lifecycle/.scope +79 -0
  61. dotscope-1.3.0/virtual/core_lifecycle/.scope +55 -0
  62. dotscope-1.3.0/virtual/counterfactual_lifecycle/.scope +27 -0
  63. dotscope-1.3.0/virtual/go_lifecycle/.scope +23 -0
  64. dotscope-1.3.0/virtual/harness_lifecycle/.scope +25 -0
  65. dotscope-1.3.0/virtual/history_lifecycle/.scope +51 -0
  66. dotscope-1.3.0/virtual/ignore_lifecycle/.scope +21 -0
  67. dotscope-1.3.0/virtual/incremental_lifecycle/.scope +43 -0
  68. dotscope-1.3.0/virtual/incremental_state_lifecycle/.scope +47 -0
  69. dotscope-1.3.0/virtual/intent_lifecycle/.scope +106 -0
  70. dotscope-1.3.0/virtual/line_filter_lifecycle/.scope +22 -0
  71. dotscope-1.3.0/virtual/matcher_lifecycle/.scope +53 -0
  72. dotscope-1.3.0/virtual/merge_lifecycle/.scope +40 -0
  73. dotscope-1.3.0/virtual/models_lifecycle/.scope +64 -0
  74. dotscope-1.3.0/virtual/parser_lifecycle/.scope +103 -0
  75. dotscope-1.3.0/virtual/refresh_lifecycle/.scope +73 -0
  76. dotscope-1.3.0/virtual/resolver_lifecycle/.scope +44 -0
  77. dotscope-1.3.0/virtual/runtime_overlay_lifecycle/.scope +41 -0
  78. dotscope-1.3.0/virtual/sessions_lifecycle/.scope +30 -0
  79. dotscope-1.3.0/virtual/state_lifecycle/.scope +50 -0
  80. dotscope-1.3.0/virtual/textio_lifecycle/.scope +53 -0
  81. dotscope-1.3.0/virtual/utility_lifecycle/.scope +80 -0
  82. dotscope-1.2.2/dotscope/cli.py +0 -1567
  83. dotscope-1.2.2/dotscope/mcp_server.py +0 -1577
  84. dotscope-1.2.2/dotscope/models.py +0 -9
  85. dotscope-1.2.2/dotscope/storage/mcp_config.py +0 -98
  86. dotscope-1.2.2/dotscope/swarm/__init__.py +0 -13
  87. dotscope-1.2.2/dotscope/swarm/merge.py +0 -161
  88. dotscope-1.2.2/dotscope/swarm/partition.py +0 -236
  89. dotscope-1.2.2/dotscope/swarm/trace.py +0 -251
  90. dotscope-1.2.2/tests/test_swarm.py +0 -300
  91. {dotscope-1.2.2 → dotscope-1.3.0}/.claude/hooks/pre-commit-check.sh +0 -0
  92. {dotscope-1.2.2 → dotscope-1.3.0}/.claude/settings.json +0 -0
  93. {dotscope-1.2.2 → dotscope-1.3.0}/.github/workflows/python-publish.yml +0 -0
  94. {dotscope-1.2.2 → dotscope-1.3.0}/.gitignore +0 -0
  95. {dotscope-1.2.2 → dotscope-1.3.0}/.scopes +0 -0
  96. {dotscope-1.2.2 → dotscope-1.3.0}/AGENT_INSTRUCTIONS.md +0 -0
  97. {dotscope-1.2.2 → dotscope-1.3.0}/CLAUDE.md +0 -0
  98. {dotscope-1.2.2 → dotscope-1.3.0}/LICENSE +0 -0
  99. {dotscope-1.2.2 → dotscope-1.3.0}/README.md +0 -0
  100. {dotscope-1.2.2 → dotscope-1.3.0}/docs/architecture.md +0 -0
  101. {dotscope-1.2.2 → dotscope-1.3.0}/docs/cli-reference.md +0 -0
  102. {dotscope-1.2.2 → dotscope-1.3.0}/docs/how-it-works.md +0 -0
  103. {dotscope-1.2.2 → dotscope-1.3.0}/docs/scope-file.md +0 -0
  104. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/.scope +0 -0
  105. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/absorber.py +0 -0
  106. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/assertions.py +0 -0
  107. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/ast_analyzer.py +0 -0
  108. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/backtest.py +0 -0
  109. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/bench.py +0 -0
  110. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/budget.py +0 -0
  111. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/cache.py +0 -0
  112. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/check/__init__.py +0 -0
  113. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/check/acknowledge.py +0 -0
  114. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/check/checker.py +0 -0
  115. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/check/checks/__init__.py +0 -0
  116. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/check/checks/antipattern.py +0 -0
  117. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/check/checks/boundary.py +0 -0
  118. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/check/checks/contracts.py +0 -0
  119. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/check/checks/direction.py +0 -0
  120. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/check/checks/intent.py +0 -0
  121. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/check/checks/stability.py +0 -0
  122. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/check/constraints.py +0 -0
  123. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/check/models.py +0 -0
  124. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/constants.py +0 -0
  125. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/context.py +0 -0
  126. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/counterfactual.py +0 -0
  127. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/debug.py +0 -0
  128. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/eval/.scope +0 -0
  129. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/eval/__init__.py +0 -0
  130. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/eval/bootstrap.py +0 -0
  131. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/eval/compare.py +0 -0
  132. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/eval/corpus.py +0 -0
  133. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/eval/harness.py +0 -0
  134. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/eval/replay.py +0 -0
  135. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/formatter.py +0 -0
  136. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/generate/.scope +0 -0
  137. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/generate/__init__.py +0 -0
  138. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/generate/atlas.py +0 -0
  139. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/generate/contracts.py +0 -0
  140. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/generate/engine.py +0 -0
  141. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/generate/models.py +0 -0
  142. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/generate/network.py +0 -0
  143. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/graph.py +0 -0
  144. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/history.py +0 -0
  145. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/hooks.py +0 -0
  146. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/ignore.py +0 -0
  147. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/intent.py +0 -0
  148. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/lessons.py +0 -0
  149. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/matcher.py +0 -0
  150. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/merge/.scope +0 -0
  151. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/merge/__init__.py +0 -0
  152. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/merge/classifier.py +0 -0
  153. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/merge/composer.py +0 -0
  154. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/merge/differ.py +0 -0
  155. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/merge/driver.py +0 -0
  156. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/merge/imports.py +0 -0
  157. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/merge/models.py +0 -0
  158. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/merge/swarm.py +0 -0
  159. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/models/.scope +0 -0
  160. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/models/__init__.py +0 -0
  161. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/models/core.py +0 -0
  162. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/models/eval.py +0 -0
  163. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/models/history.py +0 -0
  164. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/models/passes.py +0 -0
  165. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/near_miss.py +0 -0
  166. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/onboarding.py +0 -0
  167. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/parser.py +0 -0
  168. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/.scope +0 -0
  169. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/__init__.py +0 -0
  170. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/ast_analyzer.py +0 -0
  171. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/backtest.py +0 -0
  172. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/budget_allocator.py +0 -0
  173. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/convention_compliance.py +0 -0
  174. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/convention_parser.py +0 -0
  175. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/graph_builder.py +0 -0
  176. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/hint_generator.py +0 -0
  177. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/history_miner.py +0 -0
  178. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/incremental.py +0 -0
  179. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/lang/__init__.py +0 -0
  180. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/lang/_base.py +0 -0
  181. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/lang/_treesitter.py +0 -0
  182. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/lang/go.py +0 -0
  183. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/lang/javascript.py +0 -0
  184. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/lazy.py +0 -0
  185. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/preflight.py +0 -0
  186. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/semantic_diff.py +0 -0
  187. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/__init__.py +0 -0
  188. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/acknowledge.py +0 -0
  189. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/checker.py +0 -0
  190. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/checks/__init__.py +0 -0
  191. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/constraints.py +0 -0
  192. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/line_filter.py +0 -0
  193. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/sentinel/models.py +0 -0
  194. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/spatial_autofix.py +0 -0
  195. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/voice.py +0 -0
  196. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/voice_defaults.py +0 -0
  197. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/passes/voice_discovery.py +0 -0
  198. /dotscope-1.2.2/dotscope/paths.py → /dotscope-1.3.0/dotscope/paths/__init__.py +0 -0
  199. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/progress.py +0 -0
  200. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/regression.py +0 -0
  201. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/scanner.py +0 -0
  202. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/search/.scope +0 -0
  203. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/search/__init__.py +0 -0
  204. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/search/chunker.py +0 -0
  205. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/search/expander.py +0 -0
  206. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/search/flattener.py +0 -0
  207. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/search/models.py +0 -0
  208. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/search/observation.py +0 -0
  209. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/search/reranker.py +0 -0
  210. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/search/retriever.py +0 -0
  211. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/search/synthesizer.py +0 -0
  212. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/sessions.py +0 -0
  213. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/storage/.scope +0 -0
  214. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/storage/__init__.py +0 -0
  215. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/storage/cache.py +0 -0
  216. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/storage/claude_hooks.py +0 -0
  217. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/storage/git_hooks.py +0 -0
  218. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/storage/incremental_state.py +0 -0
  219. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/storage/near_miss.py +0 -0
  220. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/storage/onboarding.py +0 -0
  221. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/storage/session_manager.py +0 -0
  222. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/storage/swarm_state.py +0 -0
  223. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/storage/timing.py +0 -0
  224. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/textio.py +0 -0
  225. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/timing.py +0 -0
  226. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/utility.py +0 -0
  227. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/virtual.py +0 -0
  228. {dotscope-1.2.2 → dotscope-1.3.0}/dotscope/visibility.py +0 -0
  229. {dotscope-1.2.2 → dotscope-1.3.0}/logo.png +0 -0
  230. {dotscope-1.2.2 → dotscope-1.3.0}/tests/.scope +0 -0
  231. {dotscope-1.2.2 → dotscope-1.3.0}/tests/__init__.py +0 -0
  232. {dotscope-1.2.2 → dotscope-1.3.0}/tests/conftest.py +0 -0
  233. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_absorber.py +0 -0
  234. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_ast_analyzer.py +0 -0
  235. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_backtest.py +0 -0
  236. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_budget.py +0 -0
  237. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_canonical_snippet.py +0 -0
  238. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_cli.py +0 -0
  239. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_composer.py +0 -0
  240. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_context.py +0 -0
  241. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_enforcement.py +0 -0
  242. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_eval_harness.py +0 -0
  243. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_experience.py +0 -0
  244. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_graph.py +0 -0
  245. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_history.py +0 -0
  246. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_ignore.py +0 -0
  247. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_lessons.py +0 -0
  248. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_line_filter.py +0 -0
  249. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_loop.py +0 -0
  250. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_matcher.py +0 -0
  251. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_near_miss.py +0 -0
  252. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_refresh.py +0 -0
  253. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_resolver.py +0 -0
  254. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_rigor.py +0 -0
  255. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_routing.py +0 -0
  256. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_scanner.py +0 -0
  257. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_sessions.py +0 -0
  258. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_textio.py +0 -0
  259. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_treesitter.py +0 -0
  260. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_utility.py +0 -0
  261. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_visibility.py +0 -0
  262. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_voice_check.py +0 -0
  263. {dotscope-1.2.2 → dotscope-1.3.0}/tests/test_voice_discovery.py +0 -0
  264. {dotscope-1.2.2 → dotscope-1.3.0}/uv.lock +0 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(python -m pytest tests/test_mcp_config.py tests/test_mcp_server.py -v)"
5
+ ]
6
+ }
7
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "dotscope": {
4
+ "type": "stdio",
5
+ "command": "dotscope-mcp"
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,14 @@
1
+ description: Detected dense God-Objects. Emitting directive to isolate hubs.
2
+ includes:
3
+ - dotscope/discovery.py
4
+ - dotscope/models.py
5
+ - dotscope/parser.py
6
+ context: |
7
+ ## Cloud Directive
8
+ This virtual scope isolates objects that cross the semantic failure threshold for AI context.
9
+
10
+ ### Constraints
11
+ - prohibited_imports: *
12
+ tags:
13
+ - virtual
14
+ - federated-directive
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotscope
3
- Version: 1.2.2
3
+ Version: 1.3.0
4
4
  Summary: Agents see files. You see architecture. dotscope gives agents the architecture.
5
5
  Author: Supremum
6
6
  License-Expression: MIT
@@ -17,7 +17,7 @@ Requires-Dist: tree-sitter-javascript>=0.23.0
17
17
  Requires-Dist: tree-sitter-typescript>=0.23.0
18
18
  Requires-Dist: tree-sitter>=0.23.0
19
19
  Provides-Extra: all
20
- Requires-Dist: mcp>=1.2.0; extra == 'all'
20
+ Requires-Dist: mcp<3,>=1.2.0; extra == 'all'
21
21
  Requires-Dist: numpy; extra == 'all'
22
22
  Requires-Dist: sentence-transformers; extra == 'all'
23
23
  Requires-Dist: tiktoken; extra == 'all'
@@ -27,7 +27,7 @@ Requires-Dist: ruff; extra == 'dev'
27
27
  Provides-Extra: embeddings
28
28
  Requires-Dist: sentence-transformers; extra == 'embeddings'
29
29
  Provides-Extra: mcp
30
- Requires-Dist: mcp>=1.2.0; extra == 'mcp'
30
+ Requires-Dist: mcp<3,>=1.2.0; extra == 'mcp'
31
31
  Provides-Extra: search
32
32
  Requires-Dist: numpy; extra == 'search'
33
33
  Requires-Dist: sentence-transformers; extra == 'search'
@@ -2,18 +2,25 @@
2
2
 
3
3
  dotscope's primary interface for agents is an MCP server. The agent calls `resolve_scope` to get files, context, and constraints. dotscope tracks every call for the feedback loop.
4
4
 
5
+ **Supported clients:** Claude Desktop, Claude Code, Cursor, Windsurf, VS Code Copilot, OpenAI Codex CLI, JetBrains AI, Zed
6
+
5
7
  ## Install
6
8
 
7
9
  ```bash
8
10
  pip install dotscope[mcp]
9
11
  ```
10
12
 
13
+ ## Quick Setup
14
+
15
+ Run `dotscope init` in your project root. It auto-detects installed editors and writes config for each one.
16
+
11
17
  ## Claude Desktop
12
18
 
13
19
  Edit your config file:
14
20
 
15
21
  **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
16
22
  **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
23
+ **Linux:** `~/.config/Claude/claude_desktop_config.json`
17
24
 
18
25
  ```json
19
26
  {
@@ -30,12 +37,13 @@ Omit `--root` to use the current working directory. Restart Claude Desktop after
30
37
 
31
38
  ## Claude Code
32
39
 
33
- Add to your project's `.claude/settings.json` or the global settings:
40
+ Add a `.mcp.json` file to your project root (preferred):
34
41
 
35
42
  ```json
36
43
  {
37
44
  "mcpServers": {
38
45
  "dotscope": {
46
+ "type": "stdio",
39
47
  "command": "dotscope-mcp"
40
48
  }
41
49
  }
@@ -46,7 +54,7 @@ No `--root` needed. Claude Code launches from the project directory.
46
54
 
47
55
  ## Cursor
48
56
 
49
- Settings > MCP Servers:
57
+ Add to `.cursor/mcp.json` in your project root:
50
58
 
51
59
  ```json
52
60
  {
@@ -57,6 +65,76 @@ Settings > MCP Servers:
57
65
  }
58
66
  ```
59
67
 
68
+ Note: Cursor uses a flat structure (no `mcpServers` wrapper).
69
+
70
+ ## Windsurf
71
+
72
+ Edit `~/.codeium/windsurf/mcp_config.json` (global config):
73
+
74
+ ```json
75
+ {
76
+ "mcpServers": {
77
+ "dotscope": {
78
+ "command": "dotscope-mcp",
79
+ "args": ["--root", "/path/to/your/project"]
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ ## VS Code Copilot
86
+
87
+ Add to `.vscode/mcp.json` in your project root:
88
+
89
+ ```json
90
+ {
91
+ "servers": {
92
+ "dotscope": {
93
+ "command": "dotscope-mcp"
94
+ }
95
+ }
96
+ }
97
+ ```
98
+
99
+ Note: VS Code uses `servers` as the top-level key (not `mcpServers`).
100
+
101
+ ## OpenAI Codex CLI
102
+
103
+ Add to `.codex/config.toml` in your project root (or `~/.codex/config.toml` for global):
104
+
105
+ ```toml
106
+ [mcp_servers.dotscope]
107
+ command = "dotscope-mcp"
108
+ ```
109
+
110
+ ## JetBrains AI
111
+
112
+ Configure manually via the IDE:
113
+
114
+ 1. Open **Settings > Tools > AI Assistant > Model Context Protocol (MCP)**
115
+ 2. Add a new local server:
116
+ - **Command:** `dotscope-mcp`
117
+ - **Arguments:** `--root /path/to/your/project`
118
+ 3. Click OK and restart the AI Assistant
119
+
120
+ ## Zed
121
+
122
+ Add to `~/.config/zed/settings.json`:
123
+
124
+ ```json
125
+ {
126
+ "context_servers": {
127
+ "dotscope": {
128
+ "source": "custom",
129
+ "command": "dotscope-mcp",
130
+ "args": ["--root", "/path/to/your/project"]
131
+ }
132
+ }
133
+ }
134
+ ```
135
+
136
+ Or use Agent Panel (Cmd+Shift+A) > Settings > "Add Custom Server".
137
+
60
138
  ## Verifying
61
139
 
62
140
  Ask your agent: "What scopes are available?" It should call `list_scopes` and show what dotscope generated during ingest.
@@ -1,3 +1,3 @@
1
1
  """dotscope — Directory-scoped context boundaries for AI coding agents."""
2
2
 
3
- __version__ = "1.2.2"
3
+ __version__ = "1.3.0"
@@ -0,0 +1,320 @@
1
+ from .core import _cmd_resolve, _cmd_context, _cmd_match, _cmd_init, _cmd_intent, _cmd_utility
2
+ from .observability import _cmd_stats, _cmd_tree, _cmd_health, _cmd_validate, _cmd_virtual, _cmd_lessons, _cmd_invariants, _cmd_rebuild, _cmd_test_compiler, _cmd_bench, _cmd_debug
3
+ from .ingest import _cmd_ingest, _cmd_impact, _cmd_backtest, _cmd_conventions, _cmd_diff
4
+ from .hooks import _cmd_observe, _cmd_incremental, _cmd_hook, _cmd_refresh, _cmd_check, _cmd_check_backtest, _cmd_voice
5
+
6
+
7
+ """CLI entry point for dotscope."""
8
+
9
+ import argparse
10
+
11
+ import os
12
+
13
+ import sys
14
+
15
+ def _safe_print(text, **kwargs):
16
+ """Print with ASCII fallback for Windows cp1252 terminals."""
17
+ try:
18
+ print(text, **kwargs)
19
+ except UnicodeEncodeError:
20
+ print(text.encode("ascii", errors="replace").decode("ascii"), **kwargs)
21
+
22
+ def main(argv=None):
23
+ from ..textio import consume_decode_warnings
24
+
25
+ consume_decode_warnings()
26
+
27
+ # Intercept help before argparse touches it
28
+ args_list = argv if argv is not None else sys.argv[1:]
29
+ if not args_list or args_list == ["help"] or args_list == ["--help"] or args_list == ["-h"]:
30
+ from ..help import print_help
31
+ print_help()
32
+ return
33
+ if len(args_list) >= 2 and args_list[1] in ("--help", "-h"):
34
+ from ..help import print_help, HELP_COMMANDS
35
+ cmd = args_list[0]
36
+ if cmd in HELP_COMMANDS:
37
+ print_help(cmd)
38
+ return
39
+ # Fall through to argparse for unknown commands
40
+
41
+ parser = argparse.ArgumentParser(
42
+ prog="dotscope",
43
+ description="Directory-scoped context boundaries for AI coding agents",
44
+ add_help=False,
45
+ )
46
+ parser.add_argument("--version", action="version", version=f"%(prog)s {_version()}")
47
+ parser.add_argument("-h", "--help", action="store_true", dest="show_help")
48
+
49
+ sub = parser.add_subparsers(dest="command")
50
+
51
+ # --- resolve ---
52
+ p_resolve = sub.add_parser("resolve", help="Resolve a scope expression to files")
53
+ p_resolve.add_argument("scope", help="Scope name, path, or expression (e.g., auth+payments)")
54
+ p_resolve.add_argument("--budget", type=int, default=None, help="Max tokens (context + files)")
55
+ p_resolve.add_argument("--tokens", action="store_true", help="Show per-file token counts")
56
+ p_resolve.add_argument("--json", action="store_true", help="Output as JSON")
57
+ p_resolve.add_argument("--cursor", action="store_true", help="Output as .cursorrules format")
58
+ p_resolve.add_argument("--no-related", action="store_true", help="Don't follow related scopes")
59
+ p_resolve.add_argument("--task", default=None, help="Task description for relevance ranking")
60
+
61
+ # --- context ---
62
+ p_context = sub.add_parser("context", help="Print context for a scope")
63
+ p_context.add_argument("scope", help="Scope name or path")
64
+ p_context.add_argument("--section", default=None, help="Filter to a named section")
65
+
66
+ # --- match ---
67
+ p_match = sub.add_parser("match", help="Match a task description to scope(s)")
68
+ p_match.add_argument("task", help="Task description string")
69
+
70
+ # --- init ---
71
+ p_init = sub.add_parser("init", help="One command: ingest, install hooks, configure agents")
72
+ p_init.add_argument("path", nargs="?", default=".", help="Repository root")
73
+ p_init.add_argument("--quiet", action="store_true", help="Suppress progress (for CI)")
74
+
75
+ # --- validate ---
76
+ sub.add_parser("validate", help="Check all .scope files for broken paths")
77
+
78
+ # --- stats ---
79
+ sub.add_parser("stats", help="Token savings report across all scopes")
80
+
81
+ # --- tree ---
82
+ sub.add_parser("tree", help="Visual tree of all scopes and relationships")
83
+
84
+ # --- health ---
85
+ sub.add_parser("health", help="Scope health: staleness, coverage, drift")
86
+
87
+ # --- ingest ---
88
+ p_ingest = sub.add_parser("ingest", help="Reverse-engineer .scope files from an existing codebase")
89
+ p_ingest.add_argument("--dir", default=".", help="Repository root to ingest")
90
+ p_ingest.add_argument("--no-history", action="store_true", help="Skip git history mining")
91
+ p_ingest.add_argument("--no-docs", action="store_true", help="Skip doc absorption")
92
+ p_ingest.add_argument("--dry-run", action="store_true", help="Plan only, don't write files")
93
+ p_ingest.add_argument("--max-commits", type=int, default=500, help="Max git commits to analyze")
94
+ p_ingest.add_argument("--quiet", action="store_true", help="Suppress progress output (for CI)")
95
+ p_ingest.add_argument("--voice", choices=["prescriptive", "adaptive"], default=None,
96
+ help="Override voice mode (prescriptive for new, adaptive for existing)")
97
+
98
+ # --- voice ---
99
+ p_voice = sub.add_parser("voice", help="View and manage code voice")
100
+ p_voice.add_argument("--upgrade", metavar="RULE", help="Upgrade enforcement for a rule (typing, bare_excepts)")
101
+ p_voice.add_argument("--reset", action="store_true", help="Reset voice to defaults")
102
+ p_voice.add_argument("--json", action="store_true", help="Machine-readable output")
103
+
104
+ # --- impact ---
105
+ p_impact = sub.add_parser("impact", help="Predict blast radius of changes to a file")
106
+ p_impact.add_argument("file", help="File path to analyze impact for")
107
+
108
+ # --- backtest ---
109
+ p_backtest = sub.add_parser("backtest", help="Validate scopes against git history")
110
+ p_backtest.add_argument("--commits", type=int, default=50, help="Number of commits to test against")
111
+
112
+ # --- observe ---
113
+ p_observe = sub.add_parser("observe", help="Record observation for a commit (called by post-commit hook)")
114
+ p_observe.add_argument("commit", help="Commit hash to observe")
115
+
116
+ # --- incremental ---
117
+ p_incremental = sub.add_parser("incremental", help="Incremental scope update (called by post-commit hook)")
118
+ p_incremental.add_argument("commit", help="Commit hash")
119
+
120
+ # --- hook ---
121
+ p_hook = sub.add_parser("hook", help="Manage git hooks")
122
+ hook_sub = p_hook.add_subparsers(dest="hook_action")
123
+ hook_sub.add_parser("install", help="Install post-commit observer hook")
124
+ hook_sub.add_parser("uninstall", help="Remove post-commit observer hook")
125
+ hook_sub.add_parser("status", help="Check if hook is installed")
126
+ hook_sub.add_parser("claude", help="Install Claude Code pre-commit enforcement")
127
+
128
+ # --- refresh ---
129
+ p_refresh = sub.add_parser("refresh", help="Refresh scopes (synchronous by default)")
130
+ p_refresh.add_argument("scopes", nargs="*", help="Scope names to refresh (omit for full repo)")
131
+ p_refresh.add_argument("--repo", action="store_true", help="Force full repo refresh")
132
+ p_refresh.add_argument("--async", dest="run_async", action="store_true", help="Queue and return (legacy async mode)")
133
+ refresh_sub = p_refresh.add_subparsers(dest="refresh_action")
134
+ p_refresh_enqueue = refresh_sub.add_parser("enqueue", help="Queue runtime refresh work")
135
+ p_refresh_enqueue.add_argument("scopes", nargs="*", help="Scope names to refresh")
136
+ p_refresh_enqueue.add_argument("--commit", default=None, help="Classify a commit into refresh work")
137
+ p_refresh_enqueue.add_argument("--repo", action="store_true", help="Enqueue a full repo runtime refresh")
138
+ p_refresh_enqueue.add_argument("--reason", default="", help="Reason stored with the queued job")
139
+ p_refresh_run = refresh_sub.add_parser("run", help="Run queued refresh work")
140
+ p_refresh_run.add_argument("--drain", action="store_true", help="Drain the entire queue")
141
+ refresh_sub.add_parser("status", help="Show refresh worker and queue status")
142
+
143
+ # --- utility ---
144
+ p_utility = sub.add_parser("utility", help="Show utility scores for a scope")
145
+ p_utility.add_argument("scope", help="Scope name")
146
+
147
+ # --- virtual ---
148
+ sub.add_parser("virtual", help="Detect and show virtual (cross-cutting) scopes")
149
+
150
+ # --- lessons ---
151
+ p_lessons = sub.add_parser("lessons", help="Show lessons for a scope")
152
+ p_lessons.add_argument("scope", help="Scope name")
153
+
154
+ # --- invariants ---
155
+ p_invariants = sub.add_parser("invariants", help="Show observed invariants for a scope")
156
+ p_invariants.add_argument("scope", help="Scope name")
157
+
158
+ # --- rebuild ---
159
+ sub.add_parser("rebuild", help="Rebuild derived state from event logs")
160
+
161
+ # --- check ---
162
+ p_check = sub.add_parser("check", help="Validate a diff against codebase rules")
163
+ p_check.add_argument("--diff", default=None, help="Path to diff file (default: staged changes)")
164
+ p_check.add_argument("--session", default=None, help="Session ID for boundary checking")
165
+ p_check.add_argument("--acknowledge", action="append", default=[], help="Acknowledge a hold by ID")
166
+ p_check.add_argument("--backtest", action="store_true", help="Replay recent commits against checks")
167
+ p_check.add_argument("--commits", type=int, default=10, help="Commits to replay in backtest mode")
168
+ p_check.add_argument("--json", dest="json_output", action="store_true", help="Output as JSON")
169
+ p_check.add_argument("--explain", action="store_true", help="Show full provenance for each finding")
170
+
171
+ # --- intent ---
172
+ p_intent = sub.add_parser("intent", help="Manage architectural intents")
173
+ intent_sub = p_intent.add_subparsers(dest="intent_action")
174
+ p_intent_add = intent_sub.add_parser("add", help="Add an architectural intent")
175
+ p_intent_add.add_argument("directive", choices=["decouple", "deprecate", "freeze", "consolidate"])
176
+ p_intent_add.add_argument("targets", nargs="+", help="Modules or files")
177
+ p_intent_add.add_argument("--reason", default="", help="Why this intent exists")
178
+ p_intent_add.add_argument("--replacement", default=None, help="Replacement (for deprecate)")
179
+ p_intent_add.add_argument("--target", default=None, help="Consolidation target")
180
+ p_intent_list = intent_sub.add_parser("list", help="List all intents")
181
+ p_intent_rm = intent_sub.add_parser("remove", help="Remove an intent by ID")
182
+ p_intent_rm.add_argument("id", help="Intent ID to remove")
183
+
184
+ # --- conventions ---
185
+ p_conv = sub.add_parser("conventions", help="List or discover conventions")
186
+ p_conv.add_argument("--discover", action="store_true", help="Discover conventions from codebase")
187
+ p_conv.add_argument("--accept", action="store_true", help="Accept discovered conventions")
188
+ p_conv.add_argument("--json", dest="json_output", action="store_true", help="Output as JSON")
189
+
190
+ # --- diff ---
191
+ p_diff = sub.add_parser("diff", help="Semantic diff against conventions")
192
+ p_diff.add_argument("ref", nargs="?", default=None, help="Git ref to diff against")
193
+ p_diff.add_argument("--staged", action="store_true", help="Diff staged changes")
194
+ p_diff.add_argument("--json", dest="json_output", action="store_true", help="Output as JSON")
195
+
196
+ # --- test-compiler ---
197
+ p_tc = sub.add_parser("test-compiler", help="Replay frozen sessions as regression tests")
198
+ p_tc.add_argument("--scope", default=None, help="Filter to a specific scope")
199
+
200
+ # --- bench ---
201
+ p_bench = sub.add_parser("bench", help="Performance and accuracy benchmarks")
202
+ p_bench.add_argument("--json", dest="json_output", action="store_true", help="JSON output")
203
+
204
+ # --- debug ---
205
+ p_debug = sub.add_parser("debug", help="Bisect a bad session to find root cause")
206
+ p_debug.add_argument("session_id", nargs="?", default=None, help="Session ID to debug")
207
+ p_debug.add_argument("--last", action="store_true", help="Debug most recent bad session")
208
+ p_debug.add_argument("--list", dest="list_bad", action="store_true", help="List bad sessions")
209
+
210
+ args = parser.parse_args(argv)
211
+
212
+ if args.command is None or getattr(args, "show_help", False):
213
+ from ..help import print_help
214
+ print_help()
215
+ return
216
+
217
+ try:
218
+ handler = {
219
+ "resolve": _cmd_resolve,
220
+ "context": _cmd_context,
221
+ "match": _cmd_match,
222
+ "init": _cmd_init,
223
+ "validate": _cmd_validate,
224
+ "stats": _cmd_stats,
225
+ "tree": _cmd_tree,
226
+ "health": _cmd_health,
227
+ "ingest": _cmd_ingest,
228
+ "impact": _cmd_impact,
229
+ "backtest": _cmd_backtest,
230
+ "observe": _cmd_observe,
231
+ "incremental": _cmd_incremental,
232
+ "hook": _cmd_hook,
233
+ "refresh": _cmd_refresh,
234
+ "utility": _cmd_utility,
235
+ "virtual": _cmd_virtual,
236
+ "lessons": _cmd_lessons,
237
+ "invariants": _cmd_invariants,
238
+ "rebuild": _cmd_rebuild,
239
+ "check": _cmd_check,
240
+ "intent": _cmd_intent,
241
+ "conventions": _cmd_conventions,
242
+ "diff": _cmd_diff,
243
+ "voice": _cmd_voice,
244
+ "test-compiler": _cmd_test_compiler,
245
+ "bench": _cmd_bench,
246
+ "debug": _cmd_debug,
247
+ }[args.command]
248
+ handler(args)
249
+ except (ValueError, FileNotFoundError) as e:
250
+ print(f"Error: {e}", file=sys.stderr)
251
+ sys.exit(1)
252
+ finally:
253
+ warnings = consume_decode_warnings()
254
+ if warnings:
255
+ count = len(warnings)
256
+ noun = "file" if count == 1 else "files"
257
+ _safe_print(
258
+ f"dotscope: decoded {count} repo {noun} with replacement; "
259
+ "run `dotscope health` for details",
260
+ file=sys.stderr,
261
+ )
262
+
263
+ def _print_counterfactual(ingest_result, backtest, violations):
264
+ """Format init output as a counterfactual story."""
265
+ scopes = ingest_result.get("scopes_written", 0) if isinstance(ingest_result, dict) else 0
266
+ contracts = ingest_result.get("contracts_found", 0) if isinstance(ingest_result, dict) else 0
267
+ conventions = ingest_result.get("conventions_found", 0) if isinstance(ingest_result, dict) else 0
268
+
269
+ recall = backtest.get("overall_recall", 0)
270
+
271
+ lines = [""]
272
+ lines.append(f" {scopes} scopes, {contracts} contracts, {conventions} conventions, {recall:.0%} recall")
273
+ lines.append("")
274
+
275
+ if violations > 0:
276
+ lines.append(f" What dotscope would have caught in your last 50 commits:")
277
+ lines.append(f" {violations} files that agents would have missed")
278
+ lines.append("")
279
+ lines.append(" Your agents are ready.")
280
+ lines.append("")
281
+
282
+ print("\n".join(lines), file=sys.stderr)
283
+
284
+ def _write_agent_instructions(root: str, quiet: bool = False):
285
+ """Write AGENT_INSTRUCTIONS.md to the target repo if it doesn't exist."""
286
+ target = os.path.join(root, "AGENT_INSTRUCTIONS.md")
287
+ if os.path.exists(target):
288
+ if not quiet:
289
+ print("dotscope: AGENT_INSTRUCTIONS.md already exists, skipping", file=sys.stderr)
290
+ return
291
+
292
+ # Load the template from the dotscope package
293
+ package_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
294
+ template = os.path.join(package_dir, "AGENT_INSTRUCTIONS.md")
295
+
296
+ if os.path.isfile(template):
297
+ import shutil
298
+ shutil.copy2(template, target)
299
+ else:
300
+ # Fallback: write a minimal version
301
+ with open(target, "w", encoding="utf-8") as f:
302
+ f.write("# dotscope Agent Instructions\n\n")
303
+ f.write("Start every task with `codebase_search`. Run `dotscope_check` before every commit.\n\n")
304
+ f.write("See https://github.com/nxrobins/dotscope for full documentation.\n")
305
+
306
+ if not quiet:
307
+ print("dotscope: AGENT_INSTRUCTIONS.md written", file=sys.stderr)
308
+
309
+ def _print_summary(ingest_result):
310
+ """Fallback when backtest isn't available."""
311
+ print("", file=sys.stderr)
312
+ print(" Your agents are ready.", file=sys.stderr)
313
+ print("", file=sys.stderr)
314
+
315
+ def _version():
316
+ from .. import __version__
317
+ return __version__
318
+
319
+ if __name__ == "__main__":
320
+ main()