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.
- dotscope/.scope +63 -0
- dotscope/__init__.py +3 -0
- dotscope/absorber.py +390 -0
- dotscope/assertions.py +128 -0
- dotscope/ast_analyzer.py +2 -0
- dotscope/backtest.py +2 -0
- dotscope/bench.py +141 -0
- dotscope/budget.py +3 -0
- dotscope/cache.py +2 -0
- dotscope/check/__init__.py +1 -0
- dotscope/check/acknowledge.py +2 -0
- dotscope/check/checker.py +3 -0
- dotscope/check/checks/__init__.py +1 -0
- dotscope/check/checks/antipattern.py +2 -0
- dotscope/check/checks/boundary.py +2 -0
- dotscope/check/checks/contracts.py +3 -0
- dotscope/check/checks/direction.py +2 -0
- dotscope/check/checks/intent.py +2 -0
- dotscope/check/checks/stability.py +2 -0
- dotscope/check/constraints.py +2 -0
- dotscope/check/models.py +15 -0
- dotscope/cli.py +1447 -0
- dotscope/composer.py +147 -0
- dotscope/constants.py +45 -0
- dotscope/context.py +60 -0
- dotscope/counterfactual.py +180 -0
- dotscope/debug.py +220 -0
- dotscope/discovery.py +104 -0
- dotscope/formatter.py +157 -0
- dotscope/graph.py +3 -0
- dotscope/health.py +212 -0
- dotscope/help.py +204 -0
- dotscope/history.py +6 -0
- dotscope/hooks.py +2 -0
- dotscope/ingest.py +858 -0
- dotscope/intent.py +618 -0
- dotscope/lessons.py +223 -0
- dotscope/matcher.py +104 -0
- dotscope/mcp_server.py +1081 -0
- dotscope/models/.scope +45 -0
- dotscope/models/__init__.py +7 -0
- dotscope/models/core.py +288 -0
- dotscope/models/history.py +73 -0
- dotscope/models/intent.py +213 -0
- dotscope/models/passes.py +58 -0
- dotscope/models/state.py +250 -0
- dotscope/models.py +9 -0
- dotscope/near_miss.py +3 -0
- dotscope/onboarding.py +2 -0
- dotscope/parser.py +387 -0
- dotscope/passes/.scope +105 -0
- dotscope/passes/__init__.py +1 -0
- dotscope/passes/ast_analyzer.py +508 -0
- dotscope/passes/backtest.py +198 -0
- dotscope/passes/budget_allocator.py +164 -0
- dotscope/passes/convention_compliance.py +40 -0
- dotscope/passes/convention_discovery.py +247 -0
- dotscope/passes/convention_parser.py +223 -0
- dotscope/passes/graph_builder.py +299 -0
- dotscope/passes/history_miner.py +336 -0
- dotscope/passes/incremental.py +149 -0
- dotscope/passes/lang/__init__.py +38 -0
- dotscope/passes/lang/_base.py +20 -0
- dotscope/passes/lang/_treesitter.py +93 -0
- dotscope/passes/lang/go.py +333 -0
- dotscope/passes/lang/javascript.py +348 -0
- dotscope/passes/lazy.py +152 -0
- dotscope/passes/semantic_diff.py +160 -0
- dotscope/passes/sentinel/__init__.py +1 -0
- dotscope/passes/sentinel/acknowledge.py +222 -0
- dotscope/passes/sentinel/checker.py +383 -0
- dotscope/passes/sentinel/checks/__init__.py +1 -0
- dotscope/passes/sentinel/checks/antipattern.py +84 -0
- dotscope/passes/sentinel/checks/boundary.py +46 -0
- dotscope/passes/sentinel/checks/contracts.py +148 -0
- dotscope/passes/sentinel/checks/convention.py +54 -0
- dotscope/passes/sentinel/checks/direction.py +71 -0
- dotscope/passes/sentinel/checks/intent.py +207 -0
- dotscope/passes/sentinel/checks/stability.py +66 -0
- dotscope/passes/sentinel/checks/voice.py +108 -0
- dotscope/passes/sentinel/constraints.py +472 -0
- dotscope/passes/sentinel/line_filter.py +88 -0
- dotscope/passes/sentinel/models.py +15 -0
- dotscope/passes/virtual.py +239 -0
- dotscope/passes/voice.py +162 -0
- dotscope/passes/voice_defaults.py +28 -0
- dotscope/passes/voice_discovery.py +245 -0
- dotscope/paths.py +32 -0
- dotscope/progress.py +44 -0
- dotscope/regression.py +147 -0
- dotscope/resolver.py +203 -0
- dotscope/scanner.py +246 -0
- dotscope/sessions.py +2 -0
- dotscope/storage/.scope +64 -0
- dotscope/storage/__init__.py +1 -0
- dotscope/storage/cache.py +114 -0
- dotscope/storage/claude_hooks.py +119 -0
- dotscope/storage/git_hooks.py +277 -0
- dotscope/storage/incremental_state.py +61 -0
- dotscope/storage/mcp_config.py +98 -0
- dotscope/storage/near_miss.py +183 -0
- dotscope/storage/onboarding.py +150 -0
- dotscope/storage/session_manager.py +195 -0
- dotscope/storage/timing.py +84 -0
- dotscope/timing.py +2 -0
- dotscope/tokens.py +53 -0
- dotscope/utility.py +123 -0
- dotscope/virtual.py +3 -0
- dotscope/visibility.py +664 -0
- dotscope-0.1.0.dist-info/METADATA +50 -0
- dotscope-0.1.0.dist-info/RECORD +114 -0
- dotscope-0.1.0.dist-info/WHEEL +4 -0
- dotscope-0.1.0.dist-info/entry_points.txt +3 -0
- 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
dotscope/cache.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Backward-compatibility stub. Moved to dotscope.passes.sentinel."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Backward-compatibility stub. Moved to dotscope.passes.sentinel.checks."""
|
dotscope/check/models.py
ADDED
|
@@ -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
|
+
)
|