Download ProjectIntakeTower/verifier/prioritize_projects.py from smlflg/AgentProjectMinenraeumer: direct link, hf CLI and curl.
- Browser
- Download file 8.23 kB
-
https://huggingface.co/smlflg/AgentProjectMinenraeumer/resolve/main/ProjectIntakeTower/verifier/prioritize_projects.py
- Command line
-
hf download hf://smlflg/AgentProjectMinenraeumer/ProjectIntakeTower/verifier/prioritize_projects.py
-
curl -L -o prioritize_projects.py https://huggingface.co/smlflg/AgentProjectMinenraeumer/resolve/main/ProjectIntakeTower/verifier/prioritize_projects.py
8.23 kB
| #!/usr/bin/env python3 | |
| import json, re | |
| from pathlib import Path | |
| from collections import defaultdict | |
| ROOT = Path(__file__).resolve().parents[1] | |
| INV = ROOT / 'inventory' | |
| E = INV / 'evidence' | |
| T = ROOT / 'triage' | |
| T.mkdir(parents=True, exist_ok=True) | |
| CATEGORIES = ['KEEP','MERGE','PAUSE','ARCHIVE','URGENT','TOKEN_BURN_CANDIDATE','HUMAN_DECISION_REQUIRED'] | |
| def load_json(name, default): | |
| p = E / name | |
| if not p.exists(): return default | |
| return json.loads(p.read_text(encoding='utf-8')) | |
| def load_projects(): | |
| rows=[] | |
| p=INV/'projects.jsonl' | |
| for line in p.read_text(encoding='utf-8').splitlines(): | |
| if line.strip(): | |
| try: rows.append(json.loads(line)) | |
| except Exception: pass | |
| seen={}; out=[] | |
| for r in rows: | |
| path=r.get('path') or '' | |
| if not path or path in seen: continue | |
| seen[path]=1; out.append(r) | |
| return out | |
| def norm_path(p): return str(p or '') | |
| def basename(path): return Path(path).name or path.rstrip('/').split('/')[-1] | |
| def is_cache(path): | |
| s=path.lower() | |
| return any(x in s for x in ['/repo_cache/','/eval/fixtures/','/codex-skill-validation-fixtures/','/node_modules/','.venv']) | |
| def theme_map(theme_rows): | |
| m=defaultdict(set) | |
| for row in theme_rows: | |
| m[row.get('repo','')].update(row.get('themes',[])) | |
| return m | |
| def main(): | |
| projects=load_projects() | |
| uncomm={x['repo']:x for x in load_json('uncommitted_changes.json',[])} | |
| branches={x['repo']:x for x in load_json('open_branches.json',[])} | |
| broken={x['repo']:x for x in load_json('broken_projects.json',[])} | |
| dups=load_json('duplicate_projects.json',[]) | |
| themes=theme_map(load_json('theme_clusters.json',[])) | |
| dup_paths={p:row for row in dups for p in row.get('repos',[])} | |
| rows=[] | |
| buckets={c:[] for c in CATEGORIES} | |
| for p in projects: | |
| path=norm_path(p.get('path')) | |
| name=p.get('name') or basename(path) | |
| score=int(p.get('score') or 0) | |
| todo=int(p.get('todo_fixme_count') or 0) | |
| plan=int(p.get('plan_hit_count') or 0) | |
| agent=int(p.get('agent_artifact_count') or 0) | |
| readme=bool(p.get('readme_path') or p.get('readme_summary')) | |
| dirty=uncomm.get(path,{}).get('count',0) or (p.get('git_status') or {}).get('dirty_tracked_count',0) or 0 | |
| branch_count=len(branches.get(path,{}).get('branches',[])) | |
| is_dup=path in dup_paths | |
| th=themes.get(path,set()) | |
| tech=','.join(p.get('technologies',[]) or p.get('project_type',[]) or ['unknown']) | |
| reasons=[] | |
| cats=[] | |
| # Priority rules: categories are non-exclusive. | |
| if dirty >= 10 or (dirty >= 5 and th) or path in ['/home/smlflg/.hermes','/home/smlflg/.claude','/home/smlflg/.codex']: | |
| cats.append('URGENT'); reasons.append(f'uncommitted_changes={dirty}') | |
| if dirty or branch_count > 1 or p.get('risk_level') == 'high' or path in broken: | |
| cats.append('HUMAN_DECISION_REQUIRED'); reasons.append('state/risk needs owner decision') | |
| if is_dup: | |
| cats.append('MERGE'); reasons.append('duplicate cluster') | |
| if is_cache(path) or re.search(r'(-old|backup|copy|repo_cache|fixture|astropy__|django__|sympy__)', path, re.I): | |
| cats.append('ARCHIVE'); reasons.append('cache/fixture/old-artifact signal') | |
| # token burn: many plans/todos/agent artifacts but weak closure/readme or duplicated meta system. | |
| if (plan >= 5 and todo >= 20) or (agent >= 5 and dirty >= 5) or (is_dup and score >= 50): | |
| cats.append('TOKEN_BURN_CANDIDATE'); reasons.append(f'plan={plan}, todo={todo}, agent_artifacts={agent}, score={score}') | |
| keep_signal = score >= 65 or bool(th & {'agent_control_tower_near','hermes_near','uni_near'}) or readme and score >= 50 | |
| if keep_signal and 'ARCHIVE' not in cats: | |
| cats.append('KEEP'); reasons.append('high score/theme/readme signal') | |
| if not cats or ('KEEP' not in cats and 'MERGE' not in cats and 'ARCHIVE' not in cats and score < 50): | |
| cats.append('PAUSE'); reasons.append('low confidence or not top-level valuable now') | |
| # If KEEP plus MERGE, the repo is valuable but should be merged/consolidated, not developed separately. | |
| categories=list(dict.fromkeys(cats)) | |
| row={ | |
| 'name': name, 'path': path, 'categories': categories, | |
| 'score': score, 'dirty_count': dirty, 'branch_count': branch_count, | |
| 'todo_fixme_count': todo, 'plan_hit_count': plan, 'agent_artifact_count': agent, | |
| 'themes': sorted(th), 'duplicate_cluster': dup_paths.get(path,{}).get('key'), | |
| 'readme_present': readme, 'tech': tech, 'reasons': reasons[:8] | |
| } | |
| rows.append(row) | |
| for c in categories: buckets[c].append(row) | |
| # Sort within buckets: urgent by dirty, keep by score, archive by cache-ish path, pause low score. | |
| for c in CATEGORIES: | |
| buckets[c].sort(key=lambda r: (-(r['dirty_count'] if c=='URGENT' else r['score']), r['path'])) | |
| (T/'PROJECT_PRIORITIZATION.json').write_text(json.dumps({'categories':buckets,'projects':rows},indent=2,ensure_ascii=False),encoding='utf-8') | |
| md=['# Project Prioritization','','Categories are non-exclusive. KEEP+MERGE means valuable but consolidate instead of building separately. URGENT/HUMAN_DECISION means do not delegate blindly.',''] | |
| for c in CATEGORIES: | |
| md.append(f'## {c} ({len(buckets[c])})') | |
| for r in buckets[c][:80]: | |
| extra=[] | |
| if r['dirty_count']: extra.append(f"dirty={r['dirty_count']}") | |
| if r['branch_count']>1: extra.append(f"branches={r['branch_count']}") | |
| if r['duplicate_cluster']: extra.append(f"dup={r['duplicate_cluster']}") | |
| if r['themes']: extra.append('themes='+','.join(r['themes'])) | |
| extra.append(f"score={r['score']}") | |
| md.append(f"- `{r['path']}` — {', '.join(extra)} — {('; '.join(r['reasons']))[:220]}") | |
| if len(buckets[c])>80: md.append(f'- ... {len(buckets[c])-80} more in JSON') | |
| md.append('') | |
| (T/'PROJECT_PRIORITIZATION.md').write_text('\n'.join(md),encoding='utf-8') | |
| # one-file-per-category for quick opening | |
| for c in CATEGORIES: | |
| lines=[f'# {c}', '', f'Count: {len(buckets[c])}', ''] | |
| for r in buckets[c]: | |
| lines.append(f"## {r['name']}") | |
| lines.append(f"Path: `{r['path']}`") | |
| lines.append(f"Score: {r['score']} | Dirty: {r['dirty_count']} | Branches: {r['branch_count']} | TODO/FIXME: {r['todo_fixme_count']} | Plans: {r['plan_hit_count']}") | |
| lines.append(f"Categories: {', '.join(r['categories'])}") | |
| if r['themes']: lines.append(f"Themes: {', '.join(r['themes'])}") | |
| if r['duplicate_cluster']: lines.append(f"Duplicate cluster: {r['duplicate_cluster']}") | |
| lines.append('Reasons:') | |
| for reason in r['reasons']: lines.append(f'- {reason}') | |
| lines.append('') | |
| (T/f'{c}.md').write_text('\n'.join(lines),encoding='utf-8') | |
| top10=[] | |
| for r in buckets['URGENT'][:5]: | |
| top10.append(('Stabilize dirty repo before more work', r)) | |
| for r in buckets['MERGE'][:3]: | |
| top10.append(('Decide consolidation target', r)) | |
| for r in buckets['KEEP'][:5]: | |
| if all(x[1]['path']!=r['path'] for x in top10): top10.append(('Keep and assign focused verifier', r)) | |
| top10=top10[:10] | |
| lines=['# Top 10 Next Actions — Prioritized Categories','', '| Rank | Action | Category | Project | Why | Human gate |','|---:|---|---|---|---|---|'] | |
| for i,(action,r) in enumerate(top10,1): | |
| cats=', '.join(r['categories']) | |
| why=', '.join(r['reasons'])[:180] | |
| gate='yes' if any(c in r['categories'] for c in ['URGENT','HUMAN_DECISION_REQUIRED','MERGE','ARCHIVE']) else 'no' | |
| lines.append(f"| {i} | {action} | {cats} | `{r['path']}` | {why} | {gate} |") | |
| (T/'TOP_10_NEXT_ACTIONS.md').write_text('\n'.join(lines)+'\n',encoding='utf-8') | |
| summary={c:len(buckets[c]) for c in CATEGORIES} | |
| summary['total_projects']=len(rows) | |
| (T/'PROJECT_PRIORITIZATION_SUMMARY.json').write_text(json.dumps(summary,indent=2),encoding='utf-8') | |
| print(json.dumps(summary,indent=2)) | |
| if __name__=='__main__': main() | |