diff --git a/scripts/sync_agents_across_companies.py b/scripts/sync_agents_across_companies.py index fc70971..b9697ad 100644 --- a/scripts/sync_agents_across_companies.py +++ b/scripts/sync_agents_across_companies.py @@ -259,6 +259,14 @@ async def apply_diff(mirror_id: str, agent_name: str, diff: dict) -> list[str]: if "runtime_config" in diff: patch_body["runtimeConfig"] = diff["runtime_config"]["to"] + # Stamp claude_md_mtime + last_synced into metadata + mtime = diff.get("_claude_md_mtime") + if mtime: + current_meta = dict(patch_body.get("metadata") or {}) + current_meta["claude_md_mtime"] = mtime + current_meta["claude_md_last_synced"] = datetime.now(timezone.utc).isoformat() + patch_body["metadata"] = current_meta + if patch_body: status, data = await call_patch(mirror_id, patch_body) if status >= 400: @@ -278,12 +286,65 @@ async def apply_diff(mirror_id: str, agent_name: str, diff: dict) -> list[str]: return errors +def get_claude_md_mtime(adapter_config: dict) -> str | None: + """Return Unix mtime of the agent's instructionsFilePath, or None if file missing.""" + path = adapter_config.get("instructionsFilePath", "") + if not path or not os.path.exists(path): + return None + return str(int(os.path.getmtime(path))) + + +async def check_instructions(agents: list[dict]) -> bool: + """Print a report of all agents' instruction files. Returns True if all OK.""" + from datetime import datetime + + all_ok = True + print(f"\n{'Agent':<30} {'File':<55} {'Status':<12} {'Size':>7} {'Modified'}") + print("-" * 115) + + for agent in agents: + adapter_cfg = agent.get("adapter_config") or {} + if isinstance(adapter_cfg, str): + adapter_cfg = json.loads(adapter_cfg) + + file_path = adapter_cfg.get("instructionsFilePath", "") + name = (agent.get("name") or agent.get("id") or "?")[:29] + + if not file_path: + print(f"{name:<30} {'(none)':<55} {'⚠ NOT SET':<12}") + continue + + if not os.path.exists(file_path): + print(f"{name:<30} {file_path[-54:]:<55} {'❌ MISSING':<12}") + all_ok = False + continue + + stat = os.stat(file_path) + size_kb = stat.st_size // 1024 + mtime = datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M") + + # Check for drift vs DB metadata + metadata = agent.get("metadata") or {} + if isinstance(metadata, str): + metadata = json.loads(metadata) + db_mtime = metadata.get("claude_md_mtime", "") + actual_mtime = str(int(stat.st_mtime)) + drift = " ⚠ DRIFT" if db_mtime and db_mtime != actual_mtime else "" + + print(f"{name:<30} {file_path[-54:]:<55} {'✅ OK':<12} {size_kb:>5}KB {mtime}{drift}") + + print() + return all_ok + + async def main() -> None: p = argparse.ArgumentParser() g = p.add_mutually_exclusive_group(required=True) g.add_argument("--verify", action="store_true", help="Show current drift, no changes") g.add_argument("--dry-run", action="store_true", help="Show what would change") g.add_argument("--apply", action="store_true", help="Backup + apply changes") + g.add_argument("--check-instructions", action="store_true", + help="Scan all agents' instructionsFilePath and report missing/outdated files") p.add_argument("--only", help="Sync only the named agent (e.g., 'עוזר משפטי')") args = p.parse_args() @@ -295,6 +356,11 @@ async def main() -> None: finally: await conn.close() + if args.check_instructions: + all_agents = master_agents + mirror_agents + all_ok = await check_instructions(all_agents) + sys.exit(0 if all_ok else 1) + mirror_by_name = {a["name"]: a for a in mirror_agents} print(f"\n=== Master (CMP, 1xxx): {len(master_agents)} agents ===") @@ -332,6 +398,14 @@ async def main() -> None: return # APPLY + # Pre-flight: abort if any master agent is missing its instructions file + print("🔍 Pre-flight: checking instruction files...") + all_ok = await check_instructions(master_agents) + if not all_ok: + print("❌ Abort: one or more instruction files are missing. Fix before --apply.") + sys.exit(1) + print("✅ Pre-flight passed.\n") + print(f"\n=== Backup ===") backup_path = backup_agents_table() print(f" ✓ {backup_path}") @@ -340,6 +414,11 @@ async def main() -> None: all_errors: list[str] = [] for master, mirror, diff in plan: print(f"\n → {master['name']} ({mirror['id']})") + # Inject mtime into diff so apply_diff can stamp metadata + master_ac = master.get("adapter_config") or {} + mtime = get_claude_md_mtime(master_ac) + if mtime: + diff["_claude_md_mtime"] = mtime errors = await apply_diff(mirror["id"], master["name"], diff) if errors: for e in errors: