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()