feat(hygiene): anti-redundancy enforcement — spec rules + weekly audit

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
This commit is contained in:
hmo
2026-07-20 19:04:05 +08:00
parent d5b8bec897
commit 4f83ee8a01
21 changed files with 1286 additions and 3 deletions
+52
View File
@@ -0,0 +1,52 @@
import json, shutil, os
from datetime import datetime
# 1. 删小果相关 cron jobquick-scan / tunnel-watchdog / 情感分析 / 独立扫描)
REMOVE_JOBS = {'xiaoguo-quick-scan', 'xiaoguo-tunnel-watchdog', '小果情感分析', '小果独立扫描'}
for jf in ['/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json',
'/home/hmo/.hermes/cron/jobs.json']:
shutil.copy(jf, jf + '.bak-20260720-xiaoguo')
d = json.load(open(jf))
is_list = isinstance(d, list)
jobs = d if is_list else d.get('jobs', [])
kept = [j for j in jobs if j.get('name') not in REMOVE_JOBS]
removed = [j.get('name') for j in jobs if j.get('name') in REMOVE_JOBS]
if removed:
print(f'{jf}: removed {removed}')
if is_list:
json.dump(kept, open(jf, 'w'), ensure_ascii=False, indent=2)
else:
d['jobs'] = kept
json.dump(d, open(jf, 'w'), ensure_ascii=False, indent=2)
else:
print(f'{jf}: nothing to remove')
# 2. 归档小果脚本和数据文件
ARCHIVE = '/home/hmo/MoFin/archive/xiaoguo-retired-20260720'
os.makedirs(ARCHIVE, exist_ok=True)
candidates = [
'/home/hmo/MoFin/deploy/profile-scripts/xiaoguo_scanner.py',
'/home/hmo/MoFin/deploy/profile-scripts/xiaoguo_news_processor.py',
'/home/hmo/MoFin/deploy/profile-scripts/xiaoguo_sentiment_bridge.py',
'/home/hmo/MoFin/deploy/profile-scripts/xiaoguo_signal_consumer.py',
'/home/hmo/MoFin/scripts/inject_xiaoguo_insight.py',
'/home/hmo/web-dashboard/data/xiaoguo_insights.json',
'/home/hmo/web-dashboard/data/xiaoguo_sentiment.json',
'/home/hmo/MoFin/data/xiaoguo_insights.json',
'/home/hmo/MoFin/data/xiaoguo_sentiment.json',
]
for f in candidates:
if os.path.exists(f):
shutil.move(f, os.path.join(ARCHIVE, os.path.basename(f)))
print('archived:', f)
# 3. 处理 profile scripts 里残留的 xiaoguo 硬链接
import glob
for f in glob.glob('/home/hmo/.hermes/profiles/position-analyst/scripts/xiaoguo_*.py'):
os.unlink(f)
print('unlinked:', f)
# 4. 检查 default profile scripts 的 xiaoguo 文件
for f in glob.glob('/home/hmo/.hermes/scripts/xiaoguo_*.py') + glob.glob('/home/hmo/.hermes/scripts/xiaoguo_*.sh'):
print('default profile xiaoguo file:', f)
print('DONE')