dotscope 0.1.0__py3-none-any.whl

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 (114) hide show
  1. dotscope/.scope +63 -0
  2. dotscope/__init__.py +3 -0
  3. dotscope/absorber.py +390 -0
  4. dotscope/assertions.py +128 -0
  5. dotscope/ast_analyzer.py +2 -0
  6. dotscope/backtest.py +2 -0
  7. dotscope/bench.py +141 -0
  8. dotscope/budget.py +3 -0
  9. dotscope/cache.py +2 -0
  10. dotscope/check/__init__.py +1 -0
  11. dotscope/check/acknowledge.py +2 -0
  12. dotscope/check/checker.py +3 -0
  13. dotscope/check/checks/__init__.py +1 -0
  14. dotscope/check/checks/antipattern.py +2 -0
  15. dotscope/check/checks/boundary.py +2 -0
  16. dotscope/check/checks/contracts.py +3 -0
  17. dotscope/check/checks/direction.py +2 -0
  18. dotscope/check/checks/intent.py +2 -0
  19. dotscope/check/checks/stability.py +2 -0
  20. dotscope/check/constraints.py +2 -0
  21. dotscope/check/models.py +15 -0
  22. dotscope/cli.py +1447 -0
  23. dotscope/composer.py +147 -0
  24. dotscope/constants.py +45 -0
  25. dotscope/context.py +60 -0
  26. dotscope/counterfactual.py +180 -0
  27. dotscope/debug.py +220 -0
  28. dotscope/discovery.py +104 -0
  29. dotscope/formatter.py +157 -0
  30. dotscope/graph.py +3 -0
  31. dotscope/health.py +212 -0
  32. dotscope/help.py +204 -0
  33. dotscope/history.py +6 -0
  34. dotscope/hooks.py +2 -0
  35. dotscope/ingest.py +858 -0
  36. dotscope/intent.py +618 -0
  37. dotscope/lessons.py +223 -0
  38. dotscope/matcher.py +104 -0
  39. dotscope/mcp_server.py +1081 -0
  40. dotscope/models/.scope +45 -0
  41. dotscope/models/__init__.py +7 -0
  42. dotscope/models/core.py +288 -0
  43. dotscope/models/history.py +73 -0
  44. dotscope/models/intent.py +213 -0
  45. dotscope/models/passes.py +58 -0
  46. dotscope/models/state.py +250 -0
  47. dotscope/models.py +9 -0
  48. dotscope/near_miss.py +3 -0
  49. dotscope/onboarding.py +2 -0
  50. dotscope/parser.py +387 -0
  51. dotscope/passes/.scope +105 -0
  52. dotscope/passes/__init__.py +1 -0
  53. dotscope/passes/ast_analyzer.py +508 -0
  54. dotscope/passes/backtest.py +198 -0
  55. dotscope/passes/budget_allocator.py +164 -0
  56. dotscope/passes/convention_compliance.py +40 -0
  57. dotscope/passes/convention_discovery.py +247 -0
  58. dotscope/passes/convention_parser.py +223 -0
  59. dotscope/passes/graph_builder.py +299 -0
  60. dotscope/passes/history_miner.py +336 -0
  61. dotscope/passes/incremental.py +149 -0
  62. dotscope/passes/lang/__init__.py +38 -0
  63. dotscope/passes/lang/_base.py +20 -0
  64. dotscope/passes/lang/_treesitter.py +93 -0
  65. dotscope/passes/lang/go.py +333 -0
  66. dotscope/passes/lang/javascript.py +348 -0
  67. dotscope/passes/lazy.py +152 -0
  68. dotscope/passes/semantic_diff.py +160 -0
  69. dotscope/passes/sentinel/__init__.py +1 -0
  70. dotscope/passes/sentinel/acknowledge.py +222 -0
  71. dotscope/passes/sentinel/checker.py +383 -0
  72. dotscope/passes/sentinel/checks/__init__.py +1 -0
  73. dotscope/passes/sentinel/checks/antipattern.py +84 -0
  74. dotscope/passes/sentinel/checks/boundary.py +46 -0
  75. dotscope/passes/sentinel/checks/contracts.py +148 -0
  76. dotscope/passes/sentinel/checks/convention.py +54 -0
  77. dotscope/passes/sentinel/checks/direction.py +71 -0
  78. dotscope/passes/sentinel/checks/intent.py +207 -0
  79. dotscope/passes/sentinel/checks/stability.py +66 -0
  80. dotscope/passes/sentinel/checks/voice.py +108 -0
  81. dotscope/passes/sentinel/constraints.py +472 -0
  82. dotscope/passes/sentinel/line_filter.py +88 -0
  83. dotscope/passes/sentinel/models.py +15 -0
  84. dotscope/passes/virtual.py +239 -0
  85. dotscope/passes/voice.py +162 -0
  86. dotscope/passes/voice_defaults.py +28 -0
  87. dotscope/passes/voice_discovery.py +245 -0
  88. dotscope/paths.py +32 -0
  89. dotscope/progress.py +44 -0
  90. dotscope/regression.py +147 -0
  91. dotscope/resolver.py +203 -0
  92. dotscope/scanner.py +246 -0
  93. dotscope/sessions.py +2 -0
  94. dotscope/storage/.scope +64 -0
  95. dotscope/storage/__init__.py +1 -0
  96. dotscope/storage/cache.py +114 -0
  97. dotscope/storage/claude_hooks.py +119 -0
  98. dotscope/storage/git_hooks.py +277 -0
  99. dotscope/storage/incremental_state.py +61 -0
  100. dotscope/storage/mcp_config.py +98 -0
  101. dotscope/storage/near_miss.py +183 -0
  102. dotscope/storage/onboarding.py +150 -0
  103. dotscope/storage/session_manager.py +195 -0
  104. dotscope/storage/timing.py +84 -0
  105. dotscope/timing.py +2 -0
  106. dotscope/tokens.py +53 -0
  107. dotscope/utility.py +123 -0
  108. dotscope/virtual.py +3 -0
  109. dotscope/visibility.py +664 -0
  110. dotscope-0.1.0.dist-info/METADATA +50 -0
  111. dotscope-0.1.0.dist-info/RECORD +114 -0
  112. dotscope-0.1.0.dist-info/WHEEL +4 -0
  113. dotscope-0.1.0.dist-info/entry_points.txt +3 -0
  114. dotscope-0.1.0.dist-info/licenses/LICENSE +21 -0
dotscope/bench.py ADDED
@@ -0,0 +1,141 @@
1
+ """Benchmarking: prove dotscope works with numbers.
2
+
3
+ Three metrics: token efficiency, hold rate, compilation speed.
4
+ Plus scope health aggregation.
5
+ """
6
+
7
+ import os
8
+ from typing import Dict, List, Optional
9
+
10
+ from .models.state import BenchReport # noqa: F401
11
+ from .timing import load_timings, median, percentile
12
+
13
+
14
+ def run_bench(repo_root: str) -> BenchReport:
15
+ """Compute all benchmark metrics from stored data."""
16
+ report = BenchReport()
17
+
18
+ # Load data
19
+ from .sessions import SessionManager
20
+ mgr = SessionManager(repo_root)
21
+ sessions = mgr.get_sessions(limit=500)
22
+ observations = mgr.get_observations(limit=500)
23
+
24
+ # Token efficiency
25
+ tokens_resolved = []
26
+ tokens_used = []
27
+ for obs in observations:
28
+ predicted = set(getattr(obs, "predicted_not_touched", []) or [])
29
+ actual = set(getattr(obs, "actual_files_modified", []) or [])
30
+ all_predicted = set(getattr(obs, "predicted_not_touched", []) or []) | actual
31
+ if all_predicted:
32
+ tokens_resolved.append(len(all_predicted))
33
+ tokens_used.append(len(actual))
34
+
35
+ if tokens_resolved:
36
+ report.avg_tokens_resolved = int(sum(tokens_resolved) / len(tokens_resolved))
37
+ report.avg_tokens_used = int(sum(tokens_used) / len(tokens_used))
38
+ report.efficiency_ratio = round(
39
+ sum(tokens_used) / max(sum(tokens_resolved), 1), 3
40
+ )
41
+
42
+ # Hold rate (from acknowledgments)
43
+ report.total_commits = len(observations)
44
+ try:
45
+ from .check.acknowledge import load_acknowledgments
46
+ acks = load_acknowledgments(repo_root)
47
+ report.holds_acknowledged = len(acks)
48
+ except Exception:
49
+ pass
50
+
51
+ # Compilation speed
52
+ timings = load_timings(repo_root)
53
+ resolve_times = [t.duration_ms for t in timings if t.operation == "resolve"]
54
+ check_times = [t.duration_ms for t in timings if t.operation == "check"]
55
+
56
+ report.resolve_median_ms = round(median(resolve_times), 1)
57
+ report.resolve_p95_ms = round(percentile(resolve_times, 95), 1)
58
+ report.check_median_ms = round(median(check_times), 1)
59
+ report.check_p95_ms = round(percentile(check_times, 95), 1)
60
+
61
+ # Scope health
62
+ try:
63
+ from .discovery import find_all_scopes
64
+ scope_files = find_all_scopes(repo_root)
65
+ report.total_scopes = len(scope_files)
66
+ report.avg_observations = (
67
+ len(observations) / max(len(scope_files), 1)
68
+ )
69
+
70
+ # Count scopes with >80% recall
71
+ scope_recalls = _compute_scope_recalls(observations, sessions)
72
+ report.scopes_above_80_recall = sum(
73
+ 1 for r in scope_recalls.values() if r >= 0.8
74
+ )
75
+
76
+ # Stale scopes
77
+ import time
78
+ now = time.time()
79
+ for sf in scope_files:
80
+ mtime = os.path.getmtime(sf)
81
+ if now - mtime > 30 * 86400:
82
+ report.stale_scopes += 1
83
+ except Exception:
84
+ pass
85
+
86
+ return report
87
+
88
+
89
+ def format_bench_report(report: BenchReport) -> str:
90
+ """Format benchmark report for terminal output."""
91
+ lines = []
92
+
93
+ lines.append("dotscope bench\n")
94
+
95
+ lines.append(" Token Efficiency")
96
+ if report.efficiency_ratio > 0:
97
+ lines.append(f" Average files resolved: {report.avg_tokens_resolved}")
98
+ lines.append(f" Average files agent used: {report.avg_tokens_used}")
99
+ lines.append(f" Efficiency ratio: {report.efficiency_ratio:.1%}")
100
+ else:
101
+ lines.append(" No observation data yet")
102
+ lines.append("")
103
+
104
+ lines.append(" Hold Rate")
105
+ lines.append(f" Total commits observed: {report.total_commits}")
106
+ lines.append(f" Holds acknowledged (rule was wrong): {report.holds_acknowledged}")
107
+ eff = round(report.effective_hold_rate * 100, 1) if report.effective_hold_rate else 0
108
+ lines.append(f" Effective hold rate: {eff}%")
109
+ lines.append("")
110
+
111
+ lines.append(" Compilation Speed")
112
+ if report.resolve_median_ms > 0:
113
+ lines.append(f" Median resolve: {report.resolve_median_ms}ms")
114
+ lines.append(f" P95 resolve: {report.resolve_p95_ms}ms")
115
+ else:
116
+ lines.append(" No timing data yet")
117
+ if report.check_median_ms > 0:
118
+ lines.append(f" Median check: {report.check_median_ms}ms")
119
+ lines.append(f" P95 check: {report.check_p95_ms}ms")
120
+ lines.append("")
121
+
122
+ lines.append(" Scope Health")
123
+ lines.append(f" Scopes with >80% recall: {report.scopes_above_80_recall}/{report.total_scopes}")
124
+ lines.append(f" Stale scopes (>30 days): {report.stale_scopes}")
125
+ lines.append(f" Avg observations per scope: {report.avg_observations:.1f}")
126
+
127
+ return "\n".join(lines)
128
+
129
+
130
+ def _compute_scope_recalls(observations, sessions) -> Dict[str, float]:
131
+ """Compute average recall per scope."""
132
+ scope_recalls: Dict[str, List[float]] = {}
133
+ session_scopes = {s.session_id: s.scope_expr for s in sessions}
134
+ for obs in observations:
135
+ scope = session_scopes.get(obs.session_id, "unknown")
136
+ scope_recalls.setdefault(scope, []).append(obs.recall)
137
+ return {
138
+ scope: sum(recalls) / len(recalls)
139
+ for scope, recalls in scope_recalls.items()
140
+ if recalls
141
+ }
dotscope/budget.py ADDED
@@ -0,0 +1,3 @@
1
+ """Backward-compatibility stub. Moved to dotscope.passes.budget_allocator."""
2
+ from .passes.budget_allocator import * # noqa: F401,F403
3
+ from .passes.budget_allocator import _boost_required # noqa: F401
dotscope/cache.py ADDED
@@ -0,0 +1,2 @@
1
+ """Backward-compatibility stub. Moved to dotscope.storage.cache."""
2
+ from .storage.cache import * # noqa: F401,F403
@@ -0,0 +1 @@
1
+ """Backward-compatibility stub. Moved to dotscope.passes.sentinel."""
@@ -0,0 +1,2 @@
1
+ """Backward-compatibility stub. Moved to dotscope.passes.sentinel.acknowledge."""
2
+ from ..passes.sentinel.acknowledge import * # noqa: F401,F403
@@ -0,0 +1,3 @@
1
+ """Backward-compatibility stub. Moved to dotscope.passes.sentinel.checker."""
2
+ from ..passes.sentinel.checker import * # noqa: F401,F403
3
+ from ..passes.sentinel.checker import _parse_diff, _load_scopes_with_antipatterns # noqa: F401
@@ -0,0 +1 @@
1
+ """Backward-compatibility stub. Moved to dotscope.passes.sentinel.checks."""
@@ -0,0 +1,2 @@
1
+ """Backward-compatibility stub. Moved to dotscope.passes.sentinel.checks.antipattern."""
2
+ from ...passes.sentinel.checks.antipattern import * # noqa: F401,F403
@@ -0,0 +1,2 @@
1
+ """Backward-compatibility stub. Moved to dotscope.passes.sentinel.checks.boundary."""
2
+ from ...passes.sentinel.checks.boundary import * # noqa: F401,F403
@@ -0,0 +1,3 @@
1
+ """Backward-compatibility stub. Moved to dotscope.passes.sentinel.checks.contracts."""
2
+ from ...passes.sentinel.checks.contracts import * # noqa: F401,F403
3
+ from ...passes.sentinel.checks.contracts import _ack_id # noqa: F401
@@ -0,0 +1,2 @@
1
+ """Backward-compatibility stub. Moved to dotscope.passes.sentinel.checks.direction."""
2
+ from ...passes.sentinel.checks.direction import * # noqa: F401,F403
@@ -0,0 +1,2 @@
1
+ """Backward-compatibility stub. Moved to dotscope.passes.sentinel.checks.intent."""
2
+ from ...passes.sentinel.checks.intent import * # noqa: F401,F403
@@ -0,0 +1,2 @@
1
+ """Backward-compatibility stub. Moved to dotscope.passes.sentinel.checks.stability."""
2
+ from ...passes.sentinel.checks.stability import * # noqa: F401,F403
@@ -0,0 +1,2 @@
1
+ """Backward-compatibility stub. Moved to dotscope.passes.sentinel.constraints."""
2
+ from ..passes.sentinel.constraints import * # noqa: F401,F403
@@ -0,0 +1,15 @@
1
+ """Data models for the enforcement system.
2
+
3
+ Backward-compatibility facade. All definitions now live in dotscope.models.intent.
4
+ """
5
+
6
+ from ..models.intent import ( # noqa: F401
7
+ Severity,
8
+ CheckCategory,
9
+ IntentDirective,
10
+ Constraint,
11
+ ConventionRule,
12
+ ProposedFix,
13
+ CheckResult,
14
+ CheckReport,
15
+ )