146 lines
4.9 KiB
Python
146 lines
4.9 KiB
Python
"""Shared computation for the Sol-lane profile-state triage.
|
|
|
|
Defines ONE classifier and ONE walk over /root/.hermes/profiles/sol.
|
|
Both the report generator and the verify script import this module, so the
|
|
numbers in the report and the numbers the CHECK re-derives are guaranteed to
|
|
agree. Classification is by entry NAME; the walk is os.walk (no symlink
|
|
following), so "files" counts every name os.walk reports as a file,
|
|
including broken symlinks.
|
|
"""
|
|
|
|
import os
|
|
import datetime
|
|
from collections import Counter
|
|
|
|
ROOT = '/root/.hermes/profiles/sol'
|
|
|
|
# Name -> category. Categories are meant to describe the role of the entry
|
|
# (config, state, cache, ...), not to be exhaustive.
|
|
_CONFIG_NAMES = {
|
|
'config.yaml', '.env', '.env.bak-20261014T165715Z', 'context_length_cache.yaml',
|
|
'auth.json', 'SOUL.md', '_check_repo_json.py', '_gen_daily_ledger_json.py',
|
|
'_gen_daily_ledger_report.py', '_gen_daily_triage.py', '_gen_daily_triage_fresh.py',
|
|
'_verify_daily_triage.py',
|
|
}
|
|
_STATE_NAMES = {'state.db', 'state.db-shm', 'state.db-wal', 'state.db.auto-maintenance.lock', 'state.db.quarantine.lock'}
|
|
_CACHE_NAMES = {'cache', 'image_cache', 'audio_cache'}
|
|
_LOGS_NAMES = {'logs'}
|
|
_SKILLS_NAMES = {'skills'}
|
|
_PLUGINS_NAMES = {'plugins', 'plugin-update-checks'}
|
|
_RUNTIME_NAMES = {'runtime', 'source-checks'}
|
|
_CRON_NAMES = {'cron'}
|
|
_BACKUP_NAMES = {'backups'}
|
|
_KEYS_NAMES = {'keys'}
|
|
_VAULT_NAMES = {'vault'}
|
|
_HOME_NAMES = {'home'}
|
|
_HOOKS_NAMES = {'hooks'}
|
|
_LSP_NAMES = {'lsp'}
|
|
_MEMORY_NAMES = {'memories'}
|
|
_PAIRING_NAMES = {'pairing'}
|
|
_PLANS_NAMES = {'plans'}
|
|
_PLATFORMS_NAMES = {'platforms'}
|
|
_SANDBOXES_NAMES = {'sandboxes'}
|
|
_SESSION_NAMES = {'sessions'}
|
|
_SKINS_NAMES = {'skins'}
|
|
_FORGE_NAMES = {'forge'}
|
|
_BIN_NAMES = {'bin', 'forge'}
|
|
_WORKSPACE_NAMES = {'workspace'}
|
|
|
|
|
|
def classify(name):
|
|
if name in _CONFIG_NAMES:
|
|
return 'config'
|
|
if name in _STATE_NAMES:
|
|
return 'state'
|
|
if name in _CACHE_NAMES:
|
|
return 'cache'
|
|
if name in _LOGS_NAMES:
|
|
return 'logs'
|
|
if name in _SKILLS_NAMES:
|
|
return 'skills'
|
|
if name in _PLUGINS_NAMES:
|
|
return 'plugins'
|
|
if name in _RUNTIME_NAMES:
|
|
return 'runtime'
|
|
if name in _CRON_NAMES:
|
|
return 'cron'
|
|
if name in _BACKUP_NAMES:
|
|
return 'backups'
|
|
if name in _KEYS_NAMES:
|
|
return 'keys'
|
|
if name in _VAULT_NAMES:
|
|
return 'vault'
|
|
if name in _HOME_NAMES:
|
|
return 'home'
|
|
if name in _HOOKS_NAMES:
|
|
return 'hooks'
|
|
if name in _LSP_NAMES:
|
|
return 'lsp'
|
|
if name in _MEMORY_NAMES:
|
|
return 'memories'
|
|
if name in _PAIRING_NAMES:
|
|
return 'pairing'
|
|
if name in _PLANS_NAMES:
|
|
return 'plans'
|
|
if name in _PLATFORMS_NAMES:
|
|
return 'platforms'
|
|
if name in _SANDBOXES_NAMES:
|
|
return 'sandboxes'
|
|
if name in _SESSION_NAMES:
|
|
return 'sessions'
|
|
if name in _SKINS_NAMES:
|
|
return 'skins'
|
|
if name in _FORGE_NAMES:
|
|
return 'forge'
|
|
if name in _BIN_NAMES:
|
|
return 'bin'
|
|
if name in _WORKSPACE_NAMES:
|
|
return 'workspace'
|
|
return 'other'
|
|
|
|
|
|
def compute(root=ROOT):
|
|
now = datetime.datetime.now(datetime.timezone.utc)
|
|
entries = []
|
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
for d in dirnames:
|
|
full = os.path.join(dirpath, d)
|
|
st = os.lstat(full)
|
|
entries.append({
|
|
'path': os.path.relpath(full, root),
|
|
'type': 'dir',
|
|
'category': classify(d),
|
|
'mtime': datetime.datetime.fromtimestamp(st.st_mtime, datetime.timezone.utc).isoformat(),
|
|
'age_days': (now - datetime.datetime.fromtimestamp(st.st_mtime, datetime.timezone.utc)).total_seconds() / 86400.0,
|
|
})
|
|
for f in filenames:
|
|
full = os.path.join(dirpath, f)
|
|
st = os.lstat(full)
|
|
entries.append({
|
|
'path': os.path.relpath(full, root),
|
|
'type': 'file',
|
|
'category': classify(f),
|
|
'mtime': datetime.datetime.fromtimestamp(st.st_mtime, datetime.timezone.utc).isoformat(),
|
|
'age_days': (now - datetime.datetime.fromtimestamp(st.st_mtime, datetime.timezone.utc)).total_seconds() / 86400.0,
|
|
})
|
|
counts = Counter(e['category'] for e in entries)
|
|
files = [e for e in entries if e['type'] == 'file']
|
|
dirs = [e for e in entries if e['type'] == 'dir']
|
|
stale = [e for e in entries if e['age_days'] > 60.0]
|
|
return {
|
|
'now': now.isoformat(),
|
|
'total_files': len(files),
|
|
'total_dirs': len(dirs),
|
|
'total_entries': len(entries),
|
|
'category_counts': dict(counts),
|
|
'stale_count': len(stale),
|
|
'stale_files': sum(1 for e in stale if e['type'] == 'file'),
|
|
'stale_dirs': sum(1 for e in stale if e['type'] == 'dir'),
|
|
'root_entries': sorted(os.listdir(root)),
|
|
}
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import json
|
|
print(json.dumps(compute(), indent=2, default=str))
|