codetool-shell 0.1.1__py3-none-win_arm64.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-arm64/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,617 @@
1
+ """Line classification helpers for build/compiler diagnostics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ _ANSI_RE = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]")
8
+ _RUST_DIAGNOSTIC_RE = re.compile(r"^\s*(?:error|warning)(?:\[[A-Za-z0-9_]+\])?:")
9
+ _RUST_SPAN_RE = re.compile(r"^\s*-->\s+\S+:\d+:\d+")
10
+ _RUST_SOURCE_BAR_RE = re.compile(r"^\s*(?:\d+\s+)?\|")
11
+ _TSC_PARENS_RE = re.compile(
12
+ r"^\S.*\.(?:ts|tsx|js|jsx)\(\d+,\d+\):\s+(?:error|warning)\s+TS\d+:",
13
+ re.IGNORECASE,
14
+ )
15
+ _TSC_PRETTY_RE = re.compile(
16
+ r"^\S.*\.(?:ts|tsx|js|jsx):\d+:\d+\s+-\s+(?:error|warning)\s+TS\d+:",
17
+ re.IGNORECASE,
18
+ )
19
+ _ESLINT_ISSUE_RE = re.compile(
20
+ r"^\s*\d+:\d+\s+(?:error|warning)\s+.+\s+[@A-Za-z0-9_./-]+$",
21
+ re.IGNORECASE,
22
+ )
23
+ _ESLINT_COMPACT_RE = re.compile(
24
+ r"^\S.*\.(?:ts|tsx|js|jsx):\d+:\d+:\s+(?:error|warning)\s+.+\s+[@A-Za-z0-9_./-]+$",
25
+ re.IGNORECASE,
26
+ )
27
+ _ESLINT_FILE_HEADER_RE = re.compile(r"^(?:\.{0,2}/|/|[A-Za-z]:[\\/]|~?/)?\S+\.(?:js|jsx|ts|tsx)$")
28
+ _ESLINT_SUMMARY_RE = re.compile(r"\b\d+\s+problems?\b|\b\d+\s+errors?\b.*\b\d+\s+warnings?\b", re.I)
29
+ _MYPY_RE = re.compile(
30
+ r"^(?P<path>.+?\.pyi?):(?P<location>\d+(?::\d+)?):\s+"
31
+ r"(?P<detail>(?:error|warning|note):\s+.+)$",
32
+ re.IGNORECASE,
33
+ )
34
+ _RUFF_RE = re.compile(
35
+ r"^\S.*\.pyi?:\d+:\d+:\s+(?:[A-Z]{1,4}\d{3,4})\b\s+.+$"
36
+ )
37
+ _PYRIGHT_RE = re.compile(
38
+ r"^\s*\S.*\.pyi?:\d+:\d+\s+-\s+"
39
+ r"(?:error|warning|information):\s+.+$",
40
+ re.IGNORECASE,
41
+ )
42
+ _PYRIGHT_FILE_HEADER_RE = re.compile(r"^(?:\.{0,2}/|/|[A-Za-z]:[\\/]|~?/)?\S+\.pyi?$")
43
+ _BIOME_LOCATION_RULE_RE = re.compile(
44
+ r"^\S.*\.(?:js|jsx|ts|tsx|mjs|cjs|json|jsonc|css|md|mdx|vue|svelte|astro)"
45
+ r":\d+:\d+\s+.+\b(?:lint|style|suspicious|correctness|complexity|nursery|a11y)/"
46
+ r"[A-Za-z0-9_./-]+",
47
+ re.IGNORECASE,
48
+ )
49
+ _OXLINT_DIAGNOSTIC_RE = re.compile(
50
+ r"^\s*(?:×|!|error|warning)\s+(?:eslint|oxlint)\([^)]+\):\s+.+$",
51
+ re.IGNORECASE,
52
+ )
53
+ _OXLINT_FRAME_RE = re.compile(
54
+ r"^\s*╭─\[.+\.(?:js|jsx|ts|tsx|mjs|cjs):\d+:\d+\]",
55
+ re.IGNORECASE,
56
+ )
57
+ _SHELLCHECK_HEADER_RE = re.compile(r"^In .+ line \d+:$", re.IGNORECASE)
58
+ _SHELLCHECK_CODE_RE = re.compile(r"\bSC\d{4}\b")
59
+ _SHELLCHECK_GCC_RE = re.compile(
60
+ r"^\S.*:\d+:\d+:\s+(?:error|warning|note|style|info):\s+.+\s+\[SC\d{4}\]",
61
+ re.IGNORECASE,
62
+ )
63
+ _MARKDOWNLINT_RE = re.compile(
64
+ r"^\S.*\.mdx?:\d+(?::\d+)?\s+MD\d{3}(?:/[A-Za-z0-9-]+)?\s+.+$",
65
+ re.IGNORECASE,
66
+ )
67
+ _YAMLLINT_PARSABLE_RE = re.compile(
68
+ r"^\S.*\.ya?ml:\d+:\d+:\s+\[(?:error|warning)\]\s+.+\([A-Za-z0-9_-]+\)",
69
+ re.IGNORECASE,
70
+ )
71
+ _YAMLLINT_GROUPED_RE = re.compile(
72
+ r"^\s*\d+:\d+\s+(?:error|warning)\s+.+\([A-Za-z0-9_-]+\)",
73
+ re.IGNORECASE,
74
+ )
75
+ _YAMLLINT_FILE_HEADER_RE = re.compile(r"^(?:\.{0,2}/|/|[A-Za-z]:[\\/]|~?/)?\S+\.ya?ml$")
76
+ _GOLANGCI_LINTERS = (
77
+ "asasalint",
78
+ "asciicheck",
79
+ "bidichk",
80
+ "bodyclose",
81
+ "canonicalheader",
82
+ "copyloopvar",
83
+ "cyclop",
84
+ "decorder",
85
+ "depguard",
86
+ "dogsled",
87
+ "dupl",
88
+ "durationcheck",
89
+ "errcheck",
90
+ "errchkjson",
91
+ "errorlint",
92
+ "exhaustive",
93
+ "exhaustruct",
94
+ "forbidigo",
95
+ "forcetypeassert",
96
+ "gci",
97
+ "gocheckcompilerdirectives",
98
+ "gochecknoglobals",
99
+ "gochecknoinits",
100
+ "gochecksumtype",
101
+ "gocognit",
102
+ "goconst",
103
+ "gocritic",
104
+ "gocyclo",
105
+ "godot",
106
+ "godox",
107
+ "gofmt",
108
+ "gofumpt",
109
+ "goheader",
110
+ "goimports",
111
+ "gomoddirectives",
112
+ "gomodguard",
113
+ "goprintffuncname",
114
+ "gosec",
115
+ "gosimple",
116
+ "gosmopolitan",
117
+ "govet",
118
+ "grouper",
119
+ "iface",
120
+ "importas",
121
+ "inamedparam",
122
+ "ineffassign",
123
+ "interfacebloat",
124
+ "intrange",
125
+ "lll",
126
+ "loggercheck",
127
+ "maintidx",
128
+ "makezero",
129
+ "mirror",
130
+ "misspell",
131
+ "mnd",
132
+ "musttag",
133
+ "nakedret",
134
+ "nestif",
135
+ "nilerr",
136
+ "nilnil",
137
+ "nlreturn",
138
+ "noctx",
139
+ "nolintlint",
140
+ "nonamedreturns",
141
+ "paralleltest",
142
+ "perfsprint",
143
+ "prealloc",
144
+ "predeclared",
145
+ "promlinter",
146
+ "protogetter",
147
+ "reassign",
148
+ "recvcheck",
149
+ "revive",
150
+ "rowserrcheck",
151
+ "sloglint",
152
+ "spancheck",
153
+ "sqlclosecheck",
154
+ "staticcheck",
155
+ "stylecheck",
156
+ "tagalign",
157
+ "tagliatelle",
158
+ "testableexamples",
159
+ "testifylint",
160
+ "thelper",
161
+ "tparallel",
162
+ "typecheck",
163
+ "unconvert",
164
+ "unparam",
165
+ "unused",
166
+ "usestdlibvars",
167
+ "usetesting",
168
+ "varnamelen",
169
+ "wastedassign",
170
+ "whitespace",
171
+ "wrapcheck",
172
+ "wsl",
173
+ "zerologlint",
174
+ )
175
+ _GOLANGCI_RE = re.compile(
176
+ r"^(?P<path>\S.*\.go):(?P<location>\d+:\d+):\s+"
177
+ r"(?P<detail>.+\((?P<linter>[A-Za-z0-9_-]+)\))$"
178
+ )
179
+ _C_FAMILY_DIAGNOSTIC_RE = re.compile(
180
+ r"^\S.*\.(?:c|cc|cpp|cxx|h|hh|hpp|m|mm|swift):\d+:\d+:\s+"
181
+ r"(?:fatal error|error|warning|note):\s+.+$",
182
+ re.IGNORECASE,
183
+ )
184
+ _GO_BUILD_DIAGNOSTIC_RE = re.compile(
185
+ r"^\S.*\.go:\d+(?::\d+)?:\s+"
186
+ r"(?:undefined:|declared and not used:|cannot use |too many arguments|"
187
+ r"not enough arguments|assignment mismatch|syntax error:|"
188
+ r"imported and not used:|.+\b(?:is not a type|has no field or method)\b).+$"
189
+ )
190
+ _GO_BUILD_PACKAGE_RE = re.compile(r"^#\s+(?:command-line-arguments|\S+[./]\S+)$")
191
+ _MSBUILD_DIAGNOSTIC_RE = re.compile(
192
+ r"^(?:(?:\S.*\.(?:cs|vb|fs)\(\d+,\d+\):|[A-Z][A-Z0-9]*\s+:)\s+)?(?:fatal\s+)?"
193
+ r"(?:error|warning)\s+(?:CS|MSB|NETSDK|NU)\d+:.+$",
194
+ re.IGNORECASE,
195
+ )
196
+ _MSBUILD_TOTAL_RE = re.compile(r"^\s*\d+\s+(?:Warning|Error)\(s\)$", re.IGNORECASE)
197
+ _MAVEN_GRADLE_ERROR_RE = re.compile(r"^\[(?:ERROR|WARNING)\]\s+.+$")
198
+ _GRADLE_TASK_FAILED_RE = re.compile(r"^>\s+Task\s+.+\s+FAILED$")
199
+ _MAKE_NINJA_CMAKE_RE = re.compile(
200
+ r"^(?:make(?:\[\d+\])?: \*\*\* .+|ninja: build stopped: .+|"
201
+ r"CMake Error at .+|CMake Warning at .+)$",
202
+ re.IGNORECASE,
203
+ )
204
+ _NX_TURBO_FAILURE_RE = re.compile(
205
+ r"^(?:Failed tasks?:|Tasks:\s+.*\bfailed\b.*|ERROR\s+run failed:|"
206
+ r".+(?:#|:)(?:build|test|lint|typecheck):\s+command (?:failed|exited).*)$",
207
+ re.IGNORECASE,
208
+ )
209
+ _GOLANGCI_RUNNER_RE = re.compile(
210
+ r"^(?:ERRO|level=error\b|level=fatal\b).*(?:running error|context loading failed|"
211
+ r"can't run linter|failed to load|golangci-lint)",
212
+ re.IGNORECASE,
213
+ )
214
+ _LINT_CONTEXT_RE = re.compile(
215
+ r"^\s*(?:[>|·╭╰├│]|[0-9]+\s*│|[│╰╭].*)$"
216
+ )
217
+ _NOTE_HELP_RE = re.compile(r"^\s*(?:=\s*)?(?:note|help):", re.IGNORECASE)
218
+ _FINAL_SUMMARY_RE = re.compile(
219
+ r"(could not compile|aborting due to|error: build failed|"
220
+ r"warning: .+ generated \d+ warnings?|"
221
+ r"finished `|found \d+ errors? in \d+ files?|"
222
+ r"found \d+ errors?|found \d+ warnings?|"
223
+ r"\d+\s+errors?,\s+\d+\s+warnings?,\s+\d+\s+informations?|"
224
+ r"found \d+\s+warnings?\s+and\s+\d+\s+errors?|"
225
+ r"\d+\s+issues?:|checked \d+ files?|"
226
+ r"success: no issues found|"
227
+ r"\d+\s+problems?\s+\(\d+\s+errors?,\s+\d+\s+warnings?\))",
228
+ re.IGNORECASE,
229
+ )
230
+ _PROGRESS_RE = re.compile(
231
+ r"^\s*(?:Compiling|Checking|Fresh|Building|Running|Downloading|Downloaded|"
232
+ r"Blocking waiting|Waiting|Starting compilation|File change detected|Linting)\b",
233
+ re.IGNORECASE,
234
+ )
235
+ _ERROR_WORD_RE = re.compile(r"(?<![A-Za-z0-9_])(?:error|fatal)(?![A-Za-z0-9_])", re.IGNORECASE)
236
+ _WARNING_WORD_RE = re.compile(r"(?<![A-Za-z0-9_])warning(?![A-Za-z0-9_])", re.IGNORECASE)
237
+ _NONZERO_ERROR_COUNT_RE = re.compile(r"\b[1-9]\d*\s+errors?\b", re.IGNORECASE)
238
+ _NONZERO_WARNING_COUNT_RE = re.compile(r"\b[1-9]\d*\s+warnings?\b", re.IGNORECASE)
239
+ _ZERO_ERROR_WARNING_SUMMARY_RE = re.compile(
240
+ r"\b(?:0\s+errors?.*\b[1-9]\d*\s+warnings?|"
241
+ r"[1-9]\d*\s+warnings?\s+and\s+0\s+errors?)\b",
242
+ re.IGNORECASE,
243
+ )
244
+ _WARNING_DIAGNOSTIC_RE = re.compile(
245
+ r"^\s*(?:\S.*:\d+:\d+\s+-\s+)?warning\b|^\s*[×!]\s+.+\(.*\):",
246
+ re.IGNORECASE,
247
+ )
248
+ _FAILURE_RE = re.compile(
249
+ r"\b(failed|failure)\b|could not compile|aborting due to|running error",
250
+ re.IGNORECASE,
251
+ )
252
+
253
+
254
+ def strip_ansi(text: str) -> str:
255
+ return _ANSI_RE.sub("", text)
256
+
257
+
258
+ def is_rust_diagnostic_line(line: str) -> bool:
259
+ return _RUST_DIAGNOSTIC_RE.match(line) is not None
260
+
261
+
262
+ def is_rust_span_line(line: str) -> bool:
263
+ return _RUST_SPAN_RE.match(line) is not None
264
+
265
+
266
+ def is_rust_source_bar_line(line: str) -> bool:
267
+ return _RUST_SOURCE_BAR_RE.match(line) is not None
268
+
269
+
270
+ def is_tsc_diagnostic_line(line: str) -> bool:
271
+ return _TSC_PARENS_RE.match(line) is not None or _TSC_PRETTY_RE.match(line) is not None
272
+
273
+
274
+ def is_eslint_issue_line(line: str) -> bool:
275
+ return _ESLINT_ISSUE_RE.match(line) is not None or _ESLINT_COMPACT_RE.match(line) is not None
276
+
277
+
278
+ def is_eslint_file_header(line: str) -> bool:
279
+ stripped = line.strip()
280
+ return " " not in stripped and _ESLINT_FILE_HEADER_RE.match(stripped) is not None
281
+
282
+
283
+ def is_eslint_summary_line(line: str) -> bool:
284
+ stripped = line.strip()
285
+ return stripped.startswith("✖") or _ESLINT_SUMMARY_RE.search(stripped) is not None
286
+
287
+
288
+ def is_mypy_diagnostic_line(line: str) -> bool:
289
+ return _MYPY_RE.match(line) is not None
290
+
291
+
292
+ def parse_mypy_diagnostic_line(line: str) -> tuple[str, str, str] | None:
293
+ match = _MYPY_RE.match(line)
294
+ if match is None:
295
+ return None
296
+ return match.group("path"), match.group("location"), match.group("detail")
297
+
298
+
299
+ def is_ruff_diagnostic_line(line: str) -> bool:
300
+ return _RUFF_RE.match(line.strip()) is not None
301
+
302
+
303
+ def is_ruff_summary_line(line: str) -> bool:
304
+ lower = line.strip().lower()
305
+ return (
306
+ lower.startswith("found ") and "error" in lower
307
+ or "fixable with the" in lower
308
+ or lower.startswith("[*] ")
309
+ or lower.startswith("all checks passed")
310
+ )
311
+
312
+
313
+ def is_pyright_diagnostic_line(line: str) -> bool:
314
+ return _PYRIGHT_RE.match(line.strip()) is not None
315
+
316
+
317
+ def is_pyright_summary_line(line: str) -> bool:
318
+ lower = line.strip().lower()
319
+ return (
320
+ " errors," in lower
321
+ and (" warning," in lower or " warnings," in lower)
322
+ and (" information" in lower or " informations" in lower)
323
+ )
324
+
325
+
326
+ def is_pyright_file_header(line: str) -> bool:
327
+ stripped = line.strip()
328
+ return " " not in stripped and _PYRIGHT_FILE_HEADER_RE.match(stripped) is not None
329
+
330
+
331
+ def is_biome_diagnostic_line(line: str) -> bool:
332
+ stripped = line.strip()
333
+ return _BIOME_LOCATION_RULE_RE.match(stripped) is not None
334
+
335
+
336
+ def is_biome_summary_line(line: str) -> bool:
337
+ lower = line.strip().lower()
338
+ return (
339
+ lower.startswith("found ") and ("error" in lower or "warning" in lower)
340
+ or lower.startswith("checked ") and " file" in lower
341
+ or "no fixes applied" in lower
342
+ )
343
+
344
+
345
+ def is_oxlint_diagnostic_line(line: str) -> bool:
346
+ return _OXLINT_DIAGNOSTIC_RE.match(line.strip()) is not None
347
+
348
+
349
+ def is_oxlint_frame_line(line: str) -> bool:
350
+ return _OXLINT_FRAME_RE.match(line.strip()) is not None
351
+
352
+
353
+ def is_oxlint_summary_line(line: str) -> bool:
354
+ lower = line.strip().lower()
355
+ return lower.startswith("found ") and ("warning" in lower or "error" in lower)
356
+
357
+
358
+ def is_shellcheck_header_line(line: str) -> bool:
359
+ return _SHELLCHECK_HEADER_RE.match(line.strip()) is not None
360
+
361
+
362
+ def is_shellcheck_diagnostic_line(line: str) -> bool:
363
+ stripped = line.strip()
364
+ return (
365
+ is_shellcheck_gcc_diagnostic_line(stripped)
366
+ or _SHELLCHECK_CODE_RE.search(stripped) is not None
367
+ )
368
+
369
+
370
+ def is_shellcheck_gcc_diagnostic_line(line: str) -> bool:
371
+ return _SHELLCHECK_GCC_RE.match(line.strip()) is not None
372
+
373
+
374
+ def is_markdownlint_diagnostic_line(line: str) -> bool:
375
+ return _MARKDOWNLINT_RE.match(line.strip()) is not None
376
+
377
+
378
+ def is_yamllint_diagnostic_line(line: str) -> bool:
379
+ stripped = line.strip()
380
+ return (
381
+ _YAMLLINT_PARSABLE_RE.match(stripped) is not None
382
+ or _YAMLLINT_GROUPED_RE.match(line) is not None
383
+ )
384
+
385
+
386
+ def is_yamllint_file_header(line: str) -> bool:
387
+ stripped = line.strip()
388
+ return " " not in stripped and _YAMLLINT_FILE_HEADER_RE.match(stripped) is not None
389
+
390
+
391
+ def is_golangci_lint_issue_line(line: str) -> bool:
392
+ match = _GOLANGCI_RE.match(line.strip())
393
+ return match is not None and match.group("linter").lower() in _GOLANGCI_LINTERS
394
+
395
+
396
+ def parse_golangci_lint_issue_line(line: str) -> tuple[str, str, str] | None:
397
+ match = _GOLANGCI_RE.match(line.strip())
398
+ if match is None or match.group("linter").lower() not in _GOLANGCI_LINTERS:
399
+ return None
400
+ return match.group("path"), match.group("location"), match.group("detail")
401
+
402
+
403
+ def is_golangci_lint_runner_error_line(line: str) -> bool:
404
+ return _GOLANGCI_RUNNER_RE.match(line.strip()) is not None
405
+
406
+
407
+ def is_golangci_lint_summary_line(line: str) -> bool:
408
+ lower = line.strip().lower()
409
+ return lower.endswith("issues:") or lower.endswith("issue:") or "issues before processing" in lower
410
+
411
+
412
+ def is_c_family_diagnostic_line(line: str) -> bool:
413
+ return _C_FAMILY_DIAGNOSTIC_RE.match(line.strip()) is not None
414
+
415
+
416
+ def is_c_family_source_context_line(line: str) -> bool:
417
+ return re.match(r"^\s*(?:\d+\s*)?\|", line.rstrip()) is not None
418
+
419
+
420
+ def is_go_build_package_header(line: str) -> bool:
421
+ return _GO_BUILD_PACKAGE_RE.match(line.strip()) is not None
422
+
423
+
424
+ def is_go_build_diagnostic_line(line: str) -> bool:
425
+ return _GO_BUILD_DIAGNOSTIC_RE.match(line.strip()) is not None
426
+
427
+
428
+ def is_msbuild_diagnostic_line(line: str) -> bool:
429
+ return _MSBUILD_DIAGNOSTIC_RE.match(line.strip()) is not None
430
+
431
+
432
+ def is_msbuild_summary_line(line: str) -> bool:
433
+ stripped = line.strip()
434
+ lower = stripped.lower()
435
+ return (
436
+ stripped.startswith("Build FAILED")
437
+ or stripped.startswith("Build succeeded")
438
+ or _MSBUILD_TOTAL_RE.match(stripped) is not None
439
+ or "time elapsed" in lower
440
+ )
441
+
442
+
443
+ def is_maven_gradle_diagnostic_line(line: str) -> bool:
444
+ stripped = line.strip()
445
+ lower = stripped.lower()
446
+ return (
447
+ _MAVEN_GRADLE_ERROR_RE.match(stripped) is not None
448
+ or _GRADLE_TASK_FAILED_RE.match(stripped) is not None
449
+ or stripped.startswith(("FAILURE:", "* What went wrong:", "* Try:"))
450
+ or "build failed" in lower
451
+ or "build failure" in lower
452
+ or "execution failed for task" in lower
453
+ or "surefire-reports" in lower
454
+ or "test report" in lower
455
+ )
456
+
457
+
458
+ def is_make_ninja_cmake_diagnostic_line(line: str) -> bool:
459
+ stripped = line.strip()
460
+ return _MAKE_NINJA_CMAKE_RE.match(stripped) is not None
461
+
462
+
463
+ def is_nx_turbo_diagnostic_line(line: str) -> bool:
464
+ stripped = line.strip()
465
+ lower = stripped.lower()
466
+ return (
467
+ _NX_TURBO_FAILURE_RE.match(stripped) is not None
468
+ or stripped.startswith(("NX ", "• Running target", "Failed:"))
469
+ or (stripped.startswith("- ") and ("#" in stripped or ":" in stripped))
470
+ or "failed tasks:" in lower
471
+ or "command exited" in lower
472
+ )
473
+
474
+
475
+ def is_swift_xcode_failure_line(line: str) -> bool:
476
+ stripped = line.strip()
477
+ return stripped == "** BUILD FAILED **" or stripped.startswith("The following build commands failed:")
478
+
479
+
480
+ def is_lint_source_context_line(line: str) -> bool:
481
+ stripped = line.rstrip()
482
+ return (
483
+ _LINT_CONTEXT_RE.match(stripped) is not None
484
+ or is_rust_source_bar_line(stripped)
485
+ or is_oxlint_frame_line(stripped)
486
+ or is_c_family_source_context_line(line)
487
+ )
488
+
489
+
490
+ def is_note_help_line(line: str) -> bool:
491
+ stripped = line.strip()
492
+ return _NOTE_HELP_RE.match(line) is not None or stripped.lower().startswith("did you mean:")
493
+
494
+
495
+ def is_final_summary_line(line: str) -> bool:
496
+ return (
497
+ _FINAL_SUMMARY_RE.search(line) is not None
498
+ or is_eslint_summary_line(line)
499
+ or is_ruff_summary_line(line)
500
+ or is_pyright_summary_line(line)
501
+ or is_biome_summary_line(line)
502
+ or is_oxlint_summary_line(line)
503
+ or is_golangci_lint_summary_line(line)
504
+ or is_msbuild_summary_line(line)
505
+ or is_maven_gradle_diagnostic_line(line)
506
+ or is_make_ninja_cmake_diagnostic_line(line)
507
+ or is_nx_turbo_diagnostic_line(line)
508
+ or is_swift_xcode_failure_line(line)
509
+ )
510
+
511
+
512
+ def is_progress_line(line: str) -> bool:
513
+ return _PROGRESS_RE.match(line) is not None
514
+
515
+
516
+ def is_important_diagnostic_line(line: str) -> bool:
517
+ return (
518
+ is_rust_diagnostic_line(line)
519
+ or is_rust_span_line(line)
520
+ or is_rust_source_bar_line(line)
521
+ or is_tsc_diagnostic_line(line)
522
+ or is_eslint_issue_line(line)
523
+ or is_mypy_diagnostic_line(line)
524
+ or is_ruff_diagnostic_line(line)
525
+ or is_pyright_diagnostic_line(line)
526
+ or is_biome_diagnostic_line(line)
527
+ or is_oxlint_diagnostic_line(line)
528
+ or is_oxlint_frame_line(line)
529
+ or is_shellcheck_header_line(line)
530
+ or is_shellcheck_diagnostic_line(line)
531
+ or is_markdownlint_diagnostic_line(line)
532
+ or is_yamllint_diagnostic_line(line)
533
+ or is_golangci_lint_issue_line(line)
534
+ or is_golangci_lint_runner_error_line(line)
535
+ or is_c_family_diagnostic_line(line)
536
+ or is_go_build_package_header(line)
537
+ or is_go_build_diagnostic_line(line)
538
+ or is_msbuild_diagnostic_line(line)
539
+ or is_msbuild_summary_line(line)
540
+ or is_maven_gradle_diagnostic_line(line)
541
+ or is_make_ninja_cmake_diagnostic_line(line)
542
+ or is_nx_turbo_diagnostic_line(line)
543
+ or is_swift_xcode_failure_line(line)
544
+ or is_lint_source_context_line(line)
545
+ or is_note_help_line(line)
546
+ or is_final_summary_line(line)
547
+ )
548
+
549
+
550
+ def is_diagnostic_file_header(kind: str, line: str) -> bool:
551
+ if kind == "eslint":
552
+ return is_eslint_file_header(line)
553
+ if kind == "pyright":
554
+ return is_pyright_file_header(line)
555
+ if kind == "yamllint":
556
+ return is_yamllint_file_header(line)
557
+ if kind == "go-build":
558
+ return is_go_build_package_header(line)
559
+ return False
560
+
561
+
562
+ def diagnostic_context_budget(kind: str, line: str) -> int:
563
+ if is_tsc_diagnostic_line(line):
564
+ return 2
565
+ if kind == "shellcheck" and (
566
+ is_shellcheck_header_line(line) or is_note_help_line(line)
567
+ ):
568
+ return 3 if is_shellcheck_header_line(line) else 1
569
+ if kind in {"ruff", "biome", "oxlint"} and (
570
+ is_ruff_diagnostic_line(line)
571
+ or is_biome_diagnostic_line(line)
572
+ or is_oxlint_diagnostic_line(line)
573
+ or is_oxlint_frame_line(line)
574
+ ):
575
+ return 4
576
+ if is_c_family_diagnostic_line(line):
577
+ return 3
578
+ return 0
579
+
580
+
581
+ def is_safe_snippet_line(line: str) -> bool:
582
+ stripped = line.strip()
583
+ if not stripped or len(stripped) > 240:
584
+ return False
585
+ if is_progress_line(stripped) or is_important_diagnostic_line(stripped):
586
+ return False
587
+ return True
588
+
589
+
590
+ def has_error_signal(lines: list[str]) -> bool:
591
+ for line in lines:
592
+ lower = line.lower()
593
+ if _NONZERO_ERROR_COUNT_RE.search(line) is not None:
594
+ return True
595
+ if _ZERO_ERROR_WARNING_SUMMARY_RE.search(line) is not None:
596
+ continue
597
+ if _FAILURE_RE.search(line) is not None or "error[" in lower:
598
+ return True
599
+ if (
600
+ _ERROR_WORD_RE.search(line) is not None
601
+ and _WARNING_DIAGNOSTIC_RE.search(line) is None
602
+ ):
603
+ return True
604
+ return False
605
+
606
+
607
+ def has_warning_signal(lines: list[str]) -> bool:
608
+ return any(
609
+ _NONZERO_WARNING_COUNT_RE.search(line) is not None
610
+ or _WARNING_WORD_RE.search(line) is not None
611
+ or "warning[" in line.lower()
612
+ for line in lines
613
+ )
614
+
615
+
616
+ def normalize_line(line: str) -> str:
617
+ return line.strip()
@@ -0,0 +1,7 @@
1
+ """CI/job log shellion filter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .reducer import compress_ci_job_log_output
6
+
7
+ __all__ = ["compress_ci_job_log_output"]
@@ -0,0 +1,64 @@
1
+ """Detect conservative CI/job log shapes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from .summary import (
8
+ extract_group_step,
9
+ is_actions_marker,
10
+ is_error_annotation,
11
+ is_failure_line,
12
+ is_group_end,
13
+ is_warning_line,
14
+ parse_ci_line,
15
+ )
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class CiLogSignal:
20
+ """Detected CI log outcome signals."""
21
+
22
+ failed: bool
23
+ warning: bool
24
+
25
+
26
+ def detect_ci_job_log(lines: list[str]) -> CiLogSignal | None:
27
+ """Return a strong GitHub Actions log signal, or ``None``."""
28
+
29
+ parsed_lines = [parse_ci_line(line) for line in lines if line.strip()]
30
+ if len(parsed_lines) < 3:
31
+ return None
32
+
33
+ prefixed_count = sum(1 for line in parsed_lines if line.had_ci_prefix)
34
+ tab_prefixed_count = sum(
35
+ 1 for line in parsed_lines if line.had_ci_prefix and line.job and line.step
36
+ )
37
+ marker_count = sum(1 for line in parsed_lines if is_actions_marker(line.message))
38
+ group_count = sum(
39
+ 1
40
+ for line in parsed_lines
41
+ if extract_group_step(line.message) is not None or is_group_end(line.message)
42
+ )
43
+ annotation_count = sum(
44
+ 1
45
+ for line in parsed_lines
46
+ if is_error_annotation(line.message) or is_warning_line(line.message)
47
+ )
48
+
49
+ has_strong_prefix_shape = prefixed_count >= 2 and marker_count >= 1
50
+ has_strong_tab_shape = tab_prefixed_count >= 2 and marker_count >= 1
51
+ has_workflow_command_shape = marker_count >= 2 and (
52
+ group_count >= 1 or annotation_count >= 1
53
+ )
54
+ if not (
55
+ has_strong_prefix_shape
56
+ or has_strong_tab_shape
57
+ or has_workflow_command_shape
58
+ ):
59
+ return None
60
+
61
+ return CiLogSignal(
62
+ failed=any(is_failure_line(line.message) for line in parsed_lines),
63
+ warning=any(is_warning_line(line.message) for line in parsed_lines),
64
+ )