vigil-codeintel 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.
- vigil_codeintel-0.1.0.dist-info/METADATA +780 -0
- vigil_codeintel-0.1.0.dist-info/RECORD +131 -0
- vigil_codeintel-0.1.0.dist-info/WHEEL +5 -0
- vigil_codeintel-0.1.0.dist-info/entry_points.txt +3 -0
- vigil_codeintel-0.1.0.dist-info/licenses/LICENSE +21 -0
- vigil_codeintel-0.1.0.dist-info/top_level.txt +3 -0
- vigil_forensic/__init__.py +224 -0
- vigil_forensic/_git_utils.py +178 -0
- vigil_forensic/_shared.py +510 -0
- vigil_forensic/_stubs.py +156 -0
- vigil_forensic/gate_checks/__init__.py +1 -0
- vigil_forensic/gate_checks/_ast_helpers.py +629 -0
- vigil_forensic/gate_checks/_deployment_detector.py +573 -0
- vigil_forensic/gate_checks/atomic_write_checks.py +1143 -0
- vigil_forensic/gate_checks/authority_checks.py +95 -0
- vigil_forensic/gate_checks/boundary_breach_checks.py +202 -0
- vigil_forensic/gate_checks/broad_except_checks.py +301 -0
- vigil_forensic/gate_checks/broad_except_hidden_sentinel_checks.py +365 -0
- vigil_forensic/gate_checks/common.py +253 -0
- vigil_forensic/gate_checks/config_safety_checks.py +704 -0
- vigil_forensic/gate_checks/config_ssot_checks.py +78 -0
- vigil_forensic/gate_checks/conflict_checks.py +193 -0
- vigil_forensic/gate_checks/context_fallback_checks.py +697 -0
- vigil_forensic/gate_checks/context_health_checks.py +289 -0
- vigil_forensic/gate_checks/contract_shape_drift_checks.py +459 -0
- vigil_forensic/gate_checks/dirty_baseline_check.py +274 -0
- vigil_forensic/gate_checks/duplication_checks.py +387 -0
- vigil_forensic/gate_checks/embedded_string_checks.py +123 -0
- vigil_forensic/gate_checks/empty_output_checks.py +87 -0
- vigil_forensic/gate_checks/encoding_checks.py +847 -0
- vigil_forensic/gate_checks/export_completeness_checks.py +156 -0
- vigil_forensic/gate_checks/fallback_checks.py +41 -0
- vigil_forensic/gate_checks/file_proliferation_checks.py +171 -0
- vigil_forensic/gate_checks/fix_without_test_checks.py +69 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/__init__.py +9 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/_helpers.py +71 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/advanced_checks.py +322 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/core.py +273 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/integrity_checks.py +203 -0
- vigil_forensic/gate_checks/forensic_cluster_runners/quality_checks.py +666 -0
- vigil_forensic/gate_checks/forensic_clusters/__init__.py +193 -0
- vigil_forensic/gate_checks/forensic_clusters/allowlist.py +426 -0
- vigil_forensic/gate_checks/forensic_clusters/allowlist_writer.py +302 -0
- vigil_forensic/gate_checks/forensic_clusters/api_protocol.py +231 -0
- vigil_forensic/gate_checks/forensic_clusters/async_quality.py +1156 -0
- vigil_forensic/gate_checks/forensic_clusters/code_style.py +808 -0
- vigil_forensic/gate_checks/forensic_clusters/core.py +319 -0
- vigil_forensic/gate_checks/forensic_clusters/data_quality.py +763 -0
- vigil_forensic/gate_checks/forensic_clusters/dead_code.py +480 -0
- vigil_forensic/gate_checks/forensic_clusters/edit_mutation.py +842 -0
- vigil_forensic/gate_checks/forensic_clusters/exception_boundary.py +240 -0
- vigil_forensic/gate_checks/forensic_clusters/legacy_debt.py +556 -0
- vigil_forensic/gate_checks/forensic_clusters/static_analysis.py +834 -0
- vigil_forensic/gate_checks/forensic_clusters/structural_quality.py +298 -0
- vigil_forensic/gate_checks/god_object_zones_checks.py +173 -0
- vigil_forensic/gate_checks/hallucination_checks.py +566 -0
- vigil_forensic/gate_checks/hunter_artifact_completeness_check.py +139 -0
- vigil_forensic/gate_checks/implementation_overfit_checks.py +380 -0
- vigil_forensic/gate_checks/import_integrity_checks.py +233 -0
- vigil_forensic/gate_checks/imports_in_function_checks.py +283 -0
- vigil_forensic/gate_checks/ml_checks.py +318 -0
- vigil_forensic/gate_checks/performance_checks.py +106 -0
- vigil_forensic/gate_checks/project_specific_runner.py +691 -0
- vigil_forensic/gate_checks/provider_capability_checks.py +73 -0
- vigil_forensic/gate_checks/refactor_completeness_checks.py +274 -0
- vigil_forensic/gate_checks/reliability_checks.py +389 -0
- vigil_forensic/gate_checks/reporting_checks.py +55 -0
- vigil_forensic/gate_checks/runtime_behavior_checks.py +220 -0
- vigil_forensic/gate_checks/security_injection_checks.py +332 -0
- vigil_forensic/gate_checks/semantic_intent_checks.py +139 -0
- vigil_forensic/gate_checks/size_complexity_checks.py +336 -0
- vigil_forensic/gate_checks/stuck_feature_flag_checks.py +354 -0
- vigil_forensic/gate_checks/syntax_validity_checks.py +217 -0
- vigil_forensic/gate_checks/temporal_freshness_checks.py +79 -0
- vigil_forensic/gate_checks/test_quality_checks.py +946 -0
- vigil_forensic/gate_checks/testing_checks.py +149 -0
- vigil_forensic/gate_checks/toctou_checks.py +367 -0
- vigil_forensic/gate_checks/type_checking_checks.py +316 -0
- vigil_forensic/gate_models.py +392 -0
- vigil_forensic/gate_packs/__init__.py +1 -0
- vigil_forensic/gate_packs/universal.py +179 -0
- vigil_forensic/gate_profile.json +31 -0
- vigil_forensic/gate_registry.py +21 -0
- vigil_forensic/language_profiles.py +219 -0
- vigil_forensic/meta_findings.py +207 -0
- vigil_forensic/self_audit.py +725 -0
- vigil_forensic/source_analysis.py +175 -0
- vigil_mapper/__init__.py +103 -0
- vigil_mapper/_ast_helpers_minimal.py +229 -0
- vigil_mapper/_extract_imports_impl.py +123 -0
- vigil_mapper/_file_count_guard.py +129 -0
- vigil_mapper/_git_utils.py +178 -0
- vigil_mapper/_runtime_ast.py +438 -0
- vigil_mapper/_runtime_dispatch.py +137 -0
- vigil_mapper/_seed_helpers.py +82 -0
- vigil_mapper/authority_builder.py +1102 -0
- vigil_mapper/cli_entry.py +731 -0
- vigil_mapper/conflict_builder.py +818 -0
- vigil_mapper/data_contract_builder.py +446 -0
- vigil_mapper/findings_builder.py +716 -0
- vigil_mapper/fingerprint.py +53 -0
- vigil_mapper/hotspot_builder.py +539 -0
- vigil_mapper/map_common.py +449 -0
- vigil_mapper/map_errors.py +55 -0
- vigil_mapper/map_models.py +431 -0
- vigil_mapper/map_models_ext.py +206 -0
- vigil_mapper/map_models_findings.py +130 -0
- vigil_mapper/map_storage.py +455 -0
- vigil_mapper/parse_cache.py +795 -0
- vigil_mapper/refactor_boundary_builder.py +266 -0
- vigil_mapper/runtime_builder.py +527 -0
- vigil_mapper/runtime_tracer.py +243 -0
- vigil_mapper/runtime_tracer_entry.py +199 -0
- vigil_mapper/semantic_diff.py +71 -0
- vigil_mapper/source_adapters/__init__.py +109 -0
- vigil_mapper/source_adapters/_base.py +264 -0
- vigil_mapper/source_adapters/_ir.py +156 -0
- vigil_mapper/source_adapters/_lexer.py +309 -0
- vigil_mapper/source_adapters/_patterns.py +212 -0
- vigil_mapper/source_adapters/_treesitter.py +182 -0
- vigil_mapper/source_adapters/go.py +553 -0
- vigil_mapper/source_adapters/java.py +541 -0
- vigil_mapper/source_adapters/javascript.py +626 -0
- vigil_mapper/source_adapters/python.py +325 -0
- vigil_mapper/source_adapters/typescript.py +749 -0
- vigil_mapper/structural_builder.py +586 -0
- vigil_mcp/__init__.py +1 -0
- vigil_mcp/_jobs.py +587 -0
- vigil_mcp/_paths.py +93 -0
- vigil_mcp/forensic_server.py +419 -0
- vigil_mcp/map_server.py +452 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Forensic check: detect Python/JS escape conflicts in embedded code strings.
|
|
2
|
+
|
|
3
|
+
When JavaScript or CSS lives inside Python triple-quoted strings (*_js.py,
|
|
4
|
+
*_css.py, *_assets*.py), Python interprets \\n as literal newline. Inside a
|
|
5
|
+
JS single-quoted string literal, a literal newline is a syntax error that
|
|
6
|
+
kills the entire <script> block silently.
|
|
7
|
+
|
|
8
|
+
This check scans touched *_js.py / *_css.py / *_assets*.py files for
|
|
9
|
+
unescaped \\n/\\t/\\r inside JS string literals (single or double quoted).
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
|
|
15
|
+
from vigil_forensic._shared import EvidenceReference, GateCategory, GateImpact, GateSeverity
|
|
16
|
+
from vigil_forensic.gate_models import PostExecGateContext
|
|
17
|
+
from .common import build_check_result, build_finding, iter_touched_snapshots
|
|
18
|
+
from ..source_analysis import is_source_file
|
|
19
|
+
import logging
|
|
20
|
+
_log = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
# Matches: '...\n...' or "...\n..." inside Python triple-quoted strings
|
|
23
|
+
# that will render as literal newlines in JS.
|
|
24
|
+
# We look for single-char \n \t \r that are NOT preceded by another backslash.
|
|
25
|
+
# Pattern: a line containing a JS string with unescaped newline-producing escape.
|
|
26
|
+
_JS_FILE_PATTERNS = ("_js.py", "_css.py", "_assets")
|
|
27
|
+
|
|
28
|
+
# Inside a Python """...""", these are literal control chars:
|
|
29
|
+
# '\n' -> actual newline (JS syntax error in string literal)
|
|
30
|
+
# '\t' -> actual tab (usually OK but suspicious)
|
|
31
|
+
# '\r' -> actual CR (JS syntax error in string literal)
|
|
32
|
+
# We detect: a quote, then content with a real newline before closing quote.
|
|
33
|
+
# Simpler approach: find lines with orphaned quotes (string opened but not closed on same line)
|
|
34
|
+
# inside files that embed JS.
|
|
35
|
+
|
|
36
|
+
# Even simpler: scan for the pattern that caused the real bug:
|
|
37
|
+
# Python source has 'something\nsomething' (without raw prefix or double-backslash)
|
|
38
|
+
# which renders as a literal newline inside JS.
|
|
39
|
+
_DANGEROUS_ESCAPE_RE = re.compile(
|
|
40
|
+
r"""(?<![\\])\\n(?![\\])""" # \n not preceded or followed by another backslash
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def run_embedded_string_checks(ctx: PostExecGateContext):
|
|
45
|
+
"""Scan *_js.py / *_css.py for Python escape sequences that break embedded JS/CSS."""
|
|
46
|
+
findings = []
|
|
47
|
+
for snapshot in iter_touched_snapshots(ctx):
|
|
48
|
+
if not snapshot.exists:
|
|
49
|
+
continue
|
|
50
|
+
if not any(pat in snapshot.path for pat in _JS_FILE_PATTERNS):
|
|
51
|
+
continue
|
|
52
|
+
if not is_source_file(snapshot.path):
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
# We need to check the PYTHON SOURCE, not the rendered output.
|
|
56
|
+
# Look for lines inside triple-quoted strings that contain
|
|
57
|
+
# single-backslash \n which Python will turn into a literal newline.
|
|
58
|
+
# The tricky part: we're reading the file as Python source.
|
|
59
|
+
lines = snapshot.text.splitlines()
|
|
60
|
+
in_triple = False
|
|
61
|
+
triple_char = ""
|
|
62
|
+
for line_idx, line in enumerate(lines, 1):
|
|
63
|
+
# Track triple-quote state (simplified — enough for _js.py files)
|
|
64
|
+
count_triple_dq = line.count('"""')
|
|
65
|
+
count_triple_sq = line.count("'''")
|
|
66
|
+
if count_triple_dq % 2 == 1:
|
|
67
|
+
in_triple = not in_triple
|
|
68
|
+
triple_char = '"'
|
|
69
|
+
if count_triple_sq % 2 == 1:
|
|
70
|
+
in_triple = not in_triple
|
|
71
|
+
triple_char = "'"
|
|
72
|
+
|
|
73
|
+
if not in_triple:
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
# Inside a triple-quoted string: look for JS string literals
|
|
77
|
+
# containing \n that Python will interpret as literal newline.
|
|
78
|
+
# In the Python source, this looks like: split('\n')
|
|
79
|
+
# The \n here is a real newline in the output.
|
|
80
|
+
# We want to find: quote + backslash-n + quote patterns
|
|
81
|
+
# that are NOT \\n (escaped backslash).
|
|
82
|
+
for m in re.finditer(r"""(?:['"]).*?(?<!\\)\\n.*?(?:['"])""", line):
|
|
83
|
+
# Check it's not \\n (double backslash)
|
|
84
|
+
match_text = m.group()
|
|
85
|
+
if "\\\\n" in match_text:
|
|
86
|
+
continue # properly escaped
|
|
87
|
+
findings.append(
|
|
88
|
+
build_finding(
|
|
89
|
+
check_id="embedded_string.unescaped_newline",
|
|
90
|
+
category=GateCategory.FALLBACK,
|
|
91
|
+
title=f"Unescaped \\n in JS/CSS string: {snapshot.path}:{line_idx}",
|
|
92
|
+
severity=GateSeverity.HIGH,
|
|
93
|
+
impact=GateImpact.REVISE,
|
|
94
|
+
summary=(
|
|
95
|
+
f"File {snapshot.path} line {line_idx} contains '\\n' inside a "
|
|
96
|
+
"Python triple-quoted string that embeds JS/CSS. Python renders "
|
|
97
|
+
"this as a literal newline, which is a JS syntax error inside "
|
|
98
|
+
"string literals. The entire <script> block will fail to parse."
|
|
99
|
+
),
|
|
100
|
+
recommendation=(
|
|
101
|
+
"Use '\\\\n' (double backslash) so Python outputs '\\n' which "
|
|
102
|
+
"JS interprets as a newline escape. Or use a raw string r'...'."
|
|
103
|
+
),
|
|
104
|
+
evidence=[
|
|
105
|
+
EvidenceReference(
|
|
106
|
+
kind="file",
|
|
107
|
+
path=snapshot.path,
|
|
108
|
+
detail=f"line:{line_idx} match:{match_text[:60]}",
|
|
109
|
+
)
|
|
110
|
+
],
|
|
111
|
+
|
|
112
|
+
repair_kind='refactor',
|
|
113
|
+
executor_action='Address finding details',
|
|
114
|
+
proof_required='No embedded strings',
|
|
115
|
+
allowlist_allowed=False,
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return build_check_result(
|
|
120
|
+
check_id="embedded_string",
|
|
121
|
+
category=GateCategory.FALLBACK,
|
|
122
|
+
findings=findings,
|
|
123
|
+
)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from vigil_forensic._shared import BINARY_EXTENSIONS as _BINARY_EXTENSIONS
|
|
5
|
+
from vigil_forensic._shared import EvidenceReference, GateCategory, GateImpact, GateSeverity
|
|
6
|
+
from vigil_forensic.gate_models import PostExecGateContext
|
|
7
|
+
from .common import build_check_result, build_finding, normalize_path
|
|
8
|
+
import logging
|
|
9
|
+
_log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
_EMPTY_ALLOWLIST = {"__init__.py", ".gitkeep"}
|
|
12
|
+
# Sprint C3 (2026-04-23): _BINARY_EXTENSIONS imported above from
|
|
13
|
+
# SYSTEM.shared_helpers.file_extensions. Keep the private alias so existing
|
|
14
|
+
# call sites (abs_path.suffix.lower() in _BINARY_EXTENSIONS) resolve.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def run_empty_output_checks(ctx: PostExecGateContext):
|
|
18
|
+
findings = []
|
|
19
|
+
if not ctx.changed_files_observed or ctx.task_intent == "metadata_only":
|
|
20
|
+
return build_check_result(check_id="empty_output", category=GateCategory.REPORTING)
|
|
21
|
+
for raw_path in ctx.changed_files_observed:
|
|
22
|
+
normalized = normalize_path(raw_path)
|
|
23
|
+
abs_path = ctx.project_dir / normalized
|
|
24
|
+
|
|
25
|
+
# Probe existence first. A PermissionError on stat() (raised inside
|
|
26
|
+
# pathlib.Path.exists) must NOT be masked as a missing or empty
|
|
27
|
+
# file: that would silently hide a real issue. Surface it via
|
|
28
|
+
# meta.file_unreadable and move on.
|
|
29
|
+
try:
|
|
30
|
+
present = abs_path.exists()
|
|
31
|
+
except (PermissionError, OSError) as exc:
|
|
32
|
+
from vigil_forensic.meta_findings import emit_meta_finding
|
|
33
|
+
emit_meta_finding(
|
|
34
|
+
"meta.file_unreadable",
|
|
35
|
+
path=normalized,
|
|
36
|
+
detail=f"{type(exc).__name__}: {exc}",
|
|
37
|
+
)
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
if not present:
|
|
41
|
+
continue # drift_checks handles missing files
|
|
42
|
+
try:
|
|
43
|
+
is_file = abs_path.is_file()
|
|
44
|
+
except (PermissionError, OSError) as exc:
|
|
45
|
+
from vigil_forensic.meta_findings import emit_meta_finding
|
|
46
|
+
emit_meta_finding(
|
|
47
|
+
"meta.file_unreadable",
|
|
48
|
+
path=normalized,
|
|
49
|
+
detail=f"{type(exc).__name__}: {exc}",
|
|
50
|
+
)
|
|
51
|
+
continue
|
|
52
|
+
if not is_file:
|
|
53
|
+
continue
|
|
54
|
+
basename = abs_path.name
|
|
55
|
+
if basename in _EMPTY_ALLOWLIST:
|
|
56
|
+
continue
|
|
57
|
+
if abs_path.suffix.lower() in _BINARY_EXTENSIONS:
|
|
58
|
+
continue
|
|
59
|
+
try:
|
|
60
|
+
size = abs_path.stat().st_size
|
|
61
|
+
except (PermissionError, OSError) as exc:
|
|
62
|
+
from vigil_forensic.meta_findings import emit_meta_finding
|
|
63
|
+
emit_meta_finding(
|
|
64
|
+
"meta.file_unreadable",
|
|
65
|
+
path=normalized,
|
|
66
|
+
detail=f"{type(exc).__name__}: {exc}",
|
|
67
|
+
)
|
|
68
|
+
continue
|
|
69
|
+
if size == 0:
|
|
70
|
+
findings.append(
|
|
71
|
+
build_finding(
|
|
72
|
+
check_id="empty_output.zero_bytes",
|
|
73
|
+
category=GateCategory.REPORTING,
|
|
74
|
+
title=f"Changed file is empty (0 bytes): {normalized}",
|
|
75
|
+
severity=GateSeverity.MEDIUM,
|
|
76
|
+
impact=GateImpact.REVISE,
|
|
77
|
+
summary=f"Executor reported {normalized} as changed but the file is 0 bytes. This may indicate a false-success or truncated write.",
|
|
78
|
+
recommendation="Verify the file was written correctly. If intentionally empty, document why.",
|
|
79
|
+
evidence=[EvidenceReference(kind="file", path=normalized, detail="0 bytes")],
|
|
80
|
+
|
|
81
|
+
repair_kind='fix_contract',
|
|
82
|
+
executor_action='Fix empty output',
|
|
83
|
+
proof_required='Non-empty output',
|
|
84
|
+
allowlist_allowed=False,
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
return build_check_result(check_id="empty_output", category=GateCategory.REPORTING, findings=findings)
|