codetool-shell 0.1.1__py3-none-win_amd64.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 (72) hide show
  1. codetool_shell/__init__.py +11 -0
  2. codetool_shell/api.py +59 -0
  3. codetool_shell/bin/windows-x86_64/codetool-shell-rust.exe +0 -0
  4. codetool_shell/filters/__init__.py +14 -0
  5. codetool_shell/filters/build_compiler/__init__.py +7 -0
  6. codetool_shell/filters/build_compiler/detector.py +412 -0
  7. codetool_shell/filters/build_compiler/reducer.py +166 -0
  8. codetool_shell/filters/build_compiler/summary.py +617 -0
  9. codetool_shell/filters/ci_job_log/__init__.py +7 -0
  10. codetool_shell/filters/ci_job_log/detector.py +64 -0
  11. codetool_shell/filters/ci_job_log/reducer.py +99 -0
  12. codetool_shell/filters/ci_job_log/summary.py +243 -0
  13. codetool_shell/filters/diff/__init__.py +7 -0
  14. codetool_shell/filters/diff/detector.py +136 -0
  15. codetool_shell/filters/diff/reducer.py +308 -0
  16. codetool_shell/filters/generic_log/__init__.py +7 -0
  17. codetool_shell/filters/generic_log/detector.py +175 -0
  18. codetool_shell/filters/generic_log/reducer.py +99 -0
  19. codetool_shell/filters/generic_log/summary.py +161 -0
  20. codetool_shell/filters/git.py +514 -0
  21. codetool_shell/filters/html_cleanup/__init__.py +7 -0
  22. codetool_shell/filters/html_cleanup/detector.py +136 -0
  23. codetool_shell/filters/html_cleanup/reducer.py +27 -0
  24. codetool_shell/filters/html_cleanup/summary.py +422 -0
  25. codetool_shell/filters/json_payload/__init__.py +7 -0
  26. codetool_shell/filters/json_payload/detector.py +62 -0
  27. codetool_shell/filters/json_payload/reducer.py +81 -0
  28. codetool_shell/filters/json_payload/summary.py +233 -0
  29. codetool_shell/filters/listing/__init__.py +7 -0
  30. codetool_shell/filters/listing/detector.py +294 -0
  31. codetool_shell/filters/listing/reducer.py +30 -0
  32. codetool_shell/filters/log_template/__init__.py +7 -0
  33. codetool_shell/filters/log_template/constants.py +76 -0
  34. codetool_shell/filters/log_template/detector.py +331 -0
  35. codetool_shell/filters/log_template/reducer.py +78 -0
  36. codetool_shell/filters/log_template/template.py +280 -0
  37. codetool_shell/filters/log_template/types.py +21 -0
  38. codetool_shell/filters/opaque_payload/__init__.py +7 -0
  39. codetool_shell/filters/opaque_payload/detector.py +563 -0
  40. codetool_shell/filters/opaque_payload/reducer.py +142 -0
  41. codetool_shell/filters/opaque_payload/summary.py +61 -0
  42. codetool_shell/filters/package_manager/__init__.py +7 -0
  43. codetool_shell/filters/package_manager/detector.py +220 -0
  44. codetool_shell/filters/package_manager/reducer.py +110 -0
  45. codetool_shell/filters/package_manager/summary.py +172 -0
  46. codetool_shell/filters/pipeline.py +65 -0
  47. codetool_shell/filters/rg.py +250 -0
  48. codetool_shell/filters/system_output/__init__.py +7 -0
  49. codetool_shell/filters/system_output/detector.py +600 -0
  50. codetool_shell/filters/system_output/reducer.py +331 -0
  51. codetool_shell/filters/system_output/summary.py +164 -0
  52. codetool_shell/filters/table/__init__.py +7 -0
  53. codetool_shell/filters/table/detector.py +244 -0
  54. codetool_shell/filters/table/reducer.py +57 -0
  55. codetool_shell/filters/table/summary.py +37 -0
  56. codetool_shell/filters/test_runner/__init__.py +7 -0
  57. codetool_shell/filters/test_runner/ansi.py +80 -0
  58. codetool_shell/filters/test_runner/detector.py +409 -0
  59. codetool_shell/filters/test_runner/reducer.py +288 -0
  60. codetool_shell/filters/test_runner/summary.py +449 -0
  61. codetool_shell/filters/text.py +38 -0
  62. codetool_shell/filters/traceback/__init__.py +7 -0
  63. codetool_shell/filters/traceback/detector.py +209 -0
  64. codetool_shell/filters/traceback/reducer.py +141 -0
  65. codetool_shell/filters/traceback/summary.py +122 -0
  66. codetool_shell/filters/tree.py +59 -0
  67. codetool_shell/py.typed +0 -0
  68. codetool_shell/python_backend.py +38 -0
  69. codetool_shell/rust_backend.py +254 -0
  70. codetool_shell-0.1.1.dist-info/METADATA +152 -0
  71. codetool_shell-0.1.1.dist-info/RECORD +72 -0
  72. codetool_shell-0.1.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,76 @@
1
+ """Constants for repeated log-template compaction."""
2
+
3
+ from __future__ import annotations
4
+
5
+ _MIN_NONBLANK_LINES = 16
6
+ _MIN_TEMPLATE_MATCHES = 8
7
+ _MIN_TEMPLATED_LINES = 12
8
+ _MIN_TEMPLATED_PERCENT = 35
9
+ _MAX_CAPTURED_VALUES = 5
10
+ _MAX_VALUE_LEN = 48
11
+ _MAX_SHORT_TUPLES = 6
12
+
13
+ _KNOWN_LEVELS = frozenset(
14
+ {
15
+ "TRACE",
16
+ "DEBUG",
17
+ "INFO",
18
+ "NOTICE",
19
+ "WARN",
20
+ "WARNING",
21
+ "ERROR",
22
+ "FATAL",
23
+ "PANIC",
24
+ "CRITICAL",
25
+ "ALERT",
26
+ "EMERG",
27
+ }
28
+ )
29
+ _LOW_LEVELS = frozenset({"TRACE", "DEBUG", "INFO"})
30
+ _IMPORTANT_KV_KEYS = frozenset({"level", "severity", "result", "outcome"})
31
+ _IMPORTANT_KV_VALUES = frozenset(
32
+ {
33
+ "warn",
34
+ "warning",
35
+ "error",
36
+ "fatal",
37
+ "panic",
38
+ "critical",
39
+ "alert",
40
+ "emerg",
41
+ "fail",
42
+ "failed",
43
+ "failure",
44
+ }
45
+ )
46
+ _SECRET_KEY_PARTS = (
47
+ "password",
48
+ "passwd",
49
+ "secret",
50
+ "token",
51
+ "api_key",
52
+ "apikey",
53
+ "authorization",
54
+ "credential",
55
+ "private_key",
56
+ "access_key",
57
+ )
58
+ _OPERATIONAL_WORDS = frozenset(
59
+ {
60
+ "batch",
61
+ "chunk",
62
+ "completed",
63
+ "event",
64
+ "handled",
65
+ "item",
66
+ "iteration",
67
+ "job",
68
+ "message",
69
+ "poll",
70
+ "processed",
71
+ "record",
72
+ "request",
73
+ "shard",
74
+ "task",
75
+ }
76
+ )
@@ -0,0 +1,331 @@
1
+ """Detection and ownership guards for repeated log-template compaction."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections import Counter
6
+
7
+ from .constants import (
8
+ _IMPORTANT_KV_KEYS,
9
+ _IMPORTANT_KV_VALUES,
10
+ _LOW_LEVELS,
11
+ _MIN_NONBLANK_LINES,
12
+ _MIN_TEMPLATE_MATCHES,
13
+ _MIN_TEMPLATED_LINES,
14
+ _MIN_TEMPLATED_PERCENT,
15
+ )
16
+ from .template import (
17
+ _is_secret_key,
18
+ _looks_like_timestamp,
19
+ _normalize_level,
20
+ _parse_key_value_core,
21
+ _parse_key_value_token,
22
+ _split_wrapping_punctuation,
23
+ template_for_line,
24
+ )
25
+ from .types import LogTemplateSignal
26
+
27
+
28
+ def detect_log_templates(lines: list[str]) -> LogTemplateSignal | None:
29
+ if _has_other_filter_ownership(lines):
30
+ return None
31
+
32
+ nonblank_count = sum(1 for line in lines if line.strip())
33
+ if nonblank_count < _MIN_NONBLANK_LINES:
34
+ return None
35
+
36
+ line_templates = tuple(
37
+ None
38
+ if _is_important_line(line.strip()) or _has_forbidden_fragment(line.strip())
39
+ else template_for_line(line)
40
+ for line in lines
41
+ )
42
+ counts: Counter[str] = Counter(
43
+ candidate.template for candidate in line_templates if candidate is not None
44
+ )
45
+ repeated = frozenset(
46
+ template for template, count in counts.items() if count >= _MIN_TEMPLATE_MATCHES
47
+ )
48
+ if not repeated:
49
+ return None
50
+
51
+ templated_total = sum(counts[template] for template in repeated)
52
+ if templated_total < _MIN_TEMPLATED_LINES:
53
+ return None
54
+ if templated_total * 100 < nonblank_count * _MIN_TEMPLATED_PERCENT:
55
+ return None
56
+
57
+ return LogTemplateSignal(
58
+ line_templates=line_templates,
59
+ repeated_templates=repeated,
60
+ )
61
+
62
+
63
+ def _is_important_line(line: str) -> bool:
64
+ lower = line.lower()
65
+ if (
66
+ " warn" in f" {lower}"
67
+ or " warning" in f" {lower}"
68
+ or " error" in f" {lower}"
69
+ or " fatal" in f" {lower}"
70
+ or " panic" in f" {lower}"
71
+ or "exception" in lower
72
+ or "traceback" in lower
73
+ or "stack trace" in lower
74
+ or "failed" in lower
75
+ or "failure" in lower
76
+ or "exit code" in lower
77
+ ):
78
+ return True
79
+
80
+ tokens = line.split()
81
+ if tokens:
82
+ if _line_has_important_key_value(tokens):
83
+ return True
84
+ first = tokens[0].strip("[]")
85
+ if _looks_like_timestamp(first) and len(tokens) > 1:
86
+ level = _normalize_level(tokens[1].strip("[]:=-"))
87
+ return level is not None and level not in _LOW_LEVELS
88
+ level = _normalize_level(first.strip(":=-"))
89
+ if level is not None:
90
+ return level not in _LOW_LEVELS
91
+ return False
92
+
93
+ def _line_has_important_key_value(tokens: list[str]) -> bool:
94
+ for index, token in enumerate(tokens):
95
+ _, core, _ = _split_wrapping_punctuation(token)
96
+ key_value = _parse_key_value_core(core)
97
+ if key_value is not None:
98
+ key, value = key_value
99
+ if _is_important_kv(key, value):
100
+ return True
101
+ continue
102
+
103
+ if ":" not in core:
104
+ continue
105
+ key, value = core.split(":", 1)
106
+ if value:
107
+ if _is_important_kv(key, value):
108
+ return True
109
+ elif index + 1 < len(tokens) and _is_important_kv(key, tokens[index + 1]):
110
+ return True
111
+ return False
112
+
113
+ def _is_important_kv(key: str, value: str) -> bool:
114
+ normalized_key = key.lower().replace("-", "_").replace(".", "_")
115
+ if normalized_key not in _IMPORTANT_KV_KEYS:
116
+ return False
117
+ normalized_level = _normalize_level(value.strip("[],:;"))
118
+ if normalized_level is not None:
119
+ return normalized_level not in _LOW_LEVELS
120
+ return value.strip("[],:;").lower() in _IMPORTANT_KV_VALUES
121
+
122
+ def _has_forbidden_fragment(line: str) -> bool:
123
+ lower = line.lower()
124
+ return (
125
+ "://" in line
126
+ or "www." in lower
127
+ or _line_has_secret_like_key(line)
128
+ or _line_has_path(line)
129
+ or any(marker in lower for marker in ("file \"", " at ", "caused by:"))
130
+ )
131
+
132
+ def _line_has_secret_like_key(line: str) -> bool:
133
+ tokens = line.split()
134
+ for index, token in enumerate(tokens):
135
+ _, core, _ = _split_wrapping_punctuation(token)
136
+ key_value = _parse_key_value_token(token)
137
+ if key_value is not None and _is_secret_key(key_value[0]):
138
+ return True
139
+ if ":" not in core:
140
+ continue
141
+ key, value = core.split(":", 1)
142
+ if _is_secret_key(key) and (value or index + 1 < len(tokens)):
143
+ return True
144
+ return False
145
+
146
+ def _line_has_path(line: str) -> bool:
147
+ file_suffixes = (
148
+ ".py",
149
+ ".rs",
150
+ ".ts",
151
+ ".tsx",
152
+ ".js",
153
+ ".jsx",
154
+ ".json",
155
+ ".yaml",
156
+ ".yml",
157
+ ".toml",
158
+ ".log",
159
+ ".txt",
160
+ ".html",
161
+ ".xml",
162
+ ".csv",
163
+ ".md",
164
+ ".c",
165
+ ".cpp",
166
+ ".go",
167
+ ".rb",
168
+ ".zip",
169
+ ".png",
170
+ )
171
+ lockfile_names = (
172
+ "cargo.lock",
173
+ "package-lock.json",
174
+ "yarn.lock",
175
+ "pnpm-lock.yaml",
176
+ "poetry.lock",
177
+ "go.sum",
178
+ )
179
+ for token in line.split():
180
+ cleaned = token.strip("\"'(),;[]")
181
+ if "://" in cleaned:
182
+ return True
183
+ lower = cleaned.lower()
184
+ if (
185
+ lower in lockfile_names
186
+ or any(lower.endswith(f"={name}") for name in lockfile_names)
187
+ or any(lower.endswith(f"/{name}") for name in lockfile_names)
188
+ or any(lower.endswith(f"\\{name}") for name in lockfile_names)
189
+ or any(
190
+ lower.endswith(suffix) or f"{suffix}:" in lower
191
+ for suffix in file_suffixes
192
+ )
193
+ ):
194
+ return True
195
+ if "/" not in cleaned and "\\" not in cleaned:
196
+ continue
197
+ slash_parts = [part for part in cleaned.replace("\\", "/").split("/") if part]
198
+ if (
199
+ cleaned.startswith(("/", "./", "../", "~/"))
200
+ or ":\\" in cleaned
201
+ or len(slash_parts) >= 3
202
+ ):
203
+ return True
204
+ return False
205
+
206
+ def _has_other_filter_ownership(lines: list[str]) -> bool:
207
+ stripped = [line.strip() for line in lines if line.strip()]
208
+ if not stripped:
209
+ return True
210
+
211
+ lower = "\n".join(stripped).lower()
212
+ if any(
213
+ marker in lower
214
+ for marker in (
215
+ "log templates: repeated line summary",
216
+ "generic log:",
217
+ "test output:",
218
+ "ci log:",
219
+ "python traceback:",
220
+ "traceback (most recent call last)",
221
+ "short test summary info",
222
+ "diff --git ",
223
+ "build diagnostics:",
224
+ "package manager:",
225
+ "jsonl records=",
226
+ )
227
+ ):
228
+ return True
229
+ if any(line.startswith("… ") and " omitted" in line for line in stripped):
230
+ return True
231
+ if _looks_like_json_output(stripped):
232
+ return True
233
+ if _looks_like_diff(stripped):
234
+ return True
235
+ if _looks_like_test_runner(stripped, lower):
236
+ return True
237
+ if _looks_like_ci_log(stripped):
238
+ return True
239
+ if _looks_like_table(stripped):
240
+ return True
241
+ if _looks_like_path_location_output(stripped):
242
+ return True
243
+ if _looks_like_build_or_package_output(stripped, lower):
244
+ return True
245
+ if any(_line_has_secret_like_key(line) for line in stripped):
246
+ return True
247
+ return False
248
+
249
+ def _looks_like_json_output(lines: list[str]) -> bool:
250
+ if len(lines) == 1:
251
+ line = lines[0]
252
+ return line.startswith(("{", "[")) and line.endswith(("}", "]")) and ":" in line
253
+
254
+ joined = "\n".join(lines)
255
+ if joined[:1] in ("{", "[") and joined[-1:] in ("}", "]") and ":" in joined:
256
+ return True
257
+ json_like = sum(
258
+ 1 for line in lines if line.startswith("{") and line.endswith("}") and ":" in line
259
+ )
260
+ return json_like * 100 >= len(lines) * 70
261
+
262
+ def _looks_like_diff(lines: list[str]) -> bool:
263
+ return (
264
+ any(line.startswith(("diff --git ", "Index: ", "@@ ")) for line in lines)
265
+ or (
266
+ any(line.startswith("--- ") for line in lines)
267
+ and any(line.startswith("+++ ") for line in lines)
268
+ )
269
+ )
270
+
271
+ def _looks_like_test_runner(lines: list[str], lower: str) -> bool:
272
+ return (
273
+ "pytest" in lower
274
+ or "test session starts" in lower
275
+ or any(line.startswith(("Test Files", "Tests", "FAIL ")) for line in lines)
276
+ or any(line.startswith("test result:") for line in lines)
277
+ or (
278
+ any(line.startswith("Ran ") and " tests in " in line for line in lines)
279
+ and any(line == "OK" or line.startswith("FAILED (") for line in lines)
280
+ )
281
+ or any(line.startswith("Running ") and " tests using " in line for line in lines)
282
+ )
283
+
284
+ def _looks_like_ci_log(lines: list[str]) -> bool:
285
+ return any(
286
+ line.startswith(("##[", "::group::", "::endgroup::", "::error", "::warning"))
287
+ or "\t" in line
288
+ and "T" in line
289
+ and "Z " in line
290
+ for line in lines
291
+ )
292
+
293
+ def _looks_like_table(lines: list[str]) -> bool:
294
+ pipe_rows = sum(1 for line in lines if "|" in line)
295
+ separator_rows = sum(1 for line in lines if set(line.strip()) <= {"-", "+", "|"})
296
+ return pipe_rows >= 3 and (pipe_rows * 100 >= len(lines) * 55 or separator_rows >= 1)
297
+
298
+ def _looks_like_path_location_output(lines: list[str]) -> bool:
299
+ matches = sum(1 for line in lines if _looks_like_path_location_line(line))
300
+ return matches >= 2 and matches * 100 >= len(lines) * 45
301
+
302
+ def _looks_like_path_location_line(line: str) -> bool:
303
+ parts = line.split(":")
304
+ if len(parts) < 3:
305
+ return False
306
+ return any(part.isdigit() for part in parts[1:3]) and (
307
+ "/" in parts[0] or "." in parts[0]
308
+ )
309
+
310
+ def _looks_like_build_or_package_output(lines: list[str], lower: str) -> bool:
311
+ markers = (
312
+ "compiling ",
313
+ "checking ",
314
+ "finished ",
315
+ "could not compile",
316
+ "error[",
317
+ "warning[",
318
+ "downloading ",
319
+ "downloaded ",
320
+ "installing collected packages",
321
+ "successfully installed",
322
+ "added ",
323
+ "packages audited",
324
+ "resolved ",
325
+ "prepared ",
326
+ "installed ",
327
+ "npm warn",
328
+ "npm err",
329
+ )
330
+ return any(marker in lower for marker in markers)
331
+
@@ -0,0 +1,78 @@
1
+ """Rendering and orchestration for repeated log-template compaction."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..text import join_preserving_final_newline, score, split_preserving_final_newline
6
+ from .constants import _MAX_SHORT_TUPLES
7
+ from .detector import detect_log_templates
8
+ from .types import LogTemplateSignal
9
+
10
+
11
+ def compress_log_template_output(text: str) -> str:
12
+ """Compact repeated scalar-varied log lines into template/value blocks."""
13
+
14
+ lines, final_newline = split_preserving_final_newline(text)
15
+ signal = detect_log_templates(lines)
16
+ if signal is None:
17
+ return text
18
+
19
+ candidate_lines = _render_template_blocks(lines, signal)
20
+ if candidate_lines is None:
21
+ return text
22
+
23
+ candidate = join_preserving_final_newline(candidate_lines, final_newline)
24
+ if score(candidate) < score(text):
25
+ return candidate
26
+ return text
27
+
28
+
29
+ def _render_template_blocks(
30
+ lines: list[str], signal: LogTemplateSignal
31
+ ) -> list[str] | None:
32
+ output: list[str] = []
33
+ emitted = False
34
+ index = 0
35
+
36
+ while index < len(lines):
37
+ candidate = signal.line_templates[index]
38
+ if candidate is None or candidate.template not in signal.repeated_templates:
39
+ output.append(lines[index])
40
+ index += 1
41
+ continue
42
+
43
+ run_template = candidate.template
44
+ run_values: list[tuple[str, ...]] = []
45
+ cursor = index
46
+ while cursor < len(lines):
47
+ run_candidate = signal.line_templates[cursor]
48
+ if run_candidate is None or run_candidate.template != run_template:
49
+ break
50
+ run_values.append(run_candidate.values)
51
+ cursor += 1
52
+
53
+ if len(run_values) < 2:
54
+ output.append(lines[index])
55
+ index += 1
56
+ continue
57
+
58
+ output.append(f"… {len(run_values)} templated lines: {run_template}")
59
+ output.append(f" values: {_format_value_tuples(run_values)}")
60
+ emitted = True
61
+ index = cursor
62
+
63
+ if not emitted:
64
+ return None
65
+ return ["log templates: repeated line summary", *output]
66
+
67
+ def _format_value_tuples(values: list[tuple[str, ...]]) -> str:
68
+ if len(values) <= _MAX_SHORT_TUPLES:
69
+ return "; ".join(_format_tuple(value_tuple) for value_tuple in values)
70
+
71
+ omitted = len(values) - 6
72
+ rendered = [_format_tuple(value_tuple) for value_tuple in values[:3]]
73
+ rendered.append(f"… {omitted} omitted")
74
+ rendered.extend(_format_tuple(value_tuple) for value_tuple in values[-3:])
75
+ return "; ".join(rendered)
76
+
77
+ def _format_tuple(values: tuple[str, ...]) -> str:
78
+ return f"({', '.join(values)})"