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.
File without changes
@@ -0,0 +1,301 @@
1
+ #!/usr/bin/env python3
2
+ import asyncio
3
+ import os
4
+ import sys
5
+
6
+ # We are now a fully packaged module. No sys.path hacking required.
7
+
8
+ import subprocess
9
+ import threading
10
+ import json
11
+ from multiprocessing.connection import Listener
12
+
13
+ from polymath_nodeos.graph_manager import AGYGraphManager
14
+ from polymath_nodeos.jage_engine import JageASTEngine
15
+ from polymath_nodeos.nodes_engine import NativeNodesEngine
16
+
17
+ class AGYRawWatchdog:
18
+ """
19
+ ZERO-DEPENDENCY RAW FILE OBSERVER
20
+ Eliminates the need for 'pip install watchdog'. Uses lightweight asyncio polling.
21
+ """
22
+ def __init__(self, directory, callback, interval=1.0):
23
+ self.directory = directory
24
+ self.callback = callback
25
+ self.interval = interval
26
+ self.state = {}
27
+ self.running = False
28
+ self._initialize_state()
29
+
30
+ def _should_ignore(self, path):
31
+ # Ignore internal OS state folders and dependencies to prevent recursive loops and bloat
32
+ ignores = ['.jsagent', '.agents', '__pycache__', '.git', 'node_modules', 'build', 'dist', '.venv', 'venv', '.build_cache', 'site-packages', 'env']
33
+ return any(ign in path for ign in ignores)
34
+
35
+ def _initialize_state(self):
36
+ for root, _, files in os.walk(self.directory):
37
+ if self._should_ignore(root):
38
+ continue
39
+ for file in files:
40
+ if file.endswith(('.py', '.json')):
41
+ path = os.path.join(root, file)
42
+ self.state[path] = os.stat(path).st_mtime
43
+
44
+ async def start(self):
45
+ self.running = True
46
+ print(f"[Raw Watchdog] Natively polling {self.directory} every {self.interval}s...")
47
+ while self.running:
48
+ await asyncio.sleep(self.interval)
49
+ current_files = set()
50
+ for root, _, files in os.walk(self.directory):
51
+ if self._should_ignore(root):
52
+ continue
53
+ for file in files:
54
+ if file.endswith(('.py', '.json')):
55
+ path = os.path.join(root, file)
56
+ current_files.add(path)
57
+ try:
58
+ mtime = os.stat(path).st_mtime
59
+ if path not in self.state:
60
+ self.state[path] = mtime
61
+ self.callback(path, "created")
62
+ elif mtime > self.state[path]:
63
+ self.state[path] = mtime
64
+ self.callback(path, "modified")
65
+ except FileNotFoundError:
66
+ pass
67
+
68
+ deleted_files = set(self.state.keys()) - current_files
69
+ for path in deleted_files:
70
+ self.callback(path, "deleted")
71
+ del self.state[path]
72
+
73
+ class AGYNodeOSEventHandler:
74
+ def __init__(self, jage, spatial, graph, loop):
75
+ self.jage = jage
76
+ self.spatial = spatial
77
+ self.graph = graph
78
+ self.loop = loop
79
+
80
+ def on_event(self, filepath, event_type):
81
+ if event_type in ("created", "modified"):
82
+ print(f"\n[Daemon] Detected {event_type} in: {filepath}")
83
+
84
+ if filepath.endswith('.json'):
85
+ print(f"[Event Loop] Picked up agent JSON workflow from {filepath}!")
86
+ asyncio.run_coroutine_threadsafe(
87
+ self.dispatch_workflow([{'hash': 'workflow', 'type': 'JSON_Task', 'file': filepath}]), self.loop
88
+ )
89
+ return
90
+
91
+ ast_nodes = self.jage.parse_file(filepath)
92
+ if ast_nodes:
93
+ for node in ast_nodes:
94
+ self.spatial.add_node(node['hash'], node['type'], node.get('name', 'unknown'), node.get('calls', []), filepath=filepath)
95
+ self.spatial.resolve_edges()
96
+
97
+ # -- BEGIN NEW SWARM OS FEATURES --
98
+ if event_type == "modified":
99
+ try:
100
+ from polymath_nodeos.scripts.blast_radius_engine import BlastRadiusEngine
101
+ br_engine = BlastRadiusEngine(db_path=self.graph.db_path, workspace=self.graph.workspace, threshold=10)
102
+ br_engine.enforce_threshold(filepath)
103
+ except Exception as e:
104
+ print(f"[Swarm OS] Blast Radius Engine failed: {e}")
105
+
106
+ elif event_type == "created":
107
+ try:
108
+ from polymath_nodeos.scripts.ghost_writer_engine import GhostWriterEngine
109
+ gw_engine = GhostWriterEngine(workspace_root=self.graph.workspace, db_name=self.graph.db_path)
110
+ gw_engine.execute_pipeline(filepath)
111
+ except Exception as e:
112
+ print(f"[Swarm OS] Ghost Writer Engine failed: {e}")
113
+ # -- END NEW SWARM OS FEATURES --
114
+
115
+ elif event_type == "deleted":
116
+ print(f"\n[Daemon] Detected deletion of: {filepath}")
117
+ if not filepath.endswith('.json'):
118
+ self.graph.purge_file_nodes(filepath)
119
+ self.spatial.remove_nodes_by_file(filepath)
120
+ self.jage.purge_file_cache(filepath)
121
+
122
+ async def dispatch_workflow(self, nodes):
123
+ """True Swarm Dispatcher: Parses JSON and emits intents for AGY Agents to execute, acting as the state manager."""
124
+ for node in nodes:
125
+ if node['type'] == 'JSON_Task':
126
+ workflow_file = node['file']
127
+ try:
128
+ workflow = None
129
+ for attempt in range(5):
130
+ try:
131
+ with open(workflow_file, 'r') as f:
132
+ workflow = json.load(f)
133
+ break
134
+ except json.JSONDecodeError:
135
+ if attempt < 4:
136
+ await asyncio.sleep(0.1)
137
+ else:
138
+ raise
139
+
140
+ if not workflow:
141
+ continue
142
+
143
+ status = workflow.get('status')
144
+
145
+ if status == 'pending':
146
+ # Handle native Re-Stitch action requested by an agent
147
+ if workflow.get('action') == 'stitch':
148
+ print(f"[Swarm Dispatcher] Processing Re-Stitch request for hash {workflow.get('node_hash')[:8]}...")
149
+ workflow['status'] = 'running'
150
+ with open(workflow_file, 'w') as f: json.dump(workflow, f, indent=4)
151
+
152
+ result = self.jage.re_stitch(workflow.get('node_hash'), workflow.get('new_source'))
153
+
154
+ if result is True:
155
+ workflow['status'] = 'completed'
156
+ else:
157
+ workflow['status'] = 'failed_qa'
158
+ workflow['error_log'] = result
159
+ print(f"[Swarm Feedback Loop] Sent QA failure back to agent: {result}")
160
+
161
+ with open(workflow_file, 'w') as f: json.dump(workflow, f, indent=4)
162
+ print(f"[Event Loop] Jage Re-Stitch workflow completed.")
163
+ continue
164
+
165
+ # Handle Swarm Agent execution
166
+ agent_list = workflow.get('agents', [])
167
+ print(f"[Swarm Dispatcher] Emitting Swarm intent for AGY external swarm: {agent_list}")
168
+ print(f"[Swarm Dispatcher] Intent stored. Awaiting Antigravity Hook lifecycle to pick it up.")
169
+
170
+ elif status == 'running':
171
+ print(f"[Swarm Dispatcher] AGY Swarm has picked up the task. Monitoring execution...")
172
+
173
+ elif status == 'completed':
174
+ print(f"[Event Loop] Swarm workflow fully executed by AGY agents.")
175
+
176
+ elif status == 'failed_execution' or status == 'failed_qa':
177
+ print(f"[Swarm Error] AGY Workflow failed. Awaiting human or agent intervention.")
178
+
179
+ except Exception as e:
180
+ print(f"[Swarm Error] Failed to process workflow state: {e}")
181
+
182
+ class AGYDaemon:
183
+ def _ensure_native_rules(self):
184
+ rules_dir = os.path.join(self.workspace, ".agents", "rules")
185
+ os.makedirs(rules_dir, exist_ok=True)
186
+
187
+ rule_path = os.path.join(rules_dir, "nodeos_native_interaction.md")
188
+ rule_content = """# NodeOS Native Interaction Paradigm
189
+
190
+ Whenever operating inside this NodeOS-managed workspace, you MUST follow these constraints:
191
+ 1. You MUST use built-in system tools (view_file, list_dir, replace_file_content) to interact directly with the file system.
192
+ 2. For spatial architectural insight and dependency resolution, query the SQLite database natively (e.g., `sqlite3 agy_nodeos.db "SELECT * FROM nodes;"`).
193
+ 3. For task dispatching and swarm intent, directly modify `workflow.json` at the root of the workspace.
194
+ """
195
+ if not os.path.exists(rule_path):
196
+ with open(rule_path, "w") as f:
197
+ f.write(rule_content)
198
+ print(f"[NodeOS] Injected local workspace rules -> {rule_path}")
199
+
200
+ def __init__(self, workspace_dir):
201
+ self.workspace = workspace_dir
202
+ self._ensure_native_rules()
203
+ self.graph = AGYGraphManager()
204
+ self.jage = JageASTEngine()
205
+ self.spatial = NativeNodesEngine()
206
+ self.loop = asyncio.new_event_loop()
207
+
208
+ self.event_handler = AGYNodeOSEventHandler(self.jage, self.spatial, self.graph, self.loop)
209
+
210
+ # Initialize our zero-dependency raw watchdog
211
+ self.raw_watchdog = AGYRawWatchdog(self.workspace, self.event_handler.on_event, interval=1.0)
212
+
213
+ self.ipc_thread = threading.Thread(target=self.start_ipc_server, daemon=True)
214
+ self.ipc_thread.start()
215
+
216
+ def start_ipc_server(self):
217
+ address = ('localhost', 6000)
218
+ try:
219
+ with Listener(address, authkey=b'agy-nodeos-secret') as listener:
220
+ while True:
221
+ with listener.accept() as conn:
222
+ msg = conn.recv()
223
+ if msg == 'ping':
224
+ conn.send('pong: OS Daemon is alive and running invisibly.')
225
+ elif msg == 'status':
226
+ conn.send(f'Active Workflows: OK | Watchdog: {self.workspace}')
227
+ else:
228
+ conn.send('unknown_syscall')
229
+ except OSError:
230
+ print("[System] IPC Address 6000 already in use. A daemon is already running! Terminating duplicate process.")
231
+ os._exit(1)
232
+
233
+ def cold_start_ingestion(self):
234
+ if len(self.spatial.all_nodes) == 0:
235
+ print("[NodeOS] Detected Uninitialized Project Workspace. Initiating Deep Ingestion...")
236
+ # 1. Deep scan the workspace
237
+ for root, _, files in os.walk(self.workspace):
238
+ if self.raw_watchdog._should_ignore(root):
239
+ continue
240
+ for file in files:
241
+ if file.endswith(('.py', '.js', '.ts')):
242
+ filepath = os.path.join(root, file)
243
+ ast_nodes = self.jage.parse_file(filepath)
244
+ if ast_nodes:
245
+ for node in ast_nodes:
246
+ # We now pass the node name and the calls it makes to the spatial matrix
247
+ self.spatial.add_node(node['hash'], node['type'], node.get('name', 'unknown'), node.get('calls', []))
248
+
249
+ # Resolve physical edges using AST calls
250
+ self.spatial.resolve_edges()
251
+
252
+ print(f"[NodeOS] Deep Scan complete. Ingested {len(self.spatial.all_nodes)} kinetic nodes.")
253
+
254
+ # 2. Check for missing Architectural Nodes
255
+ architect_exists = os.path.exists(os.path.join(self.workspace, "architect_parent_node.md"))
256
+ blueprint_exists = os.path.exists(os.path.join(self.workspace, "impl_blueprint_node.md"))
257
+
258
+ if not architect_exists or not blueprint_exists:
259
+ print("[NodeOS] Essential structural nodes missing. Dispatching Architect Designer Sub-Agent...")
260
+ workflow_file = os.path.join(self.workspace, "workflow.json")
261
+ workflow = {
262
+ "type": "JSON_Task",
263
+ "status": "pending",
264
+ "action": "execute_agents",
265
+ "agents": ["architect_designer"]
266
+ }
267
+ import json
268
+ with open(workflow_file, "w") as f:
269
+ json.dump(workflow, f, indent=4)
270
+ print("[NodeOS] Workflow payload dropped. Architect worker will bootstrap the project.")
271
+
272
+ async def _async_run(self):
273
+ # Run the deep scan ingestion immediately before starting the real-time event loop
274
+ self.cold_start_ingestion()
275
+
276
+ # The raw watchdog runs in the asyncio event loop natively
277
+ await self.raw_watchdog.start()
278
+
279
+ def run(self):
280
+ try:
281
+ self.loop.run_until_complete(self._async_run())
282
+ finally:
283
+ self.loop.close()
284
+
285
+ def daemonize_and_run():
286
+ if len(sys.argv) > 1 and sys.argv[-1] == '--run-as-daemon':
287
+ target_workspace = os.getcwd()
288
+ daemon = AGYDaemon(target_workspace)
289
+ daemon.run()
290
+ else:
291
+ print("[System] Detaching AGY-NodeOS Daemon (Zero-Dependency) to background process...")
292
+ if os.name == 'nt':
293
+ CREATE_NO_WINDOW = 0x08000000
294
+ subprocess.Popen([sys.executable, __file__, '--run-as-daemon'], creationflags=CREATE_NO_WINDOW)
295
+ else:
296
+ log_file = open(os.path.join(os.getcwd(), 'daemon.log'), 'a')
297
+ subprocess.Popen([sys.executable, '-u', __file__, '--run-as-daemon'], start_new_session=True, stdout=log_file, stderr=log_file)
298
+ sys.exit(0)
299
+
300
+ if __name__ == "__main__":
301
+ daemonize_and_run()
@@ -0,0 +1,113 @@
1
+ import sqlite3
2
+ import os
3
+ import glob
4
+ from datetime import datetime
5
+
6
+ class AGYGraphManager:
7
+ def __init__(self, db_path="agy_nodeos.db"):
8
+ self.db_path = db_path
9
+ self.skills_dir = os.path.expanduser("~/.gemini/config/skills")
10
+ self.rules_dir = os.path.expanduser("~/.gemini/config/rules")
11
+ self._init_db()
12
+
13
+ def _init_db(self):
14
+ """Initializes the native SQLite Parent Node database for AGY-NodeOS."""
15
+ conn = sqlite3.connect(self.db_path)
16
+ cursor = conn.cursor()
17
+
18
+ # Parent Node Table
19
+ cursor.execute('''
20
+ CREATE TABLE IF NOT EXISTS nodes (
21
+ node_id TEXT PRIMARY KEY,
22
+ node_type TEXT,
23
+ name TEXT,
24
+ filepath TEXT,
25
+ hash TEXT,
26
+ x_coord REAL DEFAULT 500,
27
+ y_coord REAL DEFAULT 500,
28
+ last_updated TIMESTAMP
29
+ )
30
+ ''')
31
+
32
+ # Graph Edges (Relationships)
33
+ cursor.execute('''
34
+ CREATE TABLE IF NOT EXISTS edges (
35
+ source_id TEXT,
36
+ target_id TEXT,
37
+ relation_type TEXT,
38
+ PRIMARY KEY (source_id, target_id, relation_type)
39
+ )
40
+ ''')
41
+ conn.commit()
42
+ conn.close()
43
+
44
+ def purge_file_nodes(self, filepath):
45
+ """Purges all nodes (and their edges) associated with a deleted file."""
46
+ conn = sqlite3.connect(self.db_path)
47
+ cursor = conn.cursor()
48
+
49
+ cursor.execute("DELETE FROM nodes WHERE filepath = ?", (filepath,))
50
+ cursor.execute("""
51
+ DELETE FROM edges WHERE
52
+ source_id NOT IN (SELECT node_id FROM nodes) OR
53
+ target_id NOT IN (SELECT node_id FROM nodes)
54
+ """)
55
+
56
+ conn.commit()
57
+ conn.close()
58
+ print(f"[AGY-NodeOS] Purged nodes for {filepath} from SQLite graph.")
59
+
60
+ def index_system_nodes(self):
61
+ """Indexes all AGY Built-in Skills and Rules natively as System Nodes."""
62
+ print("[AGY-NodeOS] Indexing System Nodes natively...")
63
+ conn = sqlite3.connect(self.db_path)
64
+ cursor = conn.cursor()
65
+
66
+ # Map Skills
67
+ skill_files = glob.glob(os.path.join(self.skills_dir, "*", "SKILL.md"))
68
+ for filepath in skill_files:
69
+ skill_name = os.path.basename(os.path.dirname(filepath))
70
+ node_id = f"skill_{skill_name}"
71
+ cursor.execute('''
72
+ INSERT OR REPLACE INTO nodes (node_id, node_type, name, filepath, last_updated)
73
+ VALUES (?, ?, ?, ?, ?)
74
+ ''', (node_id, 'SYSTEM_SKILL', skill_name, filepath, datetime.now()))
75
+
76
+ # Map Rules
77
+ rule_files = glob.glob(os.path.join(self.rules_dir, "*.md"))
78
+ for filepath in rule_files:
79
+ rule_name = os.path.basename(filepath).replace(".md", "")
80
+ node_id = f"rule_{rule_name}"
81
+ cursor.execute('''
82
+ INSERT OR REPLACE INTO nodes (node_id, node_type, name, filepath, last_updated)
83
+ VALUES (?, ?, ?, ?, ?)
84
+ ''', (node_id, 'SYSTEM_RULE', rule_name, filepath, datetime.now()))
85
+
86
+ conn.commit()
87
+ conn.close()
88
+ print("[AGY-NodeOS] Internal System Node Indexing Complete.")
89
+
90
+ class InternalSwarmDispatcher:
91
+ """
92
+ NATIVE SWARM MANAGEMENT
93
+ Replaces external third-party neural playgrounds.
94
+ Manages local sub-agents natively within AGY-NodeOS.
95
+ """
96
+ def __init__(self, graph_manager):
97
+ self.graph = graph_manager
98
+
99
+ def dispatch_internal_task(self, task_type, payload):
100
+ """
101
+ Dispatches tasks using internal threads/processes,
102
+ directly utilizing the local SQLite Graph without relying on external ports.
103
+ """
104
+ print(f"[Internal Swarm] Dispatching native {task_type} task. Fetching local graph context...")
105
+ # Logic to launch a local Python process or AGY native subagent
106
+ return {"status": "success", "context_used": "native"}
107
+
108
+ if __name__ == "__main__":
109
+ manager = AGYGraphManager()
110
+ manager.index_system_nodes()
111
+
112
+ swarm = InternalSwarmDispatcher(manager)
113
+ swarm.dispatch_internal_task("code_generation", {"target": "calculator"})
@@ -0,0 +1,178 @@
1
+ import ast
2
+ import hashlib
3
+ import json
4
+ import os
5
+ import re
6
+ import sys
7
+
8
+ class JageASTEngine:
9
+ """
10
+ POLYGLOT AST CODE MANAGER (Fallback Implementation)
11
+ Because Android Termux environments fail to link `tree-sitter` ABI C-extensions securely,
12
+ this implementation relies on Python's built-in `ast` for Python files, and uses standard regex
13
+ chunking for JS/TS as a polyglot fallback.
14
+ """
15
+ def __init__(self, db_path=".jsagent"):
16
+ self.db_path = db_path
17
+ self.schema_dir = os.path.join(self.db_path, "schema")
18
+ os.makedirs(self.schema_dir, exist_ok=True)
19
+
20
+ def hash_content(self, content):
21
+ return hashlib.sha256(content.encode('utf-8')).hexdigest()
22
+
23
+ def parse_file(self, filepath):
24
+ if not os.path.exists(filepath):
25
+ return None
26
+
27
+ if filepath.endswith('.py'):
28
+ return self._parse_python(filepath)
29
+ elif filepath.endswith(('.js', '.ts')):
30
+ return self._parse_javascript(filepath)
31
+ return []
32
+
33
+ def _parse_python(self, filepath):
34
+ with open(filepath, 'r', encoding='utf-8') as f:
35
+ source = f.read()
36
+
37
+ try:
38
+ tree = ast.parse(source)
39
+ except SyntaxError as e:
40
+ print(f"[Jage] Syntax error in {filepath}: {e}")
41
+ return None
42
+
43
+ nodes = []
44
+ for node in ast.iter_child_nodes(tree):
45
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
46
+ node_source = ast.get_source_segment(source, node)
47
+ node_hash = self.hash_content(node_source)
48
+
49
+ # AST Deep Traversal: Extract all function calls for Dependency Edges
50
+ calls = []
51
+ for sub_node in ast.walk(node):
52
+ if isinstance(sub_node, ast.Call):
53
+ if isinstance(sub_node.func, ast.Name):
54
+ calls.append(sub_node.func.id)
55
+ elif isinstance(sub_node.func, ast.Attribute):
56
+ calls.append(sub_node.func.attr)
57
+
58
+ node_data = {
59
+ "type": type(node).__name__,
60
+ "name": node.name,
61
+ "hash": node_hash,
62
+ "file": filepath,
63
+ "line_number": node.lineno,
64
+ "calls": list(set(calls))
65
+ }
66
+ nodes.append(node_data)
67
+ self._store_schema(node_hash, node_data, node_source)
68
+ return nodes
69
+
70
+ def _parse_javascript(self, filepath):
71
+ """A simple Regex fallback for Polyglot JS/TS parsing since tree-sitter C-linkage fails on Termux."""
72
+ with open(filepath, 'r', encoding='utf-8') as f:
73
+ lines = f.readlines()
74
+
75
+ nodes = []
76
+ pattern = re.compile(r'^(?:export\s+)?(?:async\s+)?(?:function|class)\s+([a-zA-Z0-9_]+)')
77
+
78
+ for i, line in enumerate(lines):
79
+ match = pattern.search(line)
80
+ if match:
81
+ name = match.group(1)
82
+ node_source = line.strip() # Simplified extraction
83
+ node_hash = self.hash_content(node_source)
84
+ node_data = {
85
+ "type": "JS_Node",
86
+ "name": name,
87
+ "hash": node_hash,
88
+ "file": filepath,
89
+ "line_number": i + 1
90
+ }
91
+ nodes.append(node_data)
92
+ self._store_schema(node_hash, node_data, node_source)
93
+ return nodes
94
+
95
+ def _store_schema(self, node_hash, metadata, source):
96
+ schema_path = os.path.join(self.schema_dir, f"{node_hash}.json")
97
+ data = {
98
+ "metadata": metadata,
99
+ "source": source
100
+ }
101
+ with open(schema_path, 'w', encoding='utf-8') as f:
102
+ json.dump(data, f, indent=4)
103
+ print(f"[Jage Polyglot] Mapped {metadata['type']} '{metadata['name']}' -> Hash: {node_hash[:8]}...")
104
+
105
+ def purge_file_cache(self, filepath):
106
+ """Purges schema JSONs associated with a deleted file."""
107
+ if not os.path.exists(self.schema_dir):
108
+ return
109
+ print(f"[Jage] Purging schema JSONs for deleted file: {filepath}")
110
+ for filename in os.listdir(self.schema_dir):
111
+ if not filename.endswith('.json'):
112
+ continue
113
+ schema_path = os.path.join(self.schema_dir, filename)
114
+ try:
115
+ with open(schema_path, 'r', encoding='utf-8') as f:
116
+ data = json.load(f)
117
+ if data.get("metadata", {}).get("file") == filepath:
118
+ os.remove(schema_path)
119
+ except Exception as e:
120
+ print(f"[Jage] Failed to purge {filename}: {e}")
121
+
122
+ def re_stitch(self, node_hash, new_source):
123
+ """
124
+ JAGE RE-STITCHER w/ NATIVE VERIFICATION KERNEL
125
+ Injects a modified JSON child node's source code back into the original parent source file,
126
+ but only after applying shadow-patching and QA validation to prevent corruption.
127
+ """
128
+ schema_path = os.path.join(self.schema_dir, f"{node_hash}.json")
129
+ if not os.path.exists(schema_path):
130
+ return "Error: Hash not found in local DB."
131
+
132
+ with open(schema_path, 'r', encoding='utf-8') as f:
133
+ data = json.load(f)
134
+
135
+ original_source = data.get("source", "")
136
+ filepath = data["metadata"]["file"]
137
+
138
+ if not os.path.exists(filepath):
139
+ return "Error: Target file not found."
140
+
141
+ with open(filepath, 'r', encoding='utf-8') as f:
142
+ file_content = f.read()
143
+
144
+ if original_source not in file_content:
145
+ return "Error: Original source block not found. Code drifted."
146
+
147
+ # 1. Shadow Patching
148
+ updated_content = file_content.replace(original_source, new_source)
149
+
150
+ # 2. Native QA Verification (Syntax Check)
151
+ if filepath.endswith('.py'):
152
+ try:
153
+ ast.parse(updated_content)
154
+ except SyntaxError as e:
155
+ print(f"[QA Verifier] Rejected corrupted payload for {filepath}: {e}")
156
+ return f"SyntaxError at line {e.lineno}, offset {e.offset}: {e.msg}\nCode rejected by NodeOS Firewall."
157
+
158
+ # 3. Universal QA Analyzer Integration (Portable)
159
+ # Use a dynamic relative path to ensure cross-platform NodeOS portability
160
+ qa_script = os.path.join(os.getcwd(), '.agents', 'skills', 'qa-analyzer', 'scripts', 'analyzer.py')
161
+ if os.path.exists(qa_script):
162
+ import subprocess
163
+ shadow_path = filepath + ".shadow"
164
+ with open(shadow_path, 'w', encoding='utf-8') as f: f.write(updated_content)
165
+
166
+ result = subprocess.run([sys.executable, qa_script, shadow_path], capture_output=True, text=True)
167
+ os.remove(shadow_path)
168
+
169
+ if result.returncode != 0:
170
+ print(f"[QA Verifier] Structural analysis failed for {filepath}.")
171
+ return f"Structural/QA Error:\n{result.stdout}\n{result.stderr}\nCode rejected by NodeOS Firewall."
172
+
173
+ # 4. Atomic Commit
174
+ with open(filepath, 'w', encoding='utf-8') as f:
175
+ f.write(updated_content)
176
+
177
+ print(f"[Jage Re-Stitcher] Successfully re-stitched and verified {node_hash[:8]} into {filepath}.")
178
+ return True