Root cause analysis of the 2026-07-20 redundancy incident: 1. No single-source-of-truth rule -> same file legitimately lived in 4+ locations, diverging silently 2. Relative path resolution (Path(__file__).parent/'data') -> each hardlinked copy of mofin_db.py pointed to a DIFFERENT database 3. 'Backup habit' left .bak/legacy files in production dirs, which monitoring then scanned and reported as false alarms 4. Half-done migrations: DB tables created but old JSON writers/readers stayed (price_events), old files stayed 5. Dead modules never got buried: xiaoguo 'dead' but bot ran 8 days as root eating 2.5GB 6. Monitoring checked 'does it exist' not 'is it alive' -> stale file mtime reported as 'pipeline stalled 14 days' (false alarm) 7. No 'system hygiene' as a check category at all Prevention implemented: - dev-spec.md v2.0: 五条红线 -> 十条红线 #6 single source of truth (hardlink only, no independent copies) #7 absolute data paths only (no __file__-relative data resolution) #8 no backups/legacy in production data dirs (archive immediately) #9 dead module burial checklist (6 mandatory steps) #10 monitor liveness (DB table freshness) not existence - File Location Constitution: canonical location per content type - NEW system_hygiene_audit.py: weekly Monday 07:30 cron checking diverged copies / broken hardlinks / zombie processes / orphan data files / dead cron scripts / DB freshness -> hygiene_report.json + XMPP - specs/hygiene.json: module spec per red line #1 - Verified: audit found 5 real issues on first run, all fixed, re-run clean
47 lines
2.4 KiB
Python
47 lines
2.4 KiB
Python
import subprocess, os, re
|
|
from datetime import datetime
|
|
|
|
DIVERGED = """advice_reconciliation.py branch_scanner.py bulk_strategy_regenerate.py
|
|
collect_evaluation_data.py cron_to_xmpp.py market_insight.py market_screener.py market_watch.py
|
|
memory_guardian.py mo_provider.py mofin_collect.py mofin_health.py mofin_news.py multi_timeframe.py
|
|
premarket_full_review.py promote_candidates.py prune_branches.py server.py stock_profile.py
|
|
stock_sector_enrich.py strategy_evaluator.py strategy_feedback.py strategy_lifecycle.py strategy_tree.py
|
|
system_audit.py system_health_check.py technical_analysis.py trend_detector.py
|
|
xiaoguo_news_processor.py xiaoguo_scanner.py xmpp_agent_core.py""".split()
|
|
|
|
SEARCH_DIRS = ['/home/hmo/MoFin/deploy/profile-scripts', '/home/hmo/.hermes/profiles/position-analyst/scripts', '/home/hmo/.hermes/scripts']
|
|
|
|
now = datetime.now()
|
|
|
|
print(f"{'file':35s} {'imported_by':40s} {'cron?':5s} {'root_mtime':12s} {'deploy_mtime':12s} verdict")
|
|
print('-' * 130)
|
|
|
|
for f in DIVERGED:
|
|
stem = f[:-3]
|
|
# 谁 import 它
|
|
r = subprocess.run(
|
|
f"grep -rln -E '(^|\\s)(from|import)\\s+{stem}(\\s|$|\\.)' /home/hmo/MoFin/deploy/profile-scripts /home/hmo/.hermes/profiles/position-analyst/scripts /home/hmo/MoFin --include='*.py' 2>/dev/null | grep -v venv | grep -v '{f}' | head -3",
|
|
shell=True, capture_output=True, text=True, timeout=15)
|
|
importers = [os.path.basename(x) for x in r.stdout.splitlines() if x.strip() and f not in x]
|
|
imported = ','.join(importers[:3]) if importers else '-'
|
|
|
|
# 是否在 cron 里被直接执行
|
|
in_cron = subprocess.run(
|
|
f"grep -l '\"script\": \"{f}\"' /home/hmo/.hermes/profiles/position-analyst/cron/jobs.json /home/hmo/.hermes/cron/jobs.json 2>/dev/null",
|
|
shell=True, capture_output=True, text=True, timeout=5)
|
|
is_cron = 'Y' if in_cron.stdout.strip() else '-'
|
|
|
|
# mtimes
|
|
root_p = f'/home/hmo/MoFin/{f}'
|
|
deploy_p = f'/home/hmo/MoFin/deploy/profile-scripts/{f}'
|
|
rm = datetime.fromtimestamp(os.path.getmtime(root_p)).strftime('%m-%d') if os.path.exists(root_p) else '-'
|
|
dm = datetime.fromtimestamp(os.path.getmtime(deploy_p)).strftime('%m-%d') if os.path.exists(deploy_p) else '-'
|
|
|
|
# verdict
|
|
if importers:
|
|
verdict = 'LIBRARY->root为准'
|
|
elif is_cron == 'Y':
|
|
verdict = 'CRON->deploy为准'
|
|
else:
|
|
verdict = '待查'
|
|
print(f"{f:35s} {imported:40s} {is_cron:5s} {rm:12s} {dm:12s} {verdict}") |