contextops 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.
- contextops/__init__.py +3 -0
- contextops/analyzers/__init__.py +1 -0
- contextops/analyzers/density.py +146 -0
- contextops/analyzers/redundancy.py +362 -0
- contextops/analyzers/structure.py +123 -0
- contextops/analyzers/tokens.py +76 -0
- contextops/api/__init__.py +1 -0
- contextops/api/diff.py +124 -0
- contextops/api/inspect.py +52 -0
- contextops/api/stability.py +264 -0
- contextops/cli/__init__.py +1 -0
- contextops/cli/main.py +320 -0
- contextops/cli/renderer.py +424 -0
- contextops/core/__init__.py +1 -0
- contextops/core/config.py +61 -0
- contextops/core/engine.py +355 -0
- contextops/core/models.py +245 -0
- contextops/core/normalizer.py +187 -0
- contextops-0.1.0.dist-info/METADATA +272 -0
- contextops-0.1.0.dist-info/RECORD +24 -0
- contextops-0.1.0.dist-info/WHEEL +5 -0
- contextops-0.1.0.dist-info/entry_points.txt +2 -0
- contextops-0.1.0.dist-info/licenses/LICENSE +21 -0
- contextops-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI Renderer.
|
|
3
|
+
|
|
4
|
+
Pretty-prints AnalysisResult to the terminal.
|
|
5
|
+
All rendering is derived from JSON (AnalysisResult.to_dict()) — the CLI
|
|
6
|
+
is a view layer only. The JSON is the source of truth.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
from contextops.core.models import AnalysisResult
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ── ANSI color codes ────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
class _Colors:
|
|
20
|
+
RESET = "\033[0m"
|
|
21
|
+
BOLD = "\033[1m"
|
|
22
|
+
DIM = "\033[2m"
|
|
23
|
+
RED = "\033[31m"
|
|
24
|
+
GREEN = "\033[32m"
|
|
25
|
+
YELLOW = "\033[33m"
|
|
26
|
+
BLUE = "\033[34m"
|
|
27
|
+
MAGENTA = "\033[35m"
|
|
28
|
+
CYAN = "\033[36m"
|
|
29
|
+
WHITE = "\033[37m"
|
|
30
|
+
BG_RED = "\033[41m"
|
|
31
|
+
BG_GREEN = "\033[42m"
|
|
32
|
+
BG_YELLOW = "\033[43m"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
C = _Colors
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _score_color(score: int) -> str:
|
|
39
|
+
"""Pick a color based on the score range."""
|
|
40
|
+
if score >= 80:
|
|
41
|
+
return C.GREEN
|
|
42
|
+
elif score >= 60:
|
|
43
|
+
return C.YELLOW
|
|
44
|
+
elif score >= 40:
|
|
45
|
+
return C.RED
|
|
46
|
+
else:
|
|
47
|
+
return C.BG_RED + C.WHITE
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _score_label(score: int) -> str:
|
|
51
|
+
"""Human-readable label for the score."""
|
|
52
|
+
if score >= 90:
|
|
53
|
+
return "EXCELLENT"
|
|
54
|
+
elif score >= 80:
|
|
55
|
+
return "GOOD"
|
|
56
|
+
elif score >= 60:
|
|
57
|
+
return "NEEDS WORK"
|
|
58
|
+
elif score >= 40:
|
|
59
|
+
return "POOR"
|
|
60
|
+
else:
|
|
61
|
+
return "CRITICAL"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _bar(value: float, max_value: float, width: int = 20) -> str:
|
|
65
|
+
"""Render a simple horizontal bar chart."""
|
|
66
|
+
filled = int((value / max(max_value, 1)) * width)
|
|
67
|
+
filled = min(filled, width)
|
|
68
|
+
return "#" * filled + "-" * (width - filled)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def render(result: AnalysisResult, use_json: bool = False, explain: bool = False) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Render an AnalysisResult for terminal display.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
result: The analysis result to render.
|
|
77
|
+
use_json: If True, output raw JSON instead of pretty formatting.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
A formatted string ready for print().
|
|
81
|
+
"""
|
|
82
|
+
if use_json:
|
|
83
|
+
return json.dumps(result.to_dict(), indent=2)
|
|
84
|
+
|
|
85
|
+
lines: list[str] = []
|
|
86
|
+
data = result.to_dict()
|
|
87
|
+
score = data["score"]
|
|
88
|
+
|
|
89
|
+
# ── Header ──────────────────────────────────────────────────────
|
|
90
|
+
lines.append("")
|
|
91
|
+
lines.append(f"{C.BOLD}{C.CYAN}+{'=' * 50}+{C.RESET}")
|
|
92
|
+
lines.append(f"{C.BOLD}{C.CYAN}| CONTEXTOPS -- Context Analysis |{C.RESET}")
|
|
93
|
+
lines.append(f"{C.BOLD}{C.CYAN}+{'=' * 50}+{C.RESET}")
|
|
94
|
+
lines.append("")
|
|
95
|
+
|
|
96
|
+
# ── Score ────────────────────────────────────────────────────────
|
|
97
|
+
color = _score_color(score)
|
|
98
|
+
label = _score_label(score)
|
|
99
|
+
lines.append(f" {C.BOLD}Context Score:{C.RESET} {color}{C.BOLD} {score} / 100 {C.RESET} {C.DIM}({label}){C.RESET}")
|
|
100
|
+
lines.append("")
|
|
101
|
+
|
|
102
|
+
# ── Score Breakdown ─────────────────────────────────────────────
|
|
103
|
+
bd = data["score_breakdown"]
|
|
104
|
+
lines.append(f" {C.BOLD}Score Breakdown:{C.RESET}")
|
|
105
|
+
lines.append(f" {C.DIM}{'-' * 48}{C.RESET}")
|
|
106
|
+
|
|
107
|
+
penalties = [
|
|
108
|
+
("Redundancy", bd["redundancy_penalty"], 30, C.RED),
|
|
109
|
+
("Density", bd["density_penalty"], 30, C.YELLOW),
|
|
110
|
+
("Structure", bd["structure_penalty"], 20, C.MAGENTA),
|
|
111
|
+
("Concentration", bd["concentration_penalty"], 20, C.BLUE),
|
|
112
|
+
]
|
|
113
|
+
for name, value, max_val, clr in penalties:
|
|
114
|
+
bar = _bar(value, max_val)
|
|
115
|
+
lines.append(
|
|
116
|
+
f" {clr} {name:<12}{C.RESET} "
|
|
117
|
+
f"-{value:>5.1f} / {max_val} {C.DIM}{bar}{C.RESET}"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
lines.append(f" {C.DIM}{'-' * 48}{C.RESET}")
|
|
121
|
+
lines.append(f" {C.BOLD} Total Penalty{C.RESET} -{bd['total_penalty']:>5.1f} / 100")
|
|
122
|
+
lines.append("")
|
|
123
|
+
|
|
124
|
+
# ── Token Breakdown ─────────────────────────────────────────────
|
|
125
|
+
tb = data["token_breakdown"]
|
|
126
|
+
lines.append(f" {C.BOLD}Token Breakdown:{C.RESET}")
|
|
127
|
+
lines.append(f" {C.DIM}{'-' * 48}{C.RESET}")
|
|
128
|
+
if tb['wasted_tokens'] > 0:
|
|
129
|
+
lines.append(f" {C.DIM}Cost Model: ~15 penalty points per 1,000 wasted tokens (capped at 30){C.RESET}")
|
|
130
|
+
lines.append(f" Total tokens: {C.BOLD}{tb['total_tokens']:,}{C.RESET}")
|
|
131
|
+
lines.append(f" Wasted tokens: {C.RED}{tb['wasted_tokens']:,}{C.RESET}")
|
|
132
|
+
lines.append(f" Estimated cost: ${tb['estimated_cost_usd']:.4f}")
|
|
133
|
+
lines.append("")
|
|
134
|
+
|
|
135
|
+
if tb["by_type"]:
|
|
136
|
+
lines.append(f" {C.DIM}By Type:{C.RESET}")
|
|
137
|
+
for ctx_type, tokens in sorted(tb["by_type"].items(), key=lambda x: -x[1]):
|
|
138
|
+
pct = (tokens / max(1, tb["total_tokens"])) * 100
|
|
139
|
+
bar = _bar(tokens, tb["total_tokens"], width=15)
|
|
140
|
+
lines.append(
|
|
141
|
+
f" {ctx_type:<12} {tokens:>6,} tokens ({pct:>4.1f}%) {C.DIM}{bar}{C.RESET}"
|
|
142
|
+
)
|
|
143
|
+
lines.append("")
|
|
144
|
+
|
|
145
|
+
# ── Shadow Signals (Phase 2 Preview) ────────────────────────────
|
|
146
|
+
if "density_signal" in data:
|
|
147
|
+
ds = data["density_signal"]
|
|
148
|
+
effect = data.get("density_effect", "shadow")
|
|
149
|
+
if effect == "shadow":
|
|
150
|
+
lines.append(f" {C.BOLD}{C.CYAN}Shadow Metrics (Phase 2 Preview):{C.RESET}")
|
|
151
|
+
lines.append(f" {C.DIM}{'-' * 48}{C.RESET}")
|
|
152
|
+
|
|
153
|
+
lines.append(f" Format Overhead: {ds['format_overhead']:.2f}")
|
|
154
|
+
lines.append(f" Whitespace Waste: {ds['whitespace_waste']:.2f}")
|
|
155
|
+
lines.append(f" Entropy Compression: {ds['entropy_compression']:.2f}")
|
|
156
|
+
lines.append(f" {C.BOLD}Total Density Signal:{C.RESET} {C.YELLOW}{ds['total_density_signal']:.2f}{C.RESET}")
|
|
157
|
+
lines.append(f" {C.DIM}(This is a shadow metric and does not affect the score yet){C.RESET}")
|
|
158
|
+
lines.append("")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# ── Findings ────────────────────────────────────────────────────
|
|
162
|
+
if not explain:
|
|
163
|
+
redundancy_findings = data["findings"]["redundancy"]
|
|
164
|
+
structure_findings = data["findings"]["structure"]
|
|
165
|
+
|
|
166
|
+
if redundancy_findings or structure_findings:
|
|
167
|
+
lines.append(f" {C.BOLD}Findings:{C.RESET}")
|
|
168
|
+
lines.append(f" {C.DIM}{'-' * 48}{C.RESET}")
|
|
169
|
+
|
|
170
|
+
for f in redundancy_findings:
|
|
171
|
+
icon = "[~]" if f["classification"] == "expected_overlap" else "[!]"
|
|
172
|
+
lines.append(
|
|
173
|
+
f" {icon} {C.YELLOW}{f['classification'].upper()}{C.RESET}: "
|
|
174
|
+
f"{f['detail']}"
|
|
175
|
+
)
|
|
176
|
+
lines.append(
|
|
177
|
+
f" {C.DIM}similarity: {f['similarity']:.0%} | "
|
|
178
|
+
f"waste: {f['waste_tokens']:,} tokens{C.RESET}"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
for f in structure_findings:
|
|
182
|
+
sev_color = C.RED if f["severity"] in ("high", "critical") else C.YELLOW
|
|
183
|
+
lines.append(
|
|
184
|
+
f" [S] {sev_color}{f['issue']}{C.RESET}: "
|
|
185
|
+
f"{f['type']} is {f['actual_ratio']:.0%} of context"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if redundancy_findings or structure_findings:
|
|
189
|
+
lines.append("")
|
|
190
|
+
|
|
191
|
+
# ── Recommendations ─────────────────────────────────────────────
|
|
192
|
+
if not explain:
|
|
193
|
+
recs = data["recommendations"]
|
|
194
|
+
if recs:
|
|
195
|
+
lines.append(f" {C.BOLD}{C.GREEN}Recommendations:{C.RESET}")
|
|
196
|
+
lines.append(f" {C.DIM}{'-' * 48}{C.RESET}")
|
|
197
|
+
|
|
198
|
+
for i, rec in enumerate(recs, 1):
|
|
199
|
+
sev_color = C.RED if rec["severity"] in ("high", "critical") else C.YELLOW
|
|
200
|
+
lines.append(
|
|
201
|
+
f" {C.BOLD}{i}. {sev_color}{rec['issue']}{C.RESET}"
|
|
202
|
+
)
|
|
203
|
+
lines.append(
|
|
204
|
+
f" {C.GREEN}Impact: {rec['impact']}{C.RESET} | "
|
|
205
|
+
f"Save: {rec['token_savings']:,} tokens"
|
|
206
|
+
)
|
|
207
|
+
lines.append(
|
|
208
|
+
f" {C.CYAN}Fix: {rec['fix']}{C.RESET}"
|
|
209
|
+
)
|
|
210
|
+
lines.append("")
|
|
211
|
+
|
|
212
|
+
# ── Explain Mode ────────────────────────────────────────────────
|
|
213
|
+
if explain:
|
|
214
|
+
# Deterministic sorting
|
|
215
|
+
sorted_recs = sorted(
|
|
216
|
+
result.recommendations,
|
|
217
|
+
key=lambda r: (-r.impact_score, -r.token_savings, r.issue)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if sorted_recs:
|
|
221
|
+
lines.append(f" {C.BOLD}{C.MAGENTA}Top Score Drivers:{C.RESET}")
|
|
222
|
+
lines.append(f" {C.DIM}{'-' * 48}{C.RESET}")
|
|
223
|
+
|
|
224
|
+
top_recs = sorted_recs[:3]
|
|
225
|
+
for i, r in enumerate(top_recs, 1):
|
|
226
|
+
lines.append(f" {C.BOLD}{i}. {r.issue}{C.RESET}")
|
|
227
|
+
lines.append(f" Impact: {C.RED}-{round(r.impact_score, 1)}{C.RESET}")
|
|
228
|
+
lines.append("")
|
|
229
|
+
|
|
230
|
+
hidden_count = len(sorted_recs) - 3
|
|
231
|
+
if hidden_count > 0:
|
|
232
|
+
lines.append(f" {C.DIM}+ {hidden_count} minor issues hidden{C.RESET}")
|
|
233
|
+
lines.append("")
|
|
234
|
+
|
|
235
|
+
# Potential Score Summary
|
|
236
|
+
top_driver = sorted_recs[0]
|
|
237
|
+
potential_score = min(100, score + round(top_driver.impact_score))
|
|
238
|
+
|
|
239
|
+
lines.append(f" {C.BOLD}{C.GREEN}Why This Score Is Not Higher{C.RESET}")
|
|
240
|
+
lines.append(f" {C.DIM}{'-' * 48}{C.RESET}")
|
|
241
|
+
lines.append(f" Potential Score: {C.GREEN}{C.BOLD}{potential_score}{C.RESET}")
|
|
242
|
+
lines.append(f" Current Score: {color}{C.BOLD}{score}{C.RESET}")
|
|
243
|
+
lines.append("")
|
|
244
|
+
lines.append(f" {C.BOLD}Largest Opportunity:{C.RESET}")
|
|
245
|
+
lines.append(f" {C.CYAN}{top_driver.fix}{C.RESET}")
|
|
246
|
+
lines.append("")
|
|
247
|
+
lines.append(f" {C.BOLD}Expected Gain:{C.RESET}")
|
|
248
|
+
lines.append(f" {C.GREEN}+{round(top_driver.impact_score, 1)} points{C.RESET}")
|
|
249
|
+
if top_driver.token_savings > 0:
|
|
250
|
+
lines.append(f" {C.GREEN}{top_driver.token_savings:,} tokens saved{C.RESET}")
|
|
251
|
+
lines.append("")
|
|
252
|
+
|
|
253
|
+
# ── Footer ──────────────────────────────────────────────────────
|
|
254
|
+
lines.append(f" {C.DIM}contextops v{data['metadata'].get('version', '0.1.0')} "
|
|
255
|
+
f"| {data['metadata'].get('item_count', 0)} items analyzed{C.RESET}")
|
|
256
|
+
lines.append("")
|
|
257
|
+
lines.append(f" {C.YELLOW}{C.BOLD}LIMITATION:{C.RESET}")
|
|
258
|
+
lines.append(f" {C.DIM}This tool measures structural density, not semantic usefulness.{C.RESET}")
|
|
259
|
+
lines.append(f" {C.DIM}A high score does not guarantee the LLM has the right facts to answer.{C.RESET}")
|
|
260
|
+
lines.append("")
|
|
261
|
+
|
|
262
|
+
return "\n".join(lines)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def render_stability(report: Any) -> str:
|
|
266
|
+
"""
|
|
267
|
+
Render a StabilityReport for terminal display.
|
|
268
|
+
Expects report of type contextops.api.stability.StabilityReport.
|
|
269
|
+
"""
|
|
270
|
+
lines: list[str] = []
|
|
271
|
+
|
|
272
|
+
lines.append("")
|
|
273
|
+
lines.append(f"{C.BOLD}{C.CYAN}+{'=' * 50}+{C.RESET}")
|
|
274
|
+
lines.append(f"{C.BOLD}{C.CYAN}| CONTEXTOPS -- Stability Report |{C.RESET}")
|
|
275
|
+
lines.append(f"{C.BOLD}{C.CYAN}+{'=' * 50}+{C.RESET}")
|
|
276
|
+
lines.append("")
|
|
277
|
+
|
|
278
|
+
lines.append(f" {C.BOLD}Base Score:{C.RESET} {report.base_score}")
|
|
279
|
+
lines.append(f" {C.BOLD}Base Tokens:{C.RESET} {report.base_tokens:,}")
|
|
280
|
+
lines.append(f" {C.BOLD}Base Waste Tokens:{C.RESET} {report.base_waste_tokens:,}")
|
|
281
|
+
lines.append("")
|
|
282
|
+
|
|
283
|
+
for inv in report.invariants:
|
|
284
|
+
lines.append(f" {C.BOLD}{inv.name}{C.RESET}")
|
|
285
|
+
|
|
286
|
+
if inv.passed:
|
|
287
|
+
lines.append(f" {C.GREEN}PASS{C.RESET}")
|
|
288
|
+
else:
|
|
289
|
+
lines.append(f" {C.RED}FAIL{C.RESET}")
|
|
290
|
+
|
|
291
|
+
for key, value in inv.diagnostic_info.items():
|
|
292
|
+
lines.append(f" {C.DIM}{key}: {value}{C.RESET}")
|
|
293
|
+
|
|
294
|
+
lines.append("")
|
|
295
|
+
|
|
296
|
+
score = report.score_percentage
|
|
297
|
+
color = _score_color(score)
|
|
298
|
+
passed_count = sum(1 for inv in report.invariants if inv.passed)
|
|
299
|
+
total_count = len(report.invariants)
|
|
300
|
+
|
|
301
|
+
if score >= 90:
|
|
302
|
+
confidence = "High"
|
|
303
|
+
elif score >= 70:
|
|
304
|
+
confidence = "Medium"
|
|
305
|
+
else:
|
|
306
|
+
confidence = "Low"
|
|
307
|
+
|
|
308
|
+
lines.append(f" {C.DIM}{'-' * 48}{C.RESET}")
|
|
309
|
+
lines.append(f" {C.BOLD}Stability Score:{C.RESET}")
|
|
310
|
+
lines.append(f" {color}{C.BOLD}{score}%{C.RESET} {C.DIM}({passed_count}/{total_count} passed){C.RESET}")
|
|
311
|
+
lines.append(f" {C.BOLD}Confidence: {confidence}{C.RESET}")
|
|
312
|
+
lines.append("")
|
|
313
|
+
|
|
314
|
+
return "\n".join(lines)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def render_diff(diff: Any) -> str:
|
|
318
|
+
"""
|
|
319
|
+
Render a ContextDiffResult for terminal display.
|
|
320
|
+
Expects diff of type contextops.api.diff.ContextDiffResult.
|
|
321
|
+
"""
|
|
322
|
+
lines: list[str] = []
|
|
323
|
+
|
|
324
|
+
lines.append("")
|
|
325
|
+
lines.append(f"{C.BOLD}{C.CYAN}+{'=' * 50}+{C.RESET}")
|
|
326
|
+
lines.append(f"{C.BOLD}{C.CYAN}| CONTEXTOPS -- Context Diff |{C.RESET}")
|
|
327
|
+
lines.append(f"{C.BOLD}{C.CYAN}+{'=' * 50}+{C.RESET}")
|
|
328
|
+
lines.append("")
|
|
329
|
+
|
|
330
|
+
# ── Score Section ───────────────────────────────────────────────
|
|
331
|
+
score_a = diff.result_a.score
|
|
332
|
+
score_b = diff.result_b.score
|
|
333
|
+
score_color = C.GREEN if diff.score_delta > 0 else (C.RED if diff.score_delta < 0 else C.RESET)
|
|
334
|
+
score_sign = "+" if diff.score_delta > 0 else ""
|
|
335
|
+
lines.append(f" {C.BOLD}Score:{C.RESET} {score_a} -> {score_b} "
|
|
336
|
+
f"({score_color}{score_sign}{diff.score_delta}{C.RESET})")
|
|
337
|
+
lines.append("")
|
|
338
|
+
|
|
339
|
+
# ── Tokens / Cost ───────────────────────────────────────────────
|
|
340
|
+
lines.append(f" {C.BOLD}Tokens / Cost{C.RESET}")
|
|
341
|
+
tok_a = diff.result_a.token_breakdown.total_tokens
|
|
342
|
+
tok_b = diff.result_b.token_breakdown.total_tokens
|
|
343
|
+
tok_color = C.GREEN if diff.token_delta < 0 else (C.RED if diff.token_delta > 0 else C.RESET)
|
|
344
|
+
tok_sign = "+" if diff.token_delta > 0 else ""
|
|
345
|
+
lines.append(f" Tokens: {tok_a:,} -> {tok_b:,} "
|
|
346
|
+
f"({tok_color}{tok_sign}{diff.token_delta:,}{C.RESET})")
|
|
347
|
+
|
|
348
|
+
cost_a = diff.result_a.token_breakdown.estimated_cost_usd
|
|
349
|
+
cost_b = diff.result_b.token_breakdown.estimated_cost_usd
|
|
350
|
+
cost_color = C.GREEN if diff.cost_delta < 0 else (C.RED if diff.cost_delta > 0 else C.RESET)
|
|
351
|
+
cost_sign = "+" if diff.cost_delta > 0 else ""
|
|
352
|
+
lines.append(f" Cost: ${cost_a:.4f} -> ${cost_b:.4f} "
|
|
353
|
+
f"({cost_color}{cost_sign}${abs(diff.cost_delta):.4f}{C.RESET})")
|
|
354
|
+
lines.append("")
|
|
355
|
+
|
|
356
|
+
# ── Structure Changes ───────────────────────────────────────────
|
|
357
|
+
lines.append(f" {C.BOLD}Structure Changes (Penalties){C.RESET}")
|
|
358
|
+
for key, delta in diff.structure_delta.items():
|
|
359
|
+
if abs(delta) < 0.01:
|
|
360
|
+
continue
|
|
361
|
+
color = C.GREEN if delta < 0 else C.RED
|
|
362
|
+
sign = "+" if delta > 0 else ""
|
|
363
|
+
lines.append(f" {key.capitalize()}: {color}{sign}{delta:.2f}{C.RESET}")
|
|
364
|
+
|
|
365
|
+
if all(abs(v) < 0.01 for v in diff.structure_delta.values()):
|
|
366
|
+
lines.append(f" {C.DIM}No significant structure changes.{C.RESET}")
|
|
367
|
+
lines.append("")
|
|
368
|
+
|
|
369
|
+
# ── Recommendation Lifecycle ────────────────────────────────────
|
|
370
|
+
lines.append(f" {C.BOLD}Recommendation Lifecycle{C.RESET}")
|
|
371
|
+
|
|
372
|
+
if diff.resolved_recommendations:
|
|
373
|
+
lines.append(f" {C.GREEN}Resolved:{C.RESET}")
|
|
374
|
+
for r in diff.resolved_recommendations:
|
|
375
|
+
lines.append(f" {C.GREEN}[-] {r.issue}{C.RESET}")
|
|
376
|
+
|
|
377
|
+
if diff.new_recommendations:
|
|
378
|
+
lines.append(f" {C.RED}New:{C.RESET}")
|
|
379
|
+
for r in diff.new_recommendations:
|
|
380
|
+
lines.append(f" {C.RED}[+] {r.issue}{C.RESET}")
|
|
381
|
+
|
|
382
|
+
if diff.persisting_recommendations:
|
|
383
|
+
lines.append(f" {C.DIM}Persisting:{C.RESET}")
|
|
384
|
+
for r in diff.persisting_recommendations:
|
|
385
|
+
lines.append(f" {C.DIM}[~] {r.issue}{C.RESET}")
|
|
386
|
+
|
|
387
|
+
if not (diff.resolved_recommendations or diff.new_recommendations or diff.persisting_recommendations):
|
|
388
|
+
lines.append(f" {C.DIM}No recommendations.{C.RESET}")
|
|
389
|
+
|
|
390
|
+
lines.append("")
|
|
391
|
+
|
|
392
|
+
# ── Net Impact Summary ──────────────────────────────────────────
|
|
393
|
+
lines.append(f" {C.DIM}{'-' * 48}{C.RESET}")
|
|
394
|
+
lines.append(f" {C.BOLD}Net Impact Summary:{C.RESET}")
|
|
395
|
+
|
|
396
|
+
# Generate impact bullets
|
|
397
|
+
if diff.token_delta < 0:
|
|
398
|
+
lines.append(f" {C.GREEN}\u2714 Reduced token usage ({diff.token_delta:,}){C.RESET}")
|
|
399
|
+
elif diff.token_delta > 0:
|
|
400
|
+
lines.append(f" {C.RED}\u2716 Increased token usage (+{diff.token_delta:,}){C.RESET}")
|
|
401
|
+
|
|
402
|
+
if diff.score_delta > 0:
|
|
403
|
+
lines.append(f" {C.GREEN}\u2714 Improved score (+{diff.score_delta}){C.RESET}")
|
|
404
|
+
elif diff.score_delta < 0:
|
|
405
|
+
lines.append(f" {C.RED}\u2716 Score degraded ({diff.score_delta}){C.RESET}")
|
|
406
|
+
|
|
407
|
+
for key, delta in diff.structure_delta.items():
|
|
408
|
+
if delta > 0.05: # significant penalty increase
|
|
409
|
+
lines.append(f" {C.RED}\u2716 {key.replace('_', ' ')} penalty increased (+{delta:.2f}){C.RESET}")
|
|
410
|
+
elif delta < -0.05: # significant penalty decrease
|
|
411
|
+
lines.append(f" {C.GREEN}\u2714 {key.replace('_', ' ')} penalty decreased ({delta:.2f}){C.RESET}")
|
|
412
|
+
|
|
413
|
+
lines.append("")
|
|
414
|
+
|
|
415
|
+
if diff.net_impact == "IMPROVEMENT":
|
|
416
|
+
lines.append(f" Overall: {C.BG_GREEN}{C.WHITE}{C.BOLD} IMPROVEMENT {C.RESET}")
|
|
417
|
+
elif diff.net_impact == "DEGRADATION":
|
|
418
|
+
lines.append(f" Overall: {C.BG_RED}{C.WHITE}{C.BOLD} DEGRADATION {C.RESET}")
|
|
419
|
+
else:
|
|
420
|
+
lines.append(f" Overall: {C.BG_YELLOW}{C.WHITE}{C.BOLD} NEUTRAL {C.RESET}")
|
|
421
|
+
|
|
422
|
+
lines.append("")
|
|
423
|
+
|
|
424
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Core subpackage
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration models for ContextOps.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class ContextOpsConfig:
|
|
15
|
+
"""
|
|
16
|
+
Configuration for ContextOps analyzers.
|
|
17
|
+
|
|
18
|
+
Thresholds represent the maximum allowed ratio of context for a given type.
|
|
19
|
+
"""
|
|
20
|
+
retrieval_max_ratio: float = 0.70
|
|
21
|
+
system_max_ratio: float = 0.50
|
|
22
|
+
memory_max_ratio: float = 0.50
|
|
23
|
+
tool_max_ratio: float = 0.60
|
|
24
|
+
|
|
25
|
+
# "strict" means default thresholds are used (standardized score).
|
|
26
|
+
# "custom" means user has overridden thresholds.
|
|
27
|
+
mode: str = "strict"
|
|
28
|
+
version: str = "1.0"
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def default(cls) -> ContextOpsConfig:
|
|
32
|
+
return cls()
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_dict(cls, data: dict[str, Any]) -> ContextOpsConfig:
|
|
36
|
+
config = cls()
|
|
37
|
+
has_custom = False
|
|
38
|
+
|
|
39
|
+
if "retrieval_max_ratio" in data:
|
|
40
|
+
config.retrieval_max_ratio = float(data["retrieval_max_ratio"])
|
|
41
|
+
has_custom = True
|
|
42
|
+
if "system_max_ratio" in data:
|
|
43
|
+
config.system_max_ratio = float(data["system_max_ratio"])
|
|
44
|
+
has_custom = True
|
|
45
|
+
if "memory_max_ratio" in data:
|
|
46
|
+
config.memory_max_ratio = float(data["memory_max_ratio"])
|
|
47
|
+
has_custom = True
|
|
48
|
+
if "tool_max_ratio" in data:
|
|
49
|
+
config.tool_max_ratio = float(data["tool_max_ratio"])
|
|
50
|
+
has_custom = True
|
|
51
|
+
|
|
52
|
+
if has_custom:
|
|
53
|
+
config.mode = "custom"
|
|
54
|
+
|
|
55
|
+
return config
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def from_file(cls, path: str | Path) -> ContextOpsConfig:
|
|
59
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
60
|
+
data = json.load(f)
|
|
61
|
+
return cls.from_dict(data)
|