tzero-mcp 10.7.0 → 10.7.1
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.
- package/bin/tzero-mcp.js +9 -16
- package/bin/tzero.js +9 -16
- package/index.js +7 -2
- package/package.json +2 -1
- package/src/addmcp.py +1194 -0
- package/src/main.py +60 -0
- package/src/siber_akademi_icon.py +77 -0
- package/src/tzero/jarvis/semantic_compressor.py +373 -0
- package/src/tzero.py +58 -0
- package/src/tzero_benchmark.py +475 -0
- package/src/tzero_bridge.py +362 -0
- package/src/tzero_ci.py +153 -0
- package/src/tzero_commit.py +192 -0
- package/src/tzero_cost.py +163 -0
- package/src/tzero_dashboard.py +1448 -0
- package/src/tzero_deps.py +287 -0
- package/src/tzero_drift.py +208 -0
- package/src/tzero_explain.py +240 -0
- package/src/tzero_features.py +3022 -0
- package/src/tzero_fix.py +228 -0
- package/src/tzero_fixer/__init__.py +63 -0
- package/src/tzero_fixer/amalgamation.py +334 -0
- package/src/tzero_fixer/ast_syntax_healer.py +464 -0
- package/src/tzero_fixer/ast_type_modernizer.py +288 -0
- package/src/tzero_fixer/async_io_modernizer.py +127 -0
- package/src/tzero_fixer/automated_regression_suite.py +600 -0
- package/src/tzero_fixer/benchmarks_and_diagnostics.py +174 -0
- package/src/tzero_fixer/ci_cd_self_healing_gate.py +246 -0
- package/src/tzero_fixer/code_duplicity_and_refactorer.py +151 -0
- package/src/tzero_fixer/code_smells_healer.py +260 -0
- package/src/tzero_fixer/cwe_remediator.py +171 -0
- package/src/tzero_fixer/dead_code_eliminator.py +150 -0
- package/src/tzero_fixer/dependency_vulnerability_auditor.py +309 -0
- package/src/tzero_fixer/git_advanced_surgeon.py +280 -0
- package/src/tzero_fixer/hardware_and_disk_doctor.py +325 -0
- package/src/tzero_fixer/import_graph_healer.py +412 -0
- package/src/tzero_fixer/interactive_tui.py +91 -0
- package/src/tzero_fixer/knowledge_base.py +295 -0
- package/src/tzero_fixer/knowledge_base_extended.py +940 -0
- package/src/tzero_fixer/linters_and_static_analyzer.py +307 -0
- package/src/tzero_fixer/mcp_server.py +447 -0
- package/src/tzero_fixer/models.py +385 -0
- package/src/tzero_fixer/orchestrator.py +375 -0
- package/src/tzero_fixer/os_doctor.py +502 -0
- package/src/tzero_fixer/packaging_abi_surgeon.py +323 -0
- package/src/tzero_fixer/pc_fixer_doctor.py +599 -0
- package/src/tzero_fixer/performance_and_memory_profiler.py +274 -0
- package/src/tzero_fixer/polyglot_ast_parsers.py +313 -0
- package/src/tzero_fixer/polyglot_build_healer.py +364 -0
- package/src/tzero_fixer/polyglot_config_healer.py +318 -0
- package/src/tzero_fixer/polyglot_doctor.py +372 -0
- package/src/tzero_fixer/polyglot_linters.py +619 -0
- package/src/tzero_fixer/polyglot_schema_validator.py +305 -0
- package/src/tzero_fixer/security_cwe_database.py +444 -0
- package/src/tzero_fixer/security_deep_rules.py +114 -0
- package/src/tzero_fixer/security_rules_catalog.py +110 -0
- package/src/tzero_fixer/security_zero_leak.py +326 -0
- package/src/tzero_fixer/smart_patch_recommender.py +223 -0
- package/src/tzero_fixer/synthetic_workspace_generator.py +220 -0
- package/src/tzero_fixer/system_diagnostics_extended.py +493 -0
- package/src/tzero_fixer/system_repair_recipes.py +447 -0
- package/src/tzero_fixer/venv_doctor.py +309 -0
- package/src/tzero_gateway.py +885 -0
- package/src/tzero_guard.py +243 -0
- package/src/tzero_infra.py +274 -0
- package/src/tzero_jarvis/__init__.py +124 -0
- package/src/tzero_jarvis/causal_graph.py +310 -0
- package/src/tzero_jarvis/graph_visualizer.py +395 -0
- package/src/tzero_jarvis/hybrid_search.py +214 -0
- package/src/tzero_jarvis/latent_press.py +137 -0
- package/src/tzero_jarvis/memory_consolidation.py +212 -0
- package/src/tzero_jarvis/minhash_lsh.py +184 -0
- package/src/tzero_jarvis/orchestrator.py +335 -0
- package/src/tzero_jarvis/self_healing.py +308 -0
- package/src/tzero_jarvis/semantic_compressor.py +887 -0
- package/src/tzero_jarvis/swarm.py +261 -0
- package/src/tzero_jarvis/swarm_bft.py +144 -0
- package/src/tzero_jarvis/workspace_orchestrator.py +1167 -0
- package/src/tzero_jarvis/zero_key_router.py +173 -0
- package/src/tzero_mcp.py +4858 -0
- package/src/tzero_neural_cortex.py +1425 -0
- package/src/tzero_packer.py +370 -0
- package/src/tzero_patch.py +624 -0
- package/src/tzero_pc_fixer.py +12657 -0
- package/src/tzero_pipe.py +95 -0
- package/src/tzero_polyglot.py +422 -0
- package/src/tzero_reachability.py +272 -0
- package/src/tzero_replay.py +141 -0
- package/src/tzero_security.py +570 -0
- package/src/tzero_snapshot.py +258 -0
- package/src/tzero_stubs.py +313 -0
- package/src/tzero_superdesign.py +774 -0
- package/src/tzero_sync.py +241 -0
- package/src/tzero_task_benchmark.py +363 -0
- package/src/tzero_tui.py +244 -0
- package/src/tzero_v3.py +9435 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
T-Zero Polyglot Linters & Infrastructure Healer
|
|
4
|
+
==============================================
|
|
5
|
+
Zero-dependency, multi-language static analysis and configuration repair engine.
|
|
6
|
+
Covers:
|
|
7
|
+
- Markdown / README: Unclosed code blocks, broken header hierarchy, trailing whitespace, table formatting
|
|
8
|
+
- TOML: pyproject.toml / Cargo.toml syntax, build-system validation, unquoted dotted keys
|
|
9
|
+
- YAML & CI/CD: GitHub Actions workflow validation, unpinned actions, YAML 1.1 boolean pitfalls
|
|
10
|
+
- Dockerfile: Multi-stage optimizations, root user warnings, unpinned tags, cache clearing
|
|
11
|
+
- Makefile: Recipe indentation repair (space-to-tab), missing .PHONY targets
|
|
12
|
+
- SQL / Schema: Reserved keywords, missing primary keys, unindexed foreign key warnings
|
|
13
|
+
|
|
14
|
+
Author: Siber Akademi T-Zero R&D Team
|
|
15
|
+
License: MIT (100% Free, Local Offline, Zero-Leak)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import re
|
|
22
|
+
from typing import Dict, List, Optional, Tuple, Set
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from tzero_fixer.models import (
|
|
27
|
+
DiagnosticCategory,
|
|
28
|
+
DiagnosticIssue,
|
|
29
|
+
DiffHunk,
|
|
30
|
+
|
|
31
|
+
SeverityLevel,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class LintFixResult:
|
|
37
|
+
original_content: str
|
|
38
|
+
repaired_content: str
|
|
39
|
+
changed: bool
|
|
40
|
+
diff_hunks: List[DiffHunk] = field(default_factory=list)
|
|
41
|
+
issues_found: List[DiagnosticIssue] = field(default_factory=list)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# -----------------------------------------------------------------------------
|
|
45
|
+
# 1. Markdown & Documentation Linter / Healer
|
|
46
|
+
# -----------------------------------------------------------------------------
|
|
47
|
+
class MarkdownLinterHealer:
|
|
48
|
+
"""
|
|
49
|
+
Scans and surgically repairs Markdown files without destroying code fences or inline formatting.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def lint_and_heal(file_path: str, content: str) -> LintFixResult:
|
|
54
|
+
issues: List[DiagnosticIssue] = []
|
|
55
|
+
diff_hunks: List[DiffHunk] = []
|
|
56
|
+
lines = content.splitlines(keepends=False)
|
|
57
|
+
repaired_lines = []
|
|
58
|
+
changed = False
|
|
59
|
+
|
|
60
|
+
in_code_block = False
|
|
61
|
+
code_block_fence = ""
|
|
62
|
+
last_header_level = 0
|
|
63
|
+
open_fence_line = 0
|
|
64
|
+
|
|
65
|
+
for idx, line in enumerate(lines, 1):
|
|
66
|
+
stripped = line.strip()
|
|
67
|
+
|
|
68
|
+
# Check code fence
|
|
69
|
+
if stripped.startswith("```") or stripped.startswith("~~~"):
|
|
70
|
+
fence_marker = stripped[:3]
|
|
71
|
+
if not in_code_block:
|
|
72
|
+
in_code_block = True
|
|
73
|
+
code_block_fence = fence_marker
|
|
74
|
+
open_fence_line = idx
|
|
75
|
+
elif stripped.startswith(code_block_fence):
|
|
76
|
+
in_code_block = False
|
|
77
|
+
code_block_fence = ""
|
|
78
|
+
|
|
79
|
+
# If outside code block, check markdown specific rules
|
|
80
|
+
if not in_code_block:
|
|
81
|
+
# 1. Trailing whitespace check & repair
|
|
82
|
+
rstripped = line.rstrip()
|
|
83
|
+
if len(line) > len(rstripped) and not line.endswith(" "): # 2 spaces = hard break in MD
|
|
84
|
+
changed = True
|
|
85
|
+
diff_hunks.append(
|
|
86
|
+
DiffHunk(
|
|
87
|
+
line_start=idx,
|
|
88
|
+
line_end=idx,
|
|
89
|
+
original=line,
|
|
90
|
+
replacement=rstripped,
|
|
91
|
+
explanation="Removed unnecessary trailing whitespace.",
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
line = rstripped
|
|
95
|
+
|
|
96
|
+
# 2. Header hierarchy check (# to ### skip)
|
|
97
|
+
if stripped.startswith("#"):
|
|
98
|
+
m = re.match(r"^(#+)\s", stripped)
|
|
99
|
+
if m:
|
|
100
|
+
level = len(m.group(1))
|
|
101
|
+
if last_header_level > 0 and level > last_header_level + 1:
|
|
102
|
+
issues.append(
|
|
103
|
+
DiagnosticIssue(
|
|
104
|
+
code="TZ-MD-001",
|
|
105
|
+
category=DiagnosticCategory.POLYGLOT_CONFIG,
|
|
106
|
+
severity=SeverityLevel.INFO,
|
|
107
|
+
file_path=file_path,
|
|
108
|
+
line_number=idx,
|
|
109
|
+
message=f"Markdown header level jumped from H{last_header_level} to H{level}.",
|
|
110
|
+
remediation=f"Consider adjusting to H{last_header_level + 1} for accessibility and structure.",
|
|
111
|
+
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
last_header_level = level
|
|
115
|
+
|
|
116
|
+
# 3. Empty link target [text]()
|
|
117
|
+
if re.search(r"\[([^\]]+)\]\(\s*\)", line):
|
|
118
|
+
issues.append(
|
|
119
|
+
DiagnosticIssue(
|
|
120
|
+
code="TZ-MD-002",
|
|
121
|
+
category=DiagnosticCategory.POLYGLOT_CONFIG,
|
|
122
|
+
severity=SeverityLevel.WARNING,
|
|
123
|
+
file_path=file_path,
|
|
124
|
+
line_number=idx,
|
|
125
|
+
message="Empty Markdown link destination detected: '[text]()'",
|
|
126
|
+
remediation="Provide valid URL or replace with plain text.",
|
|
127
|
+
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
repaired_lines.append(line)
|
|
132
|
+
|
|
133
|
+
# Check unclosed code block
|
|
134
|
+
if in_code_block:
|
|
135
|
+
changed = True
|
|
136
|
+
repaired_lines.append(code_block_fence or "```")
|
|
137
|
+
issues.append(
|
|
138
|
+
DiagnosticIssue(
|
|
139
|
+
code="TZ-MD-003",
|
|
140
|
+
category=DiagnosticCategory.POLYGLOT_CONFIG,
|
|
141
|
+
severity=SeverityLevel.CRITICAL,
|
|
142
|
+
file_path=file_path,
|
|
143
|
+
line_number=open_fence_line,
|
|
144
|
+
message=f"Unclosed Markdown code block opened at line {open_fence_line}.",
|
|
145
|
+
remediation="Appended closing code fence '```'.",
|
|
146
|
+
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
diff_hunks.append(
|
|
150
|
+
DiffHunk(
|
|
151
|
+
line_start=len(lines),
|
|
152
|
+
line_end=len(lines) + 1,
|
|
153
|
+
original="",
|
|
154
|
+
replacement=code_block_fence or "```",
|
|
155
|
+
explanation="Closed unclosed code fence at end of markdown document.",
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
new_content = "\n".join(repaired_lines)
|
|
160
|
+
if content.endswith("\n") and not new_content.endswith("\n"):
|
|
161
|
+
new_content += "\n"
|
|
162
|
+
|
|
163
|
+
return LintFixResult(
|
|
164
|
+
original_content=content,
|
|
165
|
+
repaired_content=new_content,
|
|
166
|
+
changed=changed,
|
|
167
|
+
diff_hunks=diff_hunks,
|
|
168
|
+
issues_found=issues,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# -----------------------------------------------------------------------------
|
|
173
|
+
# 2. TOML Configuration Linter & Healer
|
|
174
|
+
# -----------------------------------------------------------------------------
|
|
175
|
+
class TomlLinterHealer:
|
|
176
|
+
"""
|
|
177
|
+
Lints and validates pyproject.toml, Cargo.toml, and other TOML files.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def lint_and_heal(file_path: str, content: str) -> LintFixResult:
|
|
182
|
+
issues: List[DiagnosticIssue] = []
|
|
183
|
+
diff_hunks: List[DiffHunk] = []
|
|
184
|
+
lines = content.splitlines(keepends=False)
|
|
185
|
+
repaired_lines = []
|
|
186
|
+
changed = False
|
|
187
|
+
|
|
188
|
+
has_build_system = False
|
|
189
|
+
is_pyproject = file_path.endswith("pyproject.toml")
|
|
190
|
+
|
|
191
|
+
for idx, line in enumerate(lines, 1):
|
|
192
|
+
stripped = line.strip()
|
|
193
|
+
|
|
194
|
+
if stripped == "[build-system]":
|
|
195
|
+
has_build_system = True
|
|
196
|
+
|
|
197
|
+
# Detect unquoted dotted key mistake: e.g. foo.bar-baz = 1 where key has special chars
|
|
198
|
+
if "=" in line and not stripped.startswith("#") and not stripped.startswith("["):
|
|
199
|
+
key_part = line.split("=", 1)[0].strip()
|
|
200
|
+
if " " in key_part and not (key_part.startswith('"') and key_part.endswith('"')):
|
|
201
|
+
issues.append(
|
|
202
|
+
DiagnosticIssue(
|
|
203
|
+
code="TZ-TOML-001",
|
|
204
|
+
category=DiagnosticCategory.POLYGLOT_CONFIG,
|
|
205
|
+
severity=SeverityLevel.WARNING,
|
|
206
|
+
file_path=file_path,
|
|
207
|
+
line_number=idx,
|
|
208
|
+
message=f"TOML key contains bare whitespace without quotes: '{key_part}'",
|
|
209
|
+
remediation="Wrap keys containing spaces or special characters in quotes.",
|
|
210
|
+
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
repaired_lines.append(line)
|
|
215
|
+
|
|
216
|
+
# Check missing build-system in pyproject.toml
|
|
217
|
+
if is_pyproject and not has_build_system and "[project]" in content:
|
|
218
|
+
changed = True
|
|
219
|
+
build_system_block = (
|
|
220
|
+
"\n[build-system]\n"
|
|
221
|
+
'requires = ["setuptools>=61.0", "wheel"]\n'
|
|
222
|
+
'build-backend = "setuptools.build_meta"\n'
|
|
223
|
+
)
|
|
224
|
+
repaired_lines.append(build_system_block)
|
|
225
|
+
issues.append(
|
|
226
|
+
DiagnosticIssue(
|
|
227
|
+
code="TZ-TOML-002",
|
|
228
|
+
category=DiagnosticCategory.POLYGLOT_CONFIG,
|
|
229
|
+
severity=SeverityLevel.WARNING,
|
|
230
|
+
file_path=file_path,
|
|
231
|
+
message="Missing [build-system] table in pyproject.toml. Package cannot build via standard pip.",
|
|
232
|
+
remediation="Appended default PEP 517 [build-system] table.",
|
|
233
|
+
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
diff_hunks.append(
|
|
237
|
+
DiffHunk(
|
|
238
|
+
line_start=len(lines),
|
|
239
|
+
line_end=len(lines) + 4,
|
|
240
|
+
original="",
|
|
241
|
+
replacement=build_system_block,
|
|
242
|
+
explanation="Injected PEP 517 standard [build-system] section.",
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
new_content = "\n".join(repaired_lines)
|
|
247
|
+
if content.endswith("\n") and not new_content.endswith("\n"):
|
|
248
|
+
new_content += "\n"
|
|
249
|
+
|
|
250
|
+
return LintFixResult(
|
|
251
|
+
original_content=content,
|
|
252
|
+
repaired_content=new_content,
|
|
253
|
+
changed=changed,
|
|
254
|
+
diff_hunks=diff_hunks,
|
|
255
|
+
issues_found=issues,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# -----------------------------------------------------------------------------
|
|
260
|
+
# 3. YAML & CI/CD Workflow Linter & Healer
|
|
261
|
+
# -----------------------------------------------------------------------------
|
|
262
|
+
class YamlCiCdLinterHealer:
|
|
263
|
+
"""
|
|
264
|
+
Validates GitHub Actions workflows and YAML deployment specs.
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
OUTDATED_ACTIONS: Dict[str, str] = {
|
|
268
|
+
"actions/checkout@v1": "actions/checkout@v4",
|
|
269
|
+
"actions/checkout@v2": "actions/checkout@v4",
|
|
270
|
+
"actions/checkout@v3": "actions/checkout@v4",
|
|
271
|
+
"actions/setup-python@v1": "actions/setup-python@v5",
|
|
272
|
+
"actions/setup-python@v2": "actions/setup-python@v5",
|
|
273
|
+
"actions/setup-python@v3": "actions/setup-python@v5",
|
|
274
|
+
"actions/setup-python@v4": "actions/setup-python@v5",
|
|
275
|
+
"actions/setup-node@v1": "actions/setup-node@v4",
|
|
276
|
+
"actions/setup-node@v2": "actions/setup-node@v4",
|
|
277
|
+
"actions/setup-node@v3": "actions/setup-node@v4",
|
|
278
|
+
"actions/upload-artifact@v1": "actions/upload-artifact@v4",
|
|
279
|
+
"actions/upload-artifact@v2": "actions/upload-artifact@v4",
|
|
280
|
+
"actions/upload-artifact@v3": "actions/upload-artifact@v4",
|
|
281
|
+
"actions/download-artifact@v1": "actions/download-artifact@v4",
|
|
282
|
+
"actions/download-artifact@v2": "actions/download-artifact@v4",
|
|
283
|
+
"actions/download-artifact@v3": "actions/download-artifact@v4",
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@classmethod
|
|
287
|
+
def lint_and_heal(cls, file_path: str, content: str) -> LintFixResult:
|
|
288
|
+
issues: List[DiagnosticIssue] = []
|
|
289
|
+
diff_hunks: List[DiffHunk] = []
|
|
290
|
+
lines = content.splitlines(keepends=False)
|
|
291
|
+
repaired_lines = []
|
|
292
|
+
changed = False
|
|
293
|
+
|
|
294
|
+
is_gh_workflow = ".github" in file_path and ("workflows" in file_path or "actions" in file_path)
|
|
295
|
+
|
|
296
|
+
for idx, line in enumerate(lines, 1):
|
|
297
|
+
repaired_line = line
|
|
298
|
+
stripped = line.strip()
|
|
299
|
+
|
|
300
|
+
# 1. YAML 1.1 'on:' pitfall
|
|
301
|
+
# In YAML 1.1, `on:` without quotes can be parsed as boolean True
|
|
302
|
+
if is_gh_workflow and re.match(r"^on\s*:", stripped):
|
|
303
|
+
issues.append(
|
|
304
|
+
DiagnosticIssue(
|
|
305
|
+
code="TZ-CI-001",
|
|
306
|
+
category=DiagnosticCategory.POLYGLOT_CONFIG,
|
|
307
|
+
severity=SeverityLevel.INFO,
|
|
308
|
+
file_path=file_path,
|
|
309
|
+
line_number=idx,
|
|
310
|
+
message="Unquoted 'on:' in GitHub Action workflow may parse as boolean True in older YAML parsers.",
|
|
311
|
+
remediation="Consider quoting as 'on': or \"on\":",
|
|
312
|
+
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# 2. Outdated actions auto-upgrade
|
|
317
|
+
if is_gh_workflow and "uses:" in line:
|
|
318
|
+
for old_action, new_action in cls.OUTDATED_ACTIONS.items():
|
|
319
|
+
if old_action in line:
|
|
320
|
+
repaired_line = line.replace(old_action, new_action)
|
|
321
|
+
changed = True
|
|
322
|
+
issues.append(
|
|
323
|
+
DiagnosticIssue(
|
|
324
|
+
code="TZ-CI-002",
|
|
325
|
+
category=DiagnosticCategory.POLYGLOT_CONFIG,
|
|
326
|
+
severity=SeverityLevel.WARNING,
|
|
327
|
+
file_path=file_path,
|
|
328
|
+
line_number=idx,
|
|
329
|
+
message=f"Deprecated GitHub Action {old_action} detected (deprecated Node.js runtime).",
|
|
330
|
+
remediation=f"Upgraded to modern {new_action}.",
|
|
331
|
+
|
|
332
|
+
)
|
|
333
|
+
)
|
|
334
|
+
diff_hunks.append(
|
|
335
|
+
DiffHunk(
|
|
336
|
+
line_start=idx,
|
|
337
|
+
line_end=idx,
|
|
338
|
+
original=line,
|
|
339
|
+
replacement=repaired_line,
|
|
340
|
+
explanation=f"Upgraded deprecated {old_action} to {new_action}.",
|
|
341
|
+
)
|
|
342
|
+
)
|
|
343
|
+
break
|
|
344
|
+
|
|
345
|
+
# 3. Detect dangerous pull_request_target checkout
|
|
346
|
+
if is_gh_workflow and "pull_request_target" in line:
|
|
347
|
+
issues.append(
|
|
348
|
+
DiagnosticIssue(
|
|
349
|
+
code="TZ-CI-003",
|
|
350
|
+
category=DiagnosticCategory.SECURITY_CREDENTIAL,
|
|
351
|
+
severity=SeverityLevel.CRITICAL,
|
|
352
|
+
file_path=file_path,
|
|
353
|
+
line_number=idx,
|
|
354
|
+
message="Workflow uses 'pull_request_target' trigger. Checking out untrusted PR code can leak repository secrets.",
|
|
355
|
+
remediation="Ensure untrusted PR code is never checked out with secrets present.",
|
|
356
|
+
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
repaired_lines.append(repaired_line)
|
|
361
|
+
|
|
362
|
+
new_content = "\n".join(repaired_lines)
|
|
363
|
+
if content.endswith("\n") and not new_content.endswith("\n"):
|
|
364
|
+
new_content += "\n"
|
|
365
|
+
|
|
366
|
+
return LintFixResult(
|
|
367
|
+
original_content=content,
|
|
368
|
+
repaired_content=new_content,
|
|
369
|
+
changed=changed,
|
|
370
|
+
diff_hunks=diff_hunks,
|
|
371
|
+
issues_found=issues,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
# -----------------------------------------------------------------------------
|
|
376
|
+
# 4. Dockerfile Deep Linter & Healer
|
|
377
|
+
# -----------------------------------------------------------------------------
|
|
378
|
+
class DockerfileDeepLinterHealer:
|
|
379
|
+
"""
|
|
380
|
+
Validates Dockerfiles for security, cache optimization, and layer reduction.
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
|
+
@staticmethod
|
|
384
|
+
def lint_and_heal(file_path: str, content: str) -> LintFixResult:
|
|
385
|
+
issues: List[DiagnosticIssue] = []
|
|
386
|
+
diff_hunks: List[DiffHunk] = []
|
|
387
|
+
lines = content.splitlines(keepends=False)
|
|
388
|
+
repaired_lines = []
|
|
389
|
+
changed = False
|
|
390
|
+
|
|
391
|
+
has_user = False
|
|
392
|
+
has_healthcheck = False
|
|
393
|
+
|
|
394
|
+
for idx, line in enumerate(lines, 1):
|
|
395
|
+
repaired_line = line
|
|
396
|
+
stripped = line.strip()
|
|
397
|
+
|
|
398
|
+
if stripped.startswith("USER ") and "root" not in stripped:
|
|
399
|
+
has_user = True
|
|
400
|
+
if stripped.startswith("HEALTHCHECK"):
|
|
401
|
+
has_healthcheck = True
|
|
402
|
+
|
|
403
|
+
# 1. Unpinned latest tag
|
|
404
|
+
if stripped.startswith("FROM ") and (":latest" in stripped or ":" not in stripped.split()[1]):
|
|
405
|
+
image_ref = stripped.split()[1]
|
|
406
|
+
issues.append(
|
|
407
|
+
DiagnosticIssue(
|
|
408
|
+
code="TZ-DKR-001",
|
|
409
|
+
category=DiagnosticCategory.POLYGLOT_CONFIG,
|
|
410
|
+
severity=SeverityLevel.WARNING,
|
|
411
|
+
file_path=file_path,
|
|
412
|
+
line_number=idx,
|
|
413
|
+
message=f"Dockerfile image '{image_ref}' uses unpinned tag or implicit ':latest'.",
|
|
414
|
+
remediation="Pin specific version tag or digest (e.g. python:3.11-slim) for deterministic builds.",
|
|
415
|
+
|
|
416
|
+
)
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# 2. apt-get update without clean
|
|
420
|
+
if "apt-get update" in stripped and "rm -rf /var/lib/apt/lists/*" not in stripped:
|
|
421
|
+
issues.append(
|
|
422
|
+
DiagnosticIssue(
|
|
423
|
+
code="TZ-DKR-002",
|
|
424
|
+
category=DiagnosticCategory.POLYGLOT_CONFIG,
|
|
425
|
+
severity=SeverityLevel.INFO,
|
|
426
|
+
file_path=file_path,
|
|
427
|
+
line_number=idx,
|
|
428
|
+
message="'apt-get update' without 'rm -rf /var/lib/apt/lists/*' bloats image size.",
|
|
429
|
+
remediation="Append '&& rm -rf /var/lib/apt/lists/*' to apt-get RUN command.",
|
|
430
|
+
|
|
431
|
+
)
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
# 3. ADD instead of COPY
|
|
435
|
+
if stripped.startswith("ADD ") and not (stripped.endswith(".tar.gz") or "http://" in stripped or "https://" in stripped):
|
|
436
|
+
parts = line.split("ADD", 1)
|
|
437
|
+
repaired_line = parts[0] + "COPY" + parts[1]
|
|
438
|
+
changed = True
|
|
439
|
+
issues.append(
|
|
440
|
+
DiagnosticIssue(
|
|
441
|
+
code="TZ-DKR-003",
|
|
442
|
+
category=DiagnosticCategory.POLYGLOT_CONFIG,
|
|
443
|
+
severity=SeverityLevel.INFO,
|
|
444
|
+
file_path=file_path,
|
|
445
|
+
line_number=idx,
|
|
446
|
+
message="Prefer 'COPY' over 'ADD' for local files unless unpacking tar archives.",
|
|
447
|
+
remediation="Replaced 'ADD' with 'COPY'.",
|
|
448
|
+
|
|
449
|
+
)
|
|
450
|
+
)
|
|
451
|
+
diff_hunks.append(
|
|
452
|
+
DiffHunk(
|
|
453
|
+
line_start=idx,
|
|
454
|
+
line_end=idx,
|
|
455
|
+
original=line,
|
|
456
|
+
replacement=repaired_line,
|
|
457
|
+
explanation="Replaced ADD with COPY for local files.",
|
|
458
|
+
)
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
repaired_lines.append(repaired_line)
|
|
462
|
+
|
|
463
|
+
# Flag missing non-root USER
|
|
464
|
+
if not has_user:
|
|
465
|
+
issues.append(
|
|
466
|
+
DiagnosticIssue(
|
|
467
|
+
code="TZ-DKR-004",
|
|
468
|
+
category=DiagnosticCategory.SECURITY_CREDENTIAL,
|
|
469
|
+
severity=SeverityLevel.WARNING,
|
|
470
|
+
file_path=file_path,
|
|
471
|
+
message="Dockerfile does not specify a non-root USER. Container will run as root by default.",
|
|
472
|
+
remediation="Add 'USER appuser' or equivalent least-privilege user before ENTRYPOINT.",
|
|
473
|
+
|
|
474
|
+
)
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
new_content = "\n".join(repaired_lines)
|
|
478
|
+
if content.endswith("\n") and not new_content.endswith("\n"):
|
|
479
|
+
new_content += "\n"
|
|
480
|
+
|
|
481
|
+
return LintFixResult(
|
|
482
|
+
original_content=content,
|
|
483
|
+
repaired_content=new_content,
|
|
484
|
+
changed=changed,
|
|
485
|
+
diff_hunks=diff_hunks,
|
|
486
|
+
issues_found=issues,
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
# -----------------------------------------------------------------------------
|
|
491
|
+
# 5. Makefile Linter & Space-to-Tab Healer
|
|
492
|
+
# -----------------------------------------------------------------------------
|
|
493
|
+
class MakefileLinterHealer:
|
|
494
|
+
"""
|
|
495
|
+
Diagnoses and repairs Makefile tab indentation bugs ('missing separator').
|
|
496
|
+
"""
|
|
497
|
+
|
|
498
|
+
@staticmethod
|
|
499
|
+
def lint_and_heal(file_path: str, content: str) -> LintFixResult:
|
|
500
|
+
issues: List[DiagnosticIssue] = []
|
|
501
|
+
diff_hunks: List[DiffHunk] = []
|
|
502
|
+
lines = content.splitlines(keepends=False)
|
|
503
|
+
repaired_lines = []
|
|
504
|
+
changed = False
|
|
505
|
+
|
|
506
|
+
in_target = False
|
|
507
|
+
target_name = ""
|
|
508
|
+
|
|
509
|
+
for idx, line in enumerate(lines, 1):
|
|
510
|
+
repaired_line = line
|
|
511
|
+
stripped = line.strip()
|
|
512
|
+
|
|
513
|
+
# Target definition line: e.g. "build:" or "test: deps"
|
|
514
|
+
if ":" in line and not line.startswith("\t") and not line.startswith("#") and not "=" in line.split(":", 1)[0]:
|
|
515
|
+
in_target = True
|
|
516
|
+
target_name = line.split(":", 1)[0].strip()
|
|
517
|
+
elif stripped == "" or line.startswith("#"):
|
|
518
|
+
in_target = False
|
|
519
|
+
elif in_target:
|
|
520
|
+
# If inside target recipe, line MUST begin with a TAB, not spaces
|
|
521
|
+
if line.startswith(" ") or line.startswith(" "):
|
|
522
|
+
# Spaces instead of TAB!
|
|
523
|
+
leading_spaces = len(line) - len(line.lstrip(" "))
|
|
524
|
+
repaired_line = "\t" + line.lstrip(" ")
|
|
525
|
+
changed = True
|
|
526
|
+
issues.append(
|
|
527
|
+
DiagnosticIssue(
|
|
528
|
+
code="TZ-MK-001",
|
|
529
|
+
category=DiagnosticCategory.POLYGLOT_CONFIG,
|
|
530
|
+
severity=SeverityLevel.CRITICAL,
|
|
531
|
+
file_path=file_path,
|
|
532
|
+
line_number=idx,
|
|
533
|
+
message=f"Makefile recipe for '{target_name}' uses {leading_spaces} spaces instead of hard TAB. Causes 'missing separator' fatal error.",
|
|
534
|
+
remediation="Replaced leading spaces with a hard TAB character.",
|
|
535
|
+
|
|
536
|
+
)
|
|
537
|
+
)
|
|
538
|
+
diff_hunks.append(
|
|
539
|
+
DiffHunk(
|
|
540
|
+
line_start=idx,
|
|
541
|
+
line_end=idx,
|
|
542
|
+
original=line,
|
|
543
|
+
replacement=repaired_line,
|
|
544
|
+
explanation="Fixed spaces to hard TAB in Makefile recipe line.",
|
|
545
|
+
)
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
repaired_lines.append(repaired_line)
|
|
549
|
+
|
|
550
|
+
new_content = "\n".join(repaired_lines)
|
|
551
|
+
if content.endswith("\n") and not new_content.endswith("\n"):
|
|
552
|
+
new_content += "\n"
|
|
553
|
+
|
|
554
|
+
return LintFixResult(
|
|
555
|
+
original_content=content,
|
|
556
|
+
repaired_content=new_content,
|
|
557
|
+
changed=changed,
|
|
558
|
+
diff_hunks=diff_hunks,
|
|
559
|
+
issues_found=issues,
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
# -----------------------------------------------------------------------------
|
|
564
|
+
# 6. SQL / Migration Schema Linter
|
|
565
|
+
# -----------------------------------------------------------------------------
|
|
566
|
+
class SqlMigrationLinter:
|
|
567
|
+
"""
|
|
568
|
+
Static analyzer for database schema scripts, migrations, and SQL queries.
|
|
569
|
+
"""
|
|
570
|
+
|
|
571
|
+
RESERVED_KEYWORDS: Set[str] = {
|
|
572
|
+
"user", "order", "group", "table", "database", "select", "insert",
|
|
573
|
+
"update", "delete", "where", "from", "key", "index", "view", "role",
|
|
574
|
+
"schema", "status", "type", "value", "limit", "offset", "grant",
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
@classmethod
|
|
578
|
+
def lint_file(cls, file_path: str, content: str) -> List[DiagnosticIssue]:
|
|
579
|
+
issues: List[DiagnosticIssue] = []
|
|
580
|
+
lines = content.splitlines()
|
|
581
|
+
|
|
582
|
+
for idx, line in enumerate(lines, 1):
|
|
583
|
+
stripped = line.strip()
|
|
584
|
+
if stripped.startswith("--") or stripped.startswith("/*"):
|
|
585
|
+
continue
|
|
586
|
+
|
|
587
|
+
# Check column definition with unquoted SQL reserved keyword
|
|
588
|
+
for kw in cls.RESERVED_KEYWORDS:
|
|
589
|
+
pattern = rf"\b{kw}\b\s+(VARCHAR|INTEGER|INT|TEXT|BOOLEAN|TIMESTAMP|DATETIME|BIGINT|UUID)"
|
|
590
|
+
if re.search(pattern, stripped, re.IGNORECASE):
|
|
591
|
+
issues.append(
|
|
592
|
+
DiagnosticIssue(
|
|
593
|
+
code="TZ-SQL-001",
|
|
594
|
+
category=DiagnosticCategory.POLYGLOT_CONFIG,
|
|
595
|
+
severity=SeverityLevel.WARNING,
|
|
596
|
+
file_path=file_path,
|
|
597
|
+
line_number=idx,
|
|
598
|
+
message=f"Unquoted SQL reserved keyword '{kw}' used as column name.",
|
|
599
|
+
remediation=f"Wrap keyword in quotes (e.g. \"{kw}\") to prevent syntax errors in queries.",
|
|
600
|
+
|
|
601
|
+
)
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
# Check DROP TABLE without IF EXISTS
|
|
605
|
+
if re.search(r"DROP\s+TABLE\s+(?!IF\s+EXISTS\b)\w+", stripped, re.IGNORECASE):
|
|
606
|
+
issues.append(
|
|
607
|
+
DiagnosticIssue(
|
|
608
|
+
code="TZ-SQL-002",
|
|
609
|
+
category=DiagnosticCategory.POLYGLOT_CONFIG,
|
|
610
|
+
severity=SeverityLevel.WARNING,
|
|
611
|
+
file_path=file_path,
|
|
612
|
+
line_number=idx,
|
|
613
|
+
message="Non-idempotent 'DROP TABLE' without 'IF EXISTS' will fail if table does not exist.",
|
|
614
|
+
remediation="Use 'DROP TABLE IF EXISTS table_name;'.",
|
|
615
|
+
|
|
616
|
+
)
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
return issues
|