#!/usr/bin/env python3 import json, re from pathlib import Path ROOT=Path(__file__).resolve().parents[1] E=ROOT/'inventory/evidence'; T=ROOT/'triage'; J=ROOT/'jobs/pending'; G=ROOT/'github/issue_drafts' def load(n): return json.loads((E/n).read_text(encoding='utf-8')) counts={n:len(load(n)) for n in ['git_repos.json','readmes.json','todos_fixmes.json','agent_results.json','next_issues.json','old_plans.json','uncommitted_changes.json','open_branches.json','tests.json','broken_projects.json','duplicate_projects.json','theme_clusters.json']} uncomm=load('uncommitted_changes.json'); branches=load('open_branches.json'); dup=load('duplicate_projects.json'); themes=load('theme_clusters.json'); broken=load('broken_projects.json') def has_theme(repo, key): return any(x['repo']==repo and key in x['themes'] for x in themes) lines=['# Focused Findings','','## Counts'] for k,v in counts.items(): lines.append(f'- {k}: {v}') lines += ['','## Highest-risk uncommitted repos'] for row in sorted(uncomm,key=lambda x:-x['count'])[:15]: lines.append(f"- {row['count']:>3} changes: `{row['repo']}`") lines += ['','## Open branch repos'] for row in branches[:20]: lines.append(f"- `{row['repo']}`: {', '.join(b['name'] for b in row['branches'][:5])}") lines += ['','## Duplicate project clusters'] for row in dup: lines.append(f"- {row['key']}: " + ' | '.join(f'`{p}`' for p in row['repos'])) lines += ['','## Theme clusters'] for key in ['agent_control_tower_near','hermes_near','uni_near']: lines.append(f'### {key}') for row in [x for x in themes if key in x['themes']][:30]: lines.append(f"- `{row['repo']}`") lines += ['','## Broken/needs-focused-verification samples'] for row in broken[:30]: lines.append(f"- `{row['repo']}`: {', '.join(row['reasons'])}") (T/'FOCUSED_FINDINGS.md').write_text('\n'.join(lines)+'\n',encoding='utf-8') # category-aware top 10 candidates=[] for row in uncomm: repo=row['repo']; score=row['count'] if has_theme(repo,'hermes_near'): score+=60 if has_theme(repo,'agent_control_tower_near'): score+=50 if has_theme(repo,'uni_near'): score+=30 if repo.startswith('/home/smlflg/Projekte'): score+=20 candidates.append((score, 'Stabilize uncommitted changes', repo, f"{row['count']} uncommitted changes; preserve/triage before more agent work", 'git status + no commit without approval')) for row in dup: score=45+len(row['repos'])*5 candidates.append((score,'Decide merge/archive direction', row['repos'][0], f"duplicate cluster: {row['key']} ({len(row['repos'])} repos)", 'human decision + merge plan')) for row in branches: repo=row['repo']; score=25+len(row['branches'])*5 if repo.startswith('/home/smlflg/Projekte'): score+=10 candidates.append((score,'Resolve open branches', repo, f"{len(row['branches'])} local branches", 'git branch review')) for row in themes: repo=row['repo'] score=35 if 'hermes_near' in row['themes']: score+=25 if 'agent_control_tower_near' in row['themes']: score+=20 if 'uni_near' in row['themes']: score+=15 candidates.append((score,'Create focused project card + verifier', repo, f"theme={','.join(row['themes'])}", 'project card + lightweight verifier')) seen=set(); top=[] for score,action,repo,why,ver in sorted(candidates,key=lambda x:-x[0]): key=(action,repo) if key in seen: continue seen.add(key); top.append((score,action,repo,why,ver)) if len(top)==10: break tl=['# Top 10 Next Actions','', '| Rank | Action | Project/Path | Why valuable | Effort | Owner | Verifier | Risk/Human Gate |','|---:|---|---|---|---|---|---|---|'] for i,(score,action,repo,why,ver) in enumerate(top,1): gate='yes for commit/push/issues; no for read-only analysis' tl.append(f"| {i} | {action} | `{repo}` | {why} | M | Agent then Samuel | {ver} | {gate} |") (T/'TOP_10_NEXT_ACTIONS.md').write_text('\n'.join(tl)+'\n',encoding='utf-8') # Handoff jobs + issue drafts for top 10, drafts only for i,(score,action,repo,why,ver) in enumerate(top,1): slug=re.sub(r'[^a-zA-Z0-9_.-]+','_', repo.replace('/home/smlflg/',''))[:80] job={ 'job_id': f'work-2026-05-04-focused-{i:02d}-{slug.lower()}', 'task_type':'verify', 'objective': f'{action}: {repo}', 'scope': {'allowed_paths':[repo, str(ROOT)], 'allowed_tools':['terminal','read_file','search_files','write_file'], 'allowed_actions':['read files','run non-destructive git/test discovery','write artifacts under ProjectIntakeTower/inventory/evidence'], 'forbidden_actions':['commit','push','merge','delete files','create GitHub issues','print secrets']}, 'inputs': {'why': why, 'source_artifacts':['inventory/evidence/uncommitted_changes.json','inventory/evidence/open_branches.json','inventory/evidence/duplicate_projects.json','inventory/evidence/theme_clusters.json']}, 'required_artifacts':[f'inventory/evidence/focused_{i:02d}_{slug}_result.json'], 'verifier': {'type':'command','command':'python3 ProjectIntakeTower/verifier/verify_artifacts.py'}, 'timeout_minutes':30, 'max_iterations':2, 'if_blocked':'write needs_human result and stop', 'rejoin_policy':'summarize artifact, verifier result, next action' } (J/f'focused_{i:02d}_{slug}.json').write_text(json.dumps(job,indent=2,ensure_ascii=False),encoding='utf-8') d=G/'focused_top10'; d.mkdir(exist_ok=True,parents=True) (d/f'{i:02d}_{slug}.md').write_text(f'Title: {action}\nRepo: NEEDS_REPO_MAPPING\nLabels: intake:discovered,agent:handoff-ready\n\n## Path\n`{repo}`\n\n## Why\n{why}\n\n## Acceptance criteria\n- Focused finding verified against repo state.\n- Safe next action documented.\n- No commit/push/merge without Samuel approval.\n\n## Verifier\n{ver}\n',encoding='utf-8') print('wrote focused findings/top10/jobs/issue drafts')