blasttrace 1.2.0__py2.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.
Potentially problematic release.
This version of blasttrace might be problematic. Click here for more details.
- blasttrace/__init__.py +7 -0
- blasttrace/classifier.py +415 -0
- blasttrace/install_agent.py +220 -0
- blasttrace/license.py +331 -0
- blasttrace/parsers/__init__.py +4 -0
- blasttrace/parsers/api_route_parser.py +318 -0
- blasttrace/parsers/contract_linker.py +815 -0
- blasttrace/parsers/dart_flutter_parser.py +110 -0
- blasttrace/parsers/go_parser.py +196 -0
- blasttrace/parsers/kotlin_android_parser.py +122 -0
- blasttrace/parsers/python_ast_parser.py +340 -0
- blasttrace/parsers/sql_schema_parser.py +376 -0
- blasttrace/parsers/swift_ios_parser.py +127 -0
- blasttrace/parsers/test_mapper.py +182 -0
- blasttrace/parsers/ts_react_parser.py +299 -0
- blasttrace/parsers/vue_nuxt_parser.py +175 -0
- blasttrace/remediation.py +220 -0
- blasttrace/server.py +935 -0
- blasttrace/visualizer.py +165 -0
- blasttrace-1.2.0.dist-info/METADATA +125 -0
- blasttrace-1.2.0.dist-info/RECORD +24 -0
- blasttrace-1.2.0.dist-info/WHEEL +6 -0
- blasttrace-1.2.0.dist-info/entry_points.txt +3 -0
- blasttrace-1.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BlastTrace Surgical Multi-File Sync Plan & Patch Generator
|
|
3
|
+
Produces ordered multi-file synchronization checklists and copy-paste code patches
|
|
4
|
+
across Database, Backend, API, Frontend, and Tests.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any, List, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_LAYER_ORDER = {
|
|
11
|
+
"database": 1,
|
|
12
|
+
"backend": 2,
|
|
13
|
+
"api": 3,
|
|
14
|
+
"frontend": 4,
|
|
15
|
+
"tests": 5,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def generate_surgical_sync_plan(
|
|
20
|
+
topology: Dict[str, Any],
|
|
21
|
+
classification_result: Dict[str, Any],
|
|
22
|
+
downstream_chains: List[List[str]],
|
|
23
|
+
) -> Dict[str, Any]:
|
|
24
|
+
"""
|
|
25
|
+
Generates an ordered checklist of files/functions to synchronize and copy-paste
|
|
26
|
+
remediation patches when a breaking change is detected.
|
|
27
|
+
"""
|
|
28
|
+
gate_status = classification_result.get("gate_status", "PASS")
|
|
29
|
+
classification = classification_result.get("classification", "SAFE_INTERNAL_REFACTOR")
|
|
30
|
+
target_symbol = classification_result.get("target_symbol", "")
|
|
31
|
+
target_file = classification_result.get("target_file", "")
|
|
32
|
+
proposed_edit = classification_result.get("proposed_edit", "")
|
|
33
|
+
|
|
34
|
+
nodes_by_id = topology.get("nodes_by_id", {})
|
|
35
|
+
impacted_nodes: List[Dict[str, Any]] = []
|
|
36
|
+
seen_ids = set()
|
|
37
|
+
|
|
38
|
+
for chain in downstream_chains:
|
|
39
|
+
for idx, nid in enumerate(chain):
|
|
40
|
+
if idx == 0:
|
|
41
|
+
continue
|
|
42
|
+
if nid not in seen_ids:
|
|
43
|
+
seen_ids.add(nid)
|
|
44
|
+
nmeta = nodes_by_id.get(nid, {"id": nid, "name": nid, "layer": "backend", "file": ""})
|
|
45
|
+
impacted_nodes.append(nmeta)
|
|
46
|
+
|
|
47
|
+
impacted_nodes.sort(key=lambda n: (_LAYER_ORDER.get(n.get("layer", "backend"), 9), n.get("file", "")))
|
|
48
|
+
|
|
49
|
+
checklist: List[Dict[str, Any]] = []
|
|
50
|
+
patches: List[Dict[str, str]] = []
|
|
51
|
+
|
|
52
|
+
# 1. Origin backward-compatibility patch
|
|
53
|
+
if classification == "BREAKING_FUNCTION_SIGNATURE":
|
|
54
|
+
patches.append({
|
|
55
|
+
"layer": "backend",
|
|
56
|
+
"file": target_file or "backend_module.py",
|
|
57
|
+
"symbol": target_symbol,
|
|
58
|
+
"strategy": "Provide backward-compatible default parameter value so existing callers do not break",
|
|
59
|
+
"patch": (
|
|
60
|
+
f"# SURGICAL SAFE-COMPATIBILITY PATCH ({target_file})\n"
|
|
61
|
+
f"# Keep newly added parameters optional with a default value during rollout:\n"
|
|
62
|
+
f"{_suggest_safe_signature(target_symbol, proposed_edit)}"
|
|
63
|
+
),
|
|
64
|
+
})
|
|
65
|
+
elif classification == "BREAKING_DATABASE_SCHEMA":
|
|
66
|
+
col_short = target_symbol.split(".")[-1]
|
|
67
|
+
patches.append({
|
|
68
|
+
"layer": "database",
|
|
69
|
+
"file": target_file or "schema.sql",
|
|
70
|
+
"symbol": target_symbol,
|
|
71
|
+
"strategy": "Use Expand-and-Contract two-phase migration instead of immediate column drop/rename",
|
|
72
|
+
"patch": (
|
|
73
|
+
f"-- PHASE 1 (EXPAND): Add new column with DEFAULT and backfill before dropping '{col_short}'\n"
|
|
74
|
+
f"ALTER TABLE {target_symbol.split('.')[0] if '.' in target_symbol else 'target_table'} "
|
|
75
|
+
f"ADD COLUMN IF NOT EXISTS {col_short}_v2 NUMERIC(12, 2) DEFAULT 0.00;\n"
|
|
76
|
+
f"-- Keep '{col_short}' readable until all ORM, API, and Frontend consumers are synced."
|
|
77
|
+
),
|
|
78
|
+
})
|
|
79
|
+
elif classification == "BREAKING_API_CONTRACT":
|
|
80
|
+
patches.append({
|
|
81
|
+
"layer": "api",
|
|
82
|
+
"file": target_file or "api/routes.py",
|
|
83
|
+
"symbol": target_symbol,
|
|
84
|
+
"strategy": "Preserve legacy response field alias alongside the new contract field",
|
|
85
|
+
"patch": (
|
|
86
|
+
f"# SURGICAL API DUAL-EMIT PATCH ({target_file})\n"
|
|
87
|
+
f"# Emit both legacy and updated keys in the JSON payload until frontend deployment completes:\n"
|
|
88
|
+
f"return {{\n"
|
|
89
|
+
f' "status": "ok",\n'
|
|
90
|
+
f' # Retain legacy contract key for active frontend consumers\n'
|
|
91
|
+
f"}}"
|
|
92
|
+
),
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
# 2. Ordered downstream multi-file sync checklist & per-layer patches
|
|
96
|
+
for step_idx, nmeta in enumerate(impacted_nodes, start=1):
|
|
97
|
+
layer = nmeta.get("layer", "backend")
|
|
98
|
+
rel_file = nmeta.get("file", "")
|
|
99
|
+
name = nmeta.get("name", nmeta.get("id", ""))
|
|
100
|
+
ntype = nmeta.get("type", "")
|
|
101
|
+
|
|
102
|
+
action_desc = _describe_sync_action(layer, ntype, name, target_symbol, classification)
|
|
103
|
+
checklist.append({
|
|
104
|
+
"step": step_idx,
|
|
105
|
+
"layer": layer,
|
|
106
|
+
"file": rel_file,
|
|
107
|
+
"symbol": name,
|
|
108
|
+
"node_type": ntype,
|
|
109
|
+
"required_action": action_desc,
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
if layer == "frontend" and rel_file:
|
|
113
|
+
prop_name = nmeta.get("property") or target_symbol.split(".")[-1]
|
|
114
|
+
patches.append({
|
|
115
|
+
"layer": "frontend",
|
|
116
|
+
"file": rel_file,
|
|
117
|
+
"symbol": name,
|
|
118
|
+
"strategy": f"Add defensive fallback on response property '{prop_name}' in {rel_file}",
|
|
119
|
+
"patch": (
|
|
120
|
+
f"// SURGICAL FRONTEND CONSUMER PATCH ({rel_file})\n"
|
|
121
|
+
f"const safe_{prop_name} = data?.{prop_name} ?? data?.{prop_name}_v2 ?? 0;"
|
|
122
|
+
),
|
|
123
|
+
})
|
|
124
|
+
elif layer == "backend" and ntype in ("orm_field", "class_field") and rel_file:
|
|
125
|
+
fld = nmeta.get("field") or target_symbol.split(".")[-1]
|
|
126
|
+
patches.append({
|
|
127
|
+
"layer": "backend",
|
|
128
|
+
"file": rel_file,
|
|
129
|
+
"symbol": name,
|
|
130
|
+
"strategy": f"Synchronize ORM model attribute '{name}' with updated schema definition",
|
|
131
|
+
"patch": (
|
|
132
|
+
f"# SURGICAL ORM SYNC PATCH ({rel_file})\n"
|
|
133
|
+
f"# Ensure '{fld}' maps cleanly or provides a @property compatibility shim:\n"
|
|
134
|
+
f"@property\n"
|
|
135
|
+
f"def {fld}_compat(self):\n"
|
|
136
|
+
f" return getattr(self, '{fld}', None)"
|
|
137
|
+
),
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
impacted_test_files = sorted({
|
|
141
|
+
n.get("file", "")
|
|
142
|
+
for n in impacted_nodes
|
|
143
|
+
if n.get("layer") == "tests" and n.get("file")
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
"gate_status": gate_status,
|
|
148
|
+
"classification": classification,
|
|
149
|
+
"total_impacted_nodes": len(impacted_nodes),
|
|
150
|
+
"ordered_sync_checklist": checklist,
|
|
151
|
+
"surgical_patches": patches,
|
|
152
|
+
"recommended_test_command": (
|
|
153
|
+
f"pytest {' '.join(impacted_test_files)}"
|
|
154
|
+
if impacted_test_files
|
|
155
|
+
else "pytest"
|
|
156
|
+
),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _suggest_safe_signature(symbol_name: str, proposed_edit: str) -> str:
|
|
161
|
+
clean = proposed_edit.strip()
|
|
162
|
+
if clean.startswith(("def ", "async def ")) and ")" in clean:
|
|
163
|
+
head, tail = clean.rsplit(")", 1)
|
|
164
|
+
if "=" not in head.split(",")[-1]:
|
|
165
|
+
return f"{head} = None){tail}"
|
|
166
|
+
return clean
|
|
167
|
+
return f"def {symbol_name}(*args, **kwargs): ..."
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _describe_sync_action(layer: str, ntype: str, name: str, target_symbol: str, classification: str) -> str:
|
|
171
|
+
if layer == "database":
|
|
172
|
+
return f"Update database column/constraint reference '{name}' to match '{target_symbol}'."
|
|
173
|
+
if layer == "backend":
|
|
174
|
+
if ntype in ("orm_model", "orm_field", "class_field"):
|
|
175
|
+
return f"Sync ORM/model field '{name}' with schema change on '{target_symbol}'."
|
|
176
|
+
return f"Update call site or field access inside '{name}' to match new contract of '{target_symbol}'."
|
|
177
|
+
if layer == "api":
|
|
178
|
+
return f"Update API endpoint/serializer '{name}' payload contract and OpenAPI schema."
|
|
179
|
+
if layer == "frontend":
|
|
180
|
+
return f"Update frontend UI component/consumer '{name}' response property binding and TypeScript interface."
|
|
181
|
+
if layer == "tests":
|
|
182
|
+
return f"Re-run and update assertions in covering test '{name}'."
|
|
183
|
+
return f"Synchronize '{name}' with modified contract '{target_symbol}'."
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def generate_cycle_refactoring_plan(cycles: List[List[str]]) -> Dict[str, Any]:
|
|
187
|
+
"""
|
|
188
|
+
Produces surgical refactoring recommendations to break circular import/dependency loops.
|
|
189
|
+
"""
|
|
190
|
+
if not cycles:
|
|
191
|
+
return {
|
|
192
|
+
"status": "Clean",
|
|
193
|
+
"message": "Zero circular code dependencies detected.",
|
|
194
|
+
"refactoring_patches": [],
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
patches: List[Dict[str, str]] = []
|
|
198
|
+
for idx, cycle in enumerate(cycles):
|
|
199
|
+
mod_a = cycle[0]
|
|
200
|
+
mod_b = cycle[1] if len(cycle) > 1 else cycle[0]
|
|
201
|
+
patches.append({
|
|
202
|
+
"cycle_index": idx + 1,
|
|
203
|
+
"cycle_members": cycle,
|
|
204
|
+
"strategic_goal": f"Break circular dependency between '{mod_a}' and '{mod_b}'",
|
|
205
|
+
"recommended_pattern": "Extract shared types/interfaces into a dedicated contracts module or move runtime import inside function scope (TYPE_CHECKING guard).",
|
|
206
|
+
"surgical_patch": (
|
|
207
|
+
f"# SURGICAL CYCLE-BREAKER PATCH (in {mod_a})\n"
|
|
208
|
+
f"from typing import TYPE_CHECKING\n"
|
|
209
|
+
f"if TYPE_CHECKING:\n"
|
|
210
|
+
f" # Keep static type-checking without runtime circular import\n"
|
|
211
|
+
f" import {mod_b.split(':')[-1].replace('/', '.').replace('.py', '')}\n"
|
|
212
|
+
),
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
"status": "Circular Dependencies Detected",
|
|
217
|
+
"cycles_count": len(cycles),
|
|
218
|
+
"refactoring_patches": patches,
|
|
219
|
+
}
|
|
220
|
+
|