polymath-nodeos 1.0.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.
- polymath_nodeos/__init__.py +0 -0
- polymath_nodeos/daemon.py +301 -0
- polymath_nodeos/graph_manager.py +113 -0
- polymath_nodeos/jage_engine.py +178 -0
- polymath_nodeos/nodes_engine.py +246 -0
- polymath_nodeos/scripts/__init__.py +0 -0
- polymath_nodeos/scripts/agy_invoke.py +20 -0
- polymath_nodeos/scripts/blast_radius_engine.py +127 -0
- polymath_nodeos/scripts/dead_code_engine.py +103 -0
- polymath_nodeos/scripts/domino_engine.py +93 -0
- polymath_nodeos/scripts/ghost_writer_engine.py +135 -0
- polymath_nodeos/scripts/intent_injector.py +32 -0
- polymath_nodeos/scripts/intent_stopper.py +28 -0
- polymath_nodeos/scripts/ipc_hook.py +27 -0
- polymath_nodeos/scripts/legacy_math.py +3 -0
- polymath_nodeos/scripts/telemetry.py +69 -0
- polymath_nodeos-1.0.0.dist-info/METADATA +113 -0
- polymath_nodeos-1.0.0.dist-info/RECORD +21 -0
- polymath_nodeos-1.0.0.dist-info/WHEEL +5 -0
- polymath_nodeos-1.0.0.dist-info/entry_points.txt +4 -0
- polymath_nodeos-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import sqlite3
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
class Point:
|
|
6
|
+
def __init__(self, x, y, node_id, node_type, mass=1.0, name="unknown", calls=None, filepath=None):
|
|
7
|
+
self.x = x
|
|
8
|
+
self.y = y
|
|
9
|
+
self.vx = 0.0
|
|
10
|
+
self.vy = 0.0
|
|
11
|
+
self.fx = 0.0
|
|
12
|
+
self.fy = 0.0
|
|
13
|
+
self.mass = mass
|
|
14
|
+
self.node_id = node_id
|
|
15
|
+
self.node_type = node_type
|
|
16
|
+
self.name = name
|
|
17
|
+
self.calls = calls if calls else []
|
|
18
|
+
self.edges = []
|
|
19
|
+
self.filepath = filepath
|
|
20
|
+
|
|
21
|
+
class Rectangle:
|
|
22
|
+
def __init__(self, x, y, w, h):
|
|
23
|
+
self.x = x
|
|
24
|
+
self.y = y
|
|
25
|
+
self.w = w
|
|
26
|
+
self.h = h
|
|
27
|
+
|
|
28
|
+
def contains(self, point):
|
|
29
|
+
return (self.x - self.w <= point.x <= self.x + self.w and
|
|
30
|
+
self.y - self.h <= point.y <= self.y + self.h)
|
|
31
|
+
|
|
32
|
+
def intersects(self, range_rect):
|
|
33
|
+
return not (range_rect.x - range_rect.w > self.x + self.w or
|
|
34
|
+
range_rect.x + range_rect.w < self.x - self.w or
|
|
35
|
+
range_rect.y - range_rect.h > self.y + self.h or
|
|
36
|
+
range_rect.y + range_rect.h < self.y - self.h)
|
|
37
|
+
|
|
38
|
+
class QuadTree:
|
|
39
|
+
def __init__(self, boundary, capacity):
|
|
40
|
+
self.boundary = boundary
|
|
41
|
+
self.capacity = capacity
|
|
42
|
+
self.points = []
|
|
43
|
+
self.divided = False
|
|
44
|
+
|
|
45
|
+
def subdivide(self):
|
|
46
|
+
x, y, w, h = self.boundary.x, self.boundary.y, self.boundary.w / 2, self.boundary.h / 2
|
|
47
|
+
self.northeast = QuadTree(Rectangle(x + w, y - h, w, h), self.capacity)
|
|
48
|
+
self.northwest = QuadTree(Rectangle(x - w, y - h, w, h), self.capacity)
|
|
49
|
+
self.southeast = QuadTree(Rectangle(x + w, y + h, w, h), self.capacity)
|
|
50
|
+
self.southwest = QuadTree(Rectangle(x - w, y + h, w, h), self.capacity)
|
|
51
|
+
self.divided = True
|
|
52
|
+
|
|
53
|
+
def insert(self, point):
|
|
54
|
+
if not self.boundary.contains(point):
|
|
55
|
+
return False
|
|
56
|
+
if len(self.points) < self.capacity:
|
|
57
|
+
self.points.append(point)
|
|
58
|
+
return True
|
|
59
|
+
if not self.divided:
|
|
60
|
+
self.subdivide()
|
|
61
|
+
return (self.northeast.insert(point) or self.northwest.insert(point) or
|
|
62
|
+
self.southeast.insert(point) or self.southwest.insert(point))
|
|
63
|
+
|
|
64
|
+
def query(self, range_rect, found=None):
|
|
65
|
+
if found is None: found = []
|
|
66
|
+
if not self.boundary.intersects(range_rect): return found
|
|
67
|
+
for p in self.points:
|
|
68
|
+
if range_rect.contains(p): found.append(p)
|
|
69
|
+
if self.divided:
|
|
70
|
+
self.northwest.query(range_rect, found)
|
|
71
|
+
self.northeast.query(range_rect, found)
|
|
72
|
+
self.southwest.query(range_rect, found)
|
|
73
|
+
self.southeast.query(range_rect, found)
|
|
74
|
+
return found
|
|
75
|
+
|
|
76
|
+
class NativeNodesEngine:
|
|
77
|
+
"""
|
|
78
|
+
NATIVE SPATIAL CLUSTERING & PHYSICS ENGINE
|
|
79
|
+
Implements Force-Directed Graph physics (Repulsion + Gravity + Hooke's Law Springs) and batch SQLite sync.
|
|
80
|
+
"""
|
|
81
|
+
def __init__(self, db_path='agy_nodeos.db'):
|
|
82
|
+
self.db_path = db_path
|
|
83
|
+
self.boundary = Rectangle(500, 500, 500, 500)
|
|
84
|
+
self.qtree = QuadTree(self.boundary, 4)
|
|
85
|
+
self.all_nodes = []
|
|
86
|
+
self.hydrate_from_db()
|
|
87
|
+
|
|
88
|
+
def hydrate_from_db(self):
|
|
89
|
+
print("[Physics Engine] Hydrating spatial matrix from SQLite Database...")
|
|
90
|
+
conn = sqlite3.connect(self.db_path)
|
|
91
|
+
cursor = conn.cursor()
|
|
92
|
+
try:
|
|
93
|
+
cursor.execute("SELECT node_id, node_type, x_coord, y_coord, name, filepath FROM nodes")
|
|
94
|
+
rows = cursor.fetchall()
|
|
95
|
+
for row in rows:
|
|
96
|
+
if row[2] is not None and row[3] is not None:
|
|
97
|
+
name = row[4] if len(row) > 4 else "unknown"
|
|
98
|
+
filepath = row[5] if len(row) > 5 else None
|
|
99
|
+
p = Point(row[2], row[3], row[0], row[1], name=name, filepath=filepath)
|
|
100
|
+
self.all_nodes.append(p)
|
|
101
|
+
self.qtree.insert(p)
|
|
102
|
+
print(f"[Physics Engine] Hydrated {len(self.all_nodes)} kinetic nodes.")
|
|
103
|
+
except sqlite3.OperationalError:
|
|
104
|
+
print("[Physics Engine] Table not initialized. Skipping hydration.")
|
|
105
|
+
conn.close()
|
|
106
|
+
|
|
107
|
+
def rebuild_qtree(self):
|
|
108
|
+
self.qtree = QuadTree(self.boundary, 4)
|
|
109
|
+
for p in self.all_nodes:
|
|
110
|
+
self.qtree.insert(p)
|
|
111
|
+
|
|
112
|
+
def resolve_edges(self):
|
|
113
|
+
"""Resolves the string AST calls into physical Point references for Spring Physics."""
|
|
114
|
+
print("[Physics Engine] Resolving execution call graph into physical edges...")
|
|
115
|
+
name_to_node = {p.name: p for p in self.all_nodes}
|
|
116
|
+
edge_count = 0
|
|
117
|
+
|
|
118
|
+
conn = sqlite3.connect(self.db_path)
|
|
119
|
+
cursor = conn.cursor()
|
|
120
|
+
|
|
121
|
+
for p in self.all_nodes:
|
|
122
|
+
for call_name in p.calls:
|
|
123
|
+
target = name_to_node.get(call_name)
|
|
124
|
+
if target and target not in p.edges:
|
|
125
|
+
p.edges.append(target)
|
|
126
|
+
edge_count += 1
|
|
127
|
+
try:
|
|
128
|
+
cursor.execute('''
|
|
129
|
+
INSERT OR IGNORE INTO edges (source_id, target_id, relation_type)
|
|
130
|
+
VALUES (?, ?, "CALL")
|
|
131
|
+
''', (p.node_id, target.node_id))
|
|
132
|
+
except sqlite3.OperationalError:
|
|
133
|
+
pass # Ignore if edges table missing/already exists
|
|
134
|
+
|
|
135
|
+
conn.commit()
|
|
136
|
+
conn.close()
|
|
137
|
+
print(f"[Physics Engine] Bonded {edge_count} structural connections. Simulating graph clustering...")
|
|
138
|
+
if edge_count > 0:
|
|
139
|
+
import threading
|
|
140
|
+
# Run physics simulation in the background so it doesn't block the Daemon event loop
|
|
141
|
+
threading.Thread(target=self.simulate_physics, args=(100,), daemon=True).start()
|
|
142
|
+
|
|
143
|
+
def simulate_physics(self, ticks=50):
|
|
144
|
+
"""Euler Integration of Coulomb Repulsion, Central Gravity, and Hooke's Law Springs."""
|
|
145
|
+
damping = 0.85
|
|
146
|
+
time_step = 1.0
|
|
147
|
+
k_repulse = 5000.0
|
|
148
|
+
k_gravity = 0.05
|
|
149
|
+
k_spring = 0.1 # Hooke's Law spring constant
|
|
150
|
+
ideal_length = 50.0 # Ideal distance between connected nodes
|
|
151
|
+
center_x, center_y = 500.0, 500.0
|
|
152
|
+
|
|
153
|
+
for tick in range(ticks):
|
|
154
|
+
# Rebuild QuadTree for rapid O(N log N) spatial queries
|
|
155
|
+
self.rebuild_qtree()
|
|
156
|
+
|
|
157
|
+
for node in self.all_nodes:
|
|
158
|
+
node.fx = (center_x - node.x) * k_gravity
|
|
159
|
+
node.fy = (center_y - node.y) * k_gravity
|
|
160
|
+
|
|
161
|
+
# Optimized Local Repulsion: Only repel against nodes within 150px radius using QuadTree
|
|
162
|
+
range_rect = Rectangle(node.x, node.y, 150, 150)
|
|
163
|
+
nearby_nodes = self.qtree.query(range_rect)
|
|
164
|
+
|
|
165
|
+
for other in nearby_nodes:
|
|
166
|
+
if other == node:
|
|
167
|
+
continue
|
|
168
|
+
dx, dy = node.x - other.x, node.y - other.y
|
|
169
|
+
dist_sq = dx**2 + dy**2
|
|
170
|
+
if dist_sq > 0:
|
|
171
|
+
force = k_repulse / dist_sq
|
|
172
|
+
dist = math.sqrt(dist_sq)
|
|
173
|
+
node.fx += force * (dx / dist)
|
|
174
|
+
node.fy += force * (dy / dist)
|
|
175
|
+
|
|
176
|
+
# Hooke's Law Spring Attraction between CONNECTED nodes
|
|
177
|
+
for n1 in self.all_nodes:
|
|
178
|
+
for n2 in n1.edges:
|
|
179
|
+
dx, dy = n2.x - n1.x, n2.y - n1.y
|
|
180
|
+
dist = math.sqrt(dx**2 + dy**2)
|
|
181
|
+
if dist > 0:
|
|
182
|
+
force = k_spring * (dist - ideal_length)
|
|
183
|
+
fx = force * (dx / dist)
|
|
184
|
+
fy = force * (dy / dist)
|
|
185
|
+
n1.fx += fx
|
|
186
|
+
n1.fy += fy
|
|
187
|
+
n2.fx -= fx
|
|
188
|
+
n2.fy -= fy
|
|
189
|
+
|
|
190
|
+
# Euler Integration
|
|
191
|
+
for node in self.all_nodes:
|
|
192
|
+
ax = node.fx / node.mass
|
|
193
|
+
ay = node.fy / node.mass
|
|
194
|
+
node.vx = (node.vx + ax * time_step) * damping
|
|
195
|
+
node.vy = (node.vy + ay * time_step) * damping
|
|
196
|
+
node.x += node.vx * time_step
|
|
197
|
+
node.y += node.vy * time_step
|
|
198
|
+
|
|
199
|
+
self.rebuild_qtree()
|
|
200
|
+
self.sync_to_sqlite()
|
|
201
|
+
sys.stderr.write(f"[Physics Engine] Successfully settled {len(self.all_nodes)} nodes. Syncing to SQLite.\n")
|
|
202
|
+
|
|
203
|
+
def sync_to_sqlite(self):
|
|
204
|
+
"""Batch update physical positions to SQLite to save I/O overhead."""
|
|
205
|
+
conn = sqlite3.connect(self.db_path)
|
|
206
|
+
cursor = conn.cursor()
|
|
207
|
+
for p in self.all_nodes:
|
|
208
|
+
cursor.execute('''
|
|
209
|
+
INSERT INTO nodes (node_id, node_type, name, filepath, x_coord, y_coord, last_updated)
|
|
210
|
+
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
211
|
+
ON CONFLICT(node_id) DO UPDATE SET
|
|
212
|
+
name=excluded.name,
|
|
213
|
+
filepath=excluded.filepath,
|
|
214
|
+
x_coord=excluded.x_coord,
|
|
215
|
+
y_coord=excluded.y_coord,
|
|
216
|
+
last_updated=excluded.last_updated
|
|
217
|
+
''', (p.node_id, p.node_type, p.name, p.filepath, p.x, p.y))
|
|
218
|
+
conn.commit()
|
|
219
|
+
conn.close()
|
|
220
|
+
|
|
221
|
+
def remove_nodes_by_file(self, filepath):
|
|
222
|
+
"""Removes all spatial nodes belonging to a deleted file."""
|
|
223
|
+
sys.stderr.write(f"[Physics Engine] Removing spatial nodes for deleted file: {filepath}\n")
|
|
224
|
+
self.all_nodes = [node for node in self.all_nodes if node.filepath != filepath]
|
|
225
|
+
for node in self.all_nodes:
|
|
226
|
+
node.edges = [e for e in node.edges if e.filepath != filepath]
|
|
227
|
+
self.rebuild_qtree()
|
|
228
|
+
|
|
229
|
+
def add_node(self, node_id, node_type, name="unknown", calls=None, parent_x=500, parent_y=500, filepath=None):
|
|
230
|
+
import random
|
|
231
|
+
drop_x = parent_x + random.uniform(-10, 10)
|
|
232
|
+
drop_y = parent_y + random.uniform(-10, 10)
|
|
233
|
+
|
|
234
|
+
existing = next((n for n in self.all_nodes if n.node_id == node_id), None)
|
|
235
|
+
if not existing:
|
|
236
|
+
p = Point(drop_x, drop_y, node_id, node_type, name=name, calls=calls, filepath=filepath)
|
|
237
|
+
self.all_nodes.append(p)
|
|
238
|
+
sys.stderr.write(f"[Physics Engine] Dropped {node_type} '{name}' into kinetic simulation.\n")
|
|
239
|
+
return p
|
|
240
|
+
else:
|
|
241
|
+
existing.name = name
|
|
242
|
+
if filepath:
|
|
243
|
+
existing.filepath = filepath
|
|
244
|
+
if calls:
|
|
245
|
+
existing.calls = calls
|
|
246
|
+
return existing
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import json
|
|
3
|
+
import telemetry
|
|
4
|
+
|
|
5
|
+
def invoke(payload_str):
|
|
6
|
+
try:
|
|
7
|
+
workflow = json.loads(payload_str)
|
|
8
|
+
with open('workflow.json', 'w') as f:
|
|
9
|
+
json.dump(workflow, f, indent=4)
|
|
10
|
+
|
|
11
|
+
# Now natively stream the NodeOS telemetry to AGY's sys.stdout
|
|
12
|
+
telemetry.track_workflow('workflow.json')
|
|
13
|
+
except Exception as e:
|
|
14
|
+
print(f"Failed to invoke workflow: {e}")
|
|
15
|
+
|
|
16
|
+
if __name__ == "__main__":
|
|
17
|
+
if len(sys.argv) > 1:
|
|
18
|
+
invoke(sys.argv[1])
|
|
19
|
+
else:
|
|
20
|
+
print("Usage: python agy_invoke.py '<json_string>'")
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - [%(levelname)s] - %(message)s')
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class BlastRadiusEngine:
|
|
11
|
+
"""
|
|
12
|
+
Engine to calculate the blast radius of file modifications using a Recursive CTE
|
|
13
|
+
against the NodeOS SQLite graph.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, db_path: str, workspace: str, threshold: int = 10):
|
|
17
|
+
self.db_path = db_path
|
|
18
|
+
self.workspace = workspace
|
|
19
|
+
self.threshold = threshold
|
|
20
|
+
|
|
21
|
+
def get_blast_radius(self, filepath: str) -> int:
|
|
22
|
+
"""
|
|
23
|
+
Calculates the number of downstream nodes that depend on the given filepath.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
filepath: The path of the modified file.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
int: The number of distinct downstream nodes impacted.
|
|
30
|
+
"""
|
|
31
|
+
if not os.path.exists(self.db_path):
|
|
32
|
+
logger.error(f"Database not found at {self.db_path}")
|
|
33
|
+
return 0
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
37
|
+
cursor = conn.cursor()
|
|
38
|
+
|
|
39
|
+
query = '''
|
|
40
|
+
WITH RECURSIVE downstream AS (
|
|
41
|
+
-- Base Case: Nodes that directly call nodes in the modified file
|
|
42
|
+
SELECT e.source_id
|
|
43
|
+
FROM edges e
|
|
44
|
+
JOIN nodes n ON e.target_id = n.node_id
|
|
45
|
+
WHERE n.filepath = ?
|
|
46
|
+
|
|
47
|
+
UNION
|
|
48
|
+
|
|
49
|
+
-- Recursive Step: Nodes that call the nodes found in the previous step
|
|
50
|
+
SELECT e.source_id
|
|
51
|
+
FROM edges e
|
|
52
|
+
INNER JOIN downstream d ON e.target_id = d.source_id
|
|
53
|
+
)
|
|
54
|
+
SELECT COUNT(DISTINCT source_id) FROM downstream;
|
|
55
|
+
'''
|
|
56
|
+
cursor.execute(query, (filepath,))
|
|
57
|
+
result = cursor.fetchone()
|
|
58
|
+
return result[0] if result else 0
|
|
59
|
+
except sqlite3.Error as e:
|
|
60
|
+
logger.error(f"SQLite error while calculating blast radius: {e}")
|
|
61
|
+
return 0
|
|
62
|
+
|
|
63
|
+
def enforce_threshold(self, filepath: str) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Checks the blast radius for the filepath and emits warnings if it exceeds the threshold.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
filepath: The path of the modified file.
|
|
69
|
+
"""
|
|
70
|
+
radius_count = self.get_blast_radius(filepath)
|
|
71
|
+
logger.info(f"Blast radius for {filepath}: {radius_count} downstream nodes.")
|
|
72
|
+
|
|
73
|
+
if radius_count > self.threshold:
|
|
74
|
+
logger.warning(f"High Blast Radius ({radius_count} dependents) for {filepath}")
|
|
75
|
+
self._drop_intent(filepath, radius_count)
|
|
76
|
+
self._write_warning_file(filepath, radius_count)
|
|
77
|
+
|
|
78
|
+
def _drop_intent(self, filepath: str, radius_count: int) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Drops a workflow.json intent to notify the agent swarm.
|
|
81
|
+
"""
|
|
82
|
+
workflow_file = os.path.join(self.workspace, "workflow.json")
|
|
83
|
+
workflow = {
|
|
84
|
+
"type": "JSON_Task",
|
|
85
|
+
"status": "pending",
|
|
86
|
+
"action": "blast_radius_warning",
|
|
87
|
+
"target_file": filepath,
|
|
88
|
+
"impacted_nodes_count": radius_count
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
with open(workflow_file, 'w') as f:
|
|
93
|
+
json.dump(workflow, f, indent=4)
|
|
94
|
+
logger.info(f"Dropped intent at {workflow_file}")
|
|
95
|
+
except IOError as e:
|
|
96
|
+
logger.error(f"Failed to write workflow.json: {e}")
|
|
97
|
+
|
|
98
|
+
def _write_warning_file(self, filepath: str, radius_count: int) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Writes a warning.md file in the same directory as the modified file.
|
|
101
|
+
"""
|
|
102
|
+
warning_path = os.path.join(os.path.dirname(filepath), "warning.md")
|
|
103
|
+
|
|
104
|
+
content = (
|
|
105
|
+
f"# High Blast Radius Warning\n"
|
|
106
|
+
f"Modifying `{filepath}` impacts {radius_count} downstream nodes. "
|
|
107
|
+
f"Proceed with caution.\n"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
with open(warning_path, 'w') as w_file:
|
|
112
|
+
w_file.write(content)
|
|
113
|
+
logger.info(f"Wrote warning markdown at {warning_path}")
|
|
114
|
+
except IOError as e:
|
|
115
|
+
logger.error(f"Failed to write warning.md: {e}")
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
import argparse
|
|
119
|
+
parser = argparse.ArgumentParser(description="Calculate Blast Radius for a file in AGY-NodeOS.")
|
|
120
|
+
parser.add_argument("--db", required=True, help="Path to agy_nodeos.db")
|
|
121
|
+
parser.add_argument("--workspace", required=True, help="Path to workspace root")
|
|
122
|
+
parser.add_argument("--file", required=True, help="Modified filepath to check")
|
|
123
|
+
parser.add_argument("--threshold", type=int, default=10, help="Blast radius threshold")
|
|
124
|
+
|
|
125
|
+
args = parser.parse_args()
|
|
126
|
+
engine = BlastRadiusEngine(db_path=args.db, workspace=args.workspace, threshold=args.threshold)
|
|
127
|
+
engine.enforce_threshold(args.file)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
from typing import List, Dict, Any
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class OrphanedNode:
|
|
10
|
+
node_id: str
|
|
11
|
+
filepath: str
|
|
12
|
+
node_type: str
|
|
13
|
+
|
|
14
|
+
class SemanticDeadCodeEngine:
|
|
15
|
+
"""
|
|
16
|
+
Advanced engine for eradicating semantic dead code in AGY-NodeOS.
|
|
17
|
+
Queries the SQLite dependency graph to identify orphaned nodes
|
|
18
|
+
and generates swarm intents for safe deletion.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, db_path: str, workspace_root: str):
|
|
22
|
+
self.db_path = db_path
|
|
23
|
+
self.workspace_root = workspace_root
|
|
24
|
+
self.workflow_path = os.path.join(self.workspace_root, "workflow.json")
|
|
25
|
+
|
|
26
|
+
def check_database_exists(self) -> bool:
|
|
27
|
+
"""Verifies if the agy_nodeos.db exists."""
|
|
28
|
+
return os.path.exists(self.db_path)
|
|
29
|
+
|
|
30
|
+
def find_orphaned_nodes(self) -> List[OrphanedNode]:
|
|
31
|
+
"""
|
|
32
|
+
Executes the SQL query to find nodes with 0 incoming edges.
|
|
33
|
+
Excludes 'entry_point' nodes to prevent removing roots.
|
|
34
|
+
"""
|
|
35
|
+
if not self.check_database_exists():
|
|
36
|
+
raise FileNotFoundError(f"Database not found at {self.db_path}")
|
|
37
|
+
|
|
38
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
39
|
+
cursor = conn.cursor()
|
|
40
|
+
query = """
|
|
41
|
+
SELECT n.node_id, n.filepath, n.node_type
|
|
42
|
+
FROM nodes n
|
|
43
|
+
LEFT JOIN edges e ON n.node_id = e.target_id
|
|
44
|
+
WHERE e.source_id IS NULL
|
|
45
|
+
AND n.node_type != 'entry_point';
|
|
46
|
+
"""
|
|
47
|
+
cursor.execute(query)
|
|
48
|
+
rows = cursor.fetchall()
|
|
49
|
+
|
|
50
|
+
return [OrphanedNode(node_id=r[0], filepath=r[1], node_type=r[2]) for r in rows]
|
|
51
|
+
|
|
52
|
+
def _build_intent_payload(self, nodes: List[OrphanedNode]) -> Dict[str, Any]:
|
|
53
|
+
"""Constructs the intent JSON payload based on NodeOS schema."""
|
|
54
|
+
targets = [
|
|
55
|
+
{
|
|
56
|
+
"id": node.node_id,
|
|
57
|
+
"file_path": node.filepath,
|
|
58
|
+
"action": "delete"
|
|
59
|
+
}
|
|
60
|
+
for node in nodes
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
"intent": "eradicate_dead_code",
|
|
65
|
+
"status": "Pending",
|
|
66
|
+
"targets": targets,
|
|
67
|
+
"context": "Node has 0 incoming edges in agy_nodeos.db",
|
|
68
|
+
"timestamp": time.time()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def generate_deletion_intents(self) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Identifies orphaned nodes and dispatches a deletion intent to workflow.json.
|
|
74
|
+
"""
|
|
75
|
+
orphans = self.find_orphaned_nodes()
|
|
76
|
+
if not orphans:
|
|
77
|
+
print("[DeadCodeEngine] No orphaned nodes detected. System clean.")
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
print(f"[DeadCodeEngine] Discovered {len(orphans)} orphaned nodes. Generating intent...")
|
|
81
|
+
intent_payload = self._build_intent_payload(orphans)
|
|
82
|
+
|
|
83
|
+
# Write intent to workflow.json to trigger swarm intelligence
|
|
84
|
+
with open(self.workflow_path, "w") as f:
|
|
85
|
+
json.dump(intent_payload, f, indent=2)
|
|
86
|
+
|
|
87
|
+
print(f"[DeadCodeEngine] Intent dispatched to {self.workflow_path}")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def main():
|
|
91
|
+
# Resolve paths relative to this script location
|
|
92
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
93
|
+
workspace_root = os.path.abspath(os.path.join(script_dir, ".."))
|
|
94
|
+
db_path = os.path.join(workspace_root, "agy_nodeos.db")
|
|
95
|
+
|
|
96
|
+
engine = SemanticDeadCodeEngine(db_path=db_path, workspace_root=workspace_root)
|
|
97
|
+
try:
|
|
98
|
+
engine.generate_deletion_intents()
|
|
99
|
+
except Exception as e:
|
|
100
|
+
print(f"[DeadCodeEngine] Error: {e}")
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
main()
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import argparse
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
class DominoEngine:
|
|
8
|
+
"""
|
|
9
|
+
Advanced architecture module for executing the Domino Refactor blueprint.
|
|
10
|
+
Manages querying the AST call graph and dispatching parallel refactor swarms.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, db_path: str = "agy_nodeos.db", workflow_path: str = "workflow.json"):
|
|
14
|
+
self.db_path = db_path
|
|
15
|
+
self.workflow_path = workflow_path
|
|
16
|
+
|
|
17
|
+
def find_callers(self, target_hash: str) -> List[str]:
|
|
18
|
+
"""
|
|
19
|
+
Finds all files calling a specific hash by querying the AST edges.
|
|
20
|
+
Uses INNER JOIN logic to resolve source and target node filepaths.
|
|
21
|
+
"""
|
|
22
|
+
if not os.path.exists(self.db_path):
|
|
23
|
+
raise FileNotFoundError(f"Database not found at path: {self.db_path}")
|
|
24
|
+
|
|
25
|
+
query = """
|
|
26
|
+
SELECT DISTINCT caller.filepath
|
|
27
|
+
FROM edges e
|
|
28
|
+
JOIN nodes target ON e.target_id = target.node_id
|
|
29
|
+
JOIN nodes caller ON e.source_id = caller.node_id
|
|
30
|
+
WHERE target.hash = ? AND e.relation_type = 'CALL';
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
35
|
+
cursor = conn.cursor()
|
|
36
|
+
cursor.execute(query, (target_hash,))
|
|
37
|
+
rows = cursor.fetchall()
|
|
38
|
+
return [row[0] for row in rows]
|
|
39
|
+
except sqlite3.Error as e:
|
|
40
|
+
print(f"Database error during find_callers: {e}")
|
|
41
|
+
return []
|
|
42
|
+
|
|
43
|
+
def generate_intent(self, target_hash: str, callers: List[str]) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Generates a multi-agent parallel intent payload and writes it to workflow.json.
|
|
46
|
+
"""
|
|
47
|
+
if not callers:
|
|
48
|
+
print(f"No callers found for hash '{target_hash}'. No workflow generated.")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
workflow = {
|
|
52
|
+
"status": "pending",
|
|
53
|
+
"agents": []
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for caller_file in callers:
|
|
57
|
+
task = {
|
|
58
|
+
"task": "refactor_domino",
|
|
59
|
+
"target_file": caller_file,
|
|
60
|
+
"instruction": f"Refactor function calls invoking hash {target_hash} to align with the newly updated signature."
|
|
61
|
+
}
|
|
62
|
+
workflow["agents"].append(task)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
with open(self.workflow_path, 'w', encoding='utf-8') as f:
|
|
66
|
+
json.dump(workflow, f, indent=2)
|
|
67
|
+
print(f"Successfully dispatched refactor swarm intent for {len(callers)} target files to {self.workflow_path}.")
|
|
68
|
+
except IOError as e:
|
|
69
|
+
print(f"IOError writing to {self.workflow_path}: {e}")
|
|
70
|
+
|
|
71
|
+
def run_refactor(self, target_hash: str) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Executes the end-to-end domino refactor chain for a specific hash.
|
|
74
|
+
"""
|
|
75
|
+
print(f"Starting Domino Refactor for hash: {target_hash}")
|
|
76
|
+
callers = self.find_callers(target_hash)
|
|
77
|
+
self.generate_intent(target_hash, callers)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def main():
|
|
81
|
+
parser = argparse.ArgumentParser(description="Domino Refactor Engine")
|
|
82
|
+
parser.add_argument("hash", help="The specific function hash to refactor")
|
|
83
|
+
parser.add_argument("--db", default="agy_nodeos.db", help="Path to agy_nodeos.db SQLite database")
|
|
84
|
+
parser.add_argument("--workflow", default="workflow.json", help="Path to output workflow.json payload")
|
|
85
|
+
|
|
86
|
+
args = parser.parse_args()
|
|
87
|
+
|
|
88
|
+
engine = DominoEngine(db_path=args.db, workflow_path=args.workflow)
|
|
89
|
+
engine.run_refactor(args.hash)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if __name__ == "__main__":
|
|
93
|
+
main()
|