File size: 9,111 Bytes
b67a1e9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | #!/usr/bin/env python3
import json, re, html, textwrap
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
ROOT=Path(__file__).resolve().parents[1]
TRIAGE=ROOT/'triage'
OUT=ROOT/'visual_summary'
OUT.mkdir(parents=True,exist_ok=True)
def load(p): return json.loads(Path(p).read_text(encoding='utf-8'))
def short(path):
s=path.replace('/home/smlflg/Projekte/','').replace('/home/smlflg/','~/' )
return s if len(s)<=48 else '…'+s[-47:]
def wrap(draw, text, font, width):
words=str(text).split(); lines=[]; cur=''
for w in words:
test=(cur+' '+w).strip()
if draw.textbbox((0,0),test,font=font)[2] <= width: cur=test
else:
if cur: lines.append(cur)
cur=w
if cur: lines.append(cur)
return lines
def top_names(items,n=8): return [short(x['path']) for x in items[:n]]
def main():
pri=load(TRIAGE/'PROJECT_PRIORITIZATION.json')
summary=load(TRIAGE/'PROJECT_PRIORITIZATION_SUMMARY.json')
keep=pri['categories']['KEEP']; urgent=pri['categories']['URGENT']; merge=pri['categories']['MERGE']; pause=pri['categories']['PAUSE']; archive=pri['categories']['ARCHIVE']; token=pri['categories']['TOKEN_BURN_CANDIDATE']; human=pri['categories']['HUMAN_DECISION_REQUIRED']
bypath={r['path']:r for r in pri['projects']}
def get(path): return bypath.get(path, {'path':path,'score':0,'dirty_count':0,'categories':[]})
top10=[
get('/home/smlflg/Projekte/Sidecar-Workspace/Sidecar-V7'),
get('/home/smlflg/Projekte/Zentrale'),
get('/home/smlflg/Projekte/ACT-agentcommandotower'),
get('/home/smlflg/Projekte/AIOS-Cockpit'),
get('/home/smlflg/Projekte/AgentArena'),
get('/home/smlflg/Projekte/Jobs_Selbständigkeit_Geldverdienen/AgentTestUniSupport'),
get('/home/smlflg/Projekte/Sidecar-Workspace/Sidecar-V8'),
get('/home/smlflg/.hermes'),
get('/home/smlflg/Projekte/UEBER_AGENT'),
get('/home/smlflg/Projekte/PromptGarage'),
]
gold=[get(p) for p in ['/home/smlflg/Projekte/Zentrale','/home/smlflg/Projekte/ACT-agentcommandotower','/home/smlflg/Projekte/AIOS-Cockpit','/home/smlflg/Projekte/Sidecar-Workspace/Sidecar-V8','/home/smlflg/.hermes']]
almost=[get(p) for p in ['/home/smlflg/Projekte/Jobs_Selbständigkeit_Geldverdienen/AgentTestUniSupport','/home/smlflg/Projekte/Sidecar-Workspace/Sidecar-V7','/home/smlflg/Projekte/ClaudeCodePanel','/home/smlflg/Projekte/PromptGarage']]
needs_tests=[r for r in keep if 'python' in (r.get('tech') or '').lower()][:7]
tomorrow=[get('/home/smlflg/Projekte/Zentrale'), get('/home/smlflg/Projekte/ACT-agentcommandotower'), get('/home/smlflg/.hermes')]
garbage=archive[:8]
data={
'title':'Samuel Project Map — Gold, Müll, Morgen',
'counts':summary,
'top10':top10,'gold':gold,'garbage':garbage,'almost_done':almost,'needs_tests':needs_tests,
'scope_creep':token[:8],'archive':archive[:8],'tomorrow':tomorrow,'human':human[:8],
'rules':[
'KEEP = weiterführen, aber mit Verifier',
'MERGE = wertvoll, aber nicht separat weiterbauen',
'PAUSE = nicht löschen, aber keine Tokens mehr verbrennen',
'ARCHIVE = nur nach Human Gate; zuerst Manifest sichern',
'URGENT = dirty/risikoreich: morgen zuerst stabilisieren'
]
}
(OUT/'learning_graphic_data.json').write_text(json.dumps(data,indent=2,ensure_ascii=False),encoding='utf-8')
W,H=1800,2400
bg='#08111f'; card='#101b2d'; stroke='#26364f'; text='#e5eefc'; muted='#94a3b8'
img=Image.new('RGB',(W,H),bg); d=ImageDraw.Draw(img)
def font(size,bold=False):
p='/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf' if bold else '/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf'
return ImageFont.truetype(p,size)
f_title=font(60,True); f_h=font(36,True); f=font(25); f_s=font(20); f_tiny=font(17)
d.text((70,55),'🧭 Samuel Project Map',fill='#67e8f9',font=f_title)
d.text((72,130),'Was ist Gold, was ist Müll, was braucht Tests, was morgen zuerst?',fill=muted,font=f)
# metric pills
x=70; y=190
pills=[('KEEP',summary['KEEP'],'#22c55e'),('MERGE',summary['MERGE'],'#a78bfa'),('PAUSE',summary['PAUSE'],'#94a3b8'),('ARCHIVE',summary['ARCHIVE'],'#f97316'),('URGENT',summary['URGENT'],'#ef4444'),('TOKEN BURN',summary['TOKEN_BURN_CANDIDATE'],'#facc15')]
for label,val,col in pills:
d.rounded_rectangle((x,y,x+250,y+64),radius=18,fill='#0f172a',outline=col,width=3)
d.text((x+18,y+14),f'{label}: {val}',fill=col,font=f_s)
x+=270
def card_box(x,y,w,h,title,color,items,subtitle=None):
d.rounded_rectangle((x,y,x+w,y+h),radius=24,fill=card,outline=color,width=4)
d.text((x+28,y+22),title,fill=color,font=f_h)
cy=y+78
if subtitle:
for ln in wrap(d,subtitle,f_s,w-56)[:2]:
d.text((x+28,cy),ln,fill=muted,font=f_s); cy+=27
cy+=8
for i,it in enumerate(items):
name=short(it['path']) if isinstance(it,dict) else str(it)
score=it.get('score') if isinstance(it,dict) else None
dirty=it.get('dirty_count') if isinstance(it,dict) else None
line=f'{i+1}. {name}'
if score is not None: line+=f' s={score}'
if dirty: line+=f' dirty={dirty}'
for j,ln in enumerate(wrap(d,line,f_s,w-70)[:2]):
d.text((x+30,cy),ln,fill=text if i<5 else muted,font=f_s); cy+=25
cy+=6
if cy>y+h-30: break
card_box(70,290,800,535,'🏆 Deine 10 wichtigsten Projekte','#22d3ee',top10,'Das ist dein echtes Agentic-OS: Control Tower + Sidecar + Hermes + Eval + Uni.')
card_box(930,290,800,535,'💎 Gold','#22c55e',gold,'Nicht neu anfangen. Diese Kerne stabilisieren und verbinden.')
card_box(70,870,530,420,'🗑️ Müll / Cache','#fb923c',garbage,'Vor allem repo_cache/fixtures. Erst Manifest, dann archivieren.')
card_box(635,870,530,420,'✅ Fast fertig','#84cc16',almost,'Hat klare Idee/README oder MVP-Signal. Braucht Closure statt Neubau.')
card_box(1200,870,530,420,'🧪 Braucht nur Tests','#38bdf8',needs_tests,'Nicht weiter bauen, bevor Smoke-Test/pytest grün ist.')
card_box(70,1335,530,455,'🌀 Scope-Creep','#facc15',token[:8],'Viele Pläne/TODOs/Agent-Spuren. Token-Burn-Gefahr.')
card_box(635,1335,530,455,'📦 Archivieren','#fb7185',archive[:8],'Nur Human-Gated. Nichts löschen; erst Patch/Manifest sichern.')
card_box(1200,1335,530,455,'🌅 Morgen als erstes','#c084fc',tomorrow,'1) Zentrale stabilisieren 2) ACT als Closure-Prozess 3) Hermes dirty state sichern')
# rules bottom
d.rounded_rectangle((70,1840,1660,455+1840),radius=24,fill='#0f172a',outline='#64748b',width=3)
d.text((100,1870),'🧠 Lernregel',fill='#e879f9',font=f_h)
yy=1930
for rule in data['rules']:
for ln in wrap(d,'• '+rule,f,1480):
d.text((110,yy),ln,fill=text,font=f); yy+=34
d.text((100,2220),'Morgen-Default: Keine neuen Projekte. Erst Zentrale + ACT + Hermes in einen verifizierbaren Loop bringen.',fill='#67e8f9',font=f)
d.text((100,2280),'Human Gate bleibt hart: Archive/Merge/Commit/Push nur nach expliziter Freigabe.',fill='#fca5a5',font=f_s)
img.save(OUT/'project_learning_graphic.png')
# HTML companion
def ul(items):
return '<ul>'+''.join(f'<li><code>{html.escape(short(x["path"]))}</code> <span>s={x.get("score")}</span></li>' for x in items)+'</ul>'
html_doc=f'''<!doctype html><html><head><meta charset="utf-8"><title>Samuel Project Map</title><style>body{{background:{bg};color:{text};font-family:Menlo,monospace;margin:0;padding:36px}}.grid{{display:grid;grid-template-columns:repeat(auto-fit,minmax(360px,1fr));gap:18px}}.card{{background:{card};border:1px solid {stroke};border-radius:18px;padding:20px}}h1{{color:#67e8f9}}h2{{color:#c084fc}}code{{color:#7dd3fc}}img{{max-width:100%;border-radius:20px;border:1px solid #334155}}</style></head><body><h1>Samuel Project Map — Lern-Grafik</h1><p><a href="project_learning_graphic.png">PNG öffnen</a></p><img src="project_learning_graphic.png"><div class="grid"><div class="card"><h2>10 wichtigste Projekte</h2>{ul(top10)}</div><div class="card"><h2>Gold</h2>{ul(gold)}</div><div class="card"><h2>Müll / Cache</h2>{ul(garbage)}</div><div class="card"><h2>Fast fertig</h2>{ul(almost)}</div><div class="card"><h2>Braucht Tests</h2>{ul(needs_tests)}</div><div class="card"><h2>Scope-Creep</h2>{ul(token[:8])}</div><div class="card"><h2>Archivieren</h2>{ul(archive[:8])}</div><div class="card"><h2>Morgen zuerst</h2>{ul(tomorrow)}</div></div></body></html>'''
(OUT/'project_learning_graphic.html').write_text(html_doc,encoding='utf-8')
result={'status':'succeeded','png':str(OUT/'project_learning_graphic.png'),'html':str(OUT/'project_learning_graphic.html'),'data':str(OUT/'learning_graphic_data.json'),'top10':[x['path'] for x in top10]}
(OUT/'result.json').write_text(json.dumps(result,indent=2,ensure_ascii=False),encoding='utf-8')
print(json.dumps(result,indent=2))
if __name__=='__main__': main()
|