File size: 6,164 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 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | #!/usr/bin/env python3
import json, re
from pathlib import Path
from datetime import datetime, timezone
ROOT=Path(__file__).resolve().parents[1]
TRIAGE=ROOT/'triage'
OUT=ROOT/'small_fix'
PLANS=OUT/'plans'; PATCHES=OUT/'patches'; REPORTS=OUT/'reports'; JOBS=ROOT/'jobs/pending'
for d in [OUT,PLANS,PATCHES,REPORTS,JOBS]: d.mkdir(parents=True,exist_ok=True)
MAX_PROJECTS=20
def load(p): return json.loads(Path(p).read_text(encoding='utf-8'))
def slug(s): return re.sub(r'[^A-Za-z0-9_.-]+','_',s.replace('/home/smlflg/',''))[:100]
def classify_fix(row):
cats=row.get('categories',[]); tech=(row.get('tech') or '').lower()
if 'MERGE' in cats: return 'write merge note / source-of-truth marker patch'
if not row.get('readme_present'): return 'add minimal README status section patch'
if row.get('dirty_count',0): return 'stabilize docs/tests around uncommitted change, no source edit'
if 'python' in tech: return 'add smoke test patch artifact'
return 'add verifier/docs status patch artifact'
def verifier(row):
tech=(row.get('tech') or '').lower()
if 'python' in tech: return 'python -m compileall -q . && pytest -q'
if 'node' in tech: return 'npm test'
return 'git status --short && test -e README.md || test -e README'
def plan(row):
fix=classify_fix(row); cmd=verifier(row)
return f"""# SMALL_FIX Plan — {row['name']}
Project: `{row['path']}`
Categories: {', '.join(row['categories'])}
Score: {row['score']}
Dirty: {row['dirty_count']}
TODO/FIXME: {row['todo_fixme_count']}
Plans: {row['plan_hit_count']}
## Mode
READ_ONLY_SMALL_FIX.
No direct source edits. Produce one minimal patch artifact under ProjectIntakeTower.
## Fix class
{fix}
## Constraints
- One small fix only.
- Must have root-cause note before patch proposal.
- Must include verifier command.
- No refactor.
- No dependency install.
- No commit/push/merge.
- If unclear, mark needs_human.
## Suggested verifier
```bash
cd {row['path']}
{cmd}
```
## Required output
- Report: `small_fix/reports/{slug(row['path'])}_small_fix_report.md`
- Patch artifact: `small_fix/patches/{slug(row['path'])}_small_fix.patch.md`
"""
def report(row):
return f"""# SMALL_FIX Report — {row['name']}
Project: `{row['path']}`
Status: draft_needs_worker
## Root-cause note
TODO(worker): explain why this small fix is needed from observed repo/doc/test evidence.
## Proposed minimal fix
TODO(worker): one small, bounded fix only.
## Patch artifact
See matching file in `small_fix/patches/`.
## Verifier
```bash
cd {row['path']}
{verifier(row)}
```
## Safety
No source files were changed by this intake artifact.
"""
def patch(row):
return f"""# SMALL_FIX Patch Artifact — {row['name']}
Project: `{row['path']}`
Fix class: {classify_fix(row)}
Status: placeholder_patch_not_applied
```diff
# Worker writes minimal proposed diff here.
# Do not apply without Samuel approval.
```
Verifier:
```bash
cd {row['path']}
{verifier(row)}
```
"""
def main():
pri=load(TRIAGE/'PROJECT_PRIORITIZATION.json')
selected=[]; seen=set()
# Prefer valuable + human-decision projects where small artifacts reduce ambiguity.
for cat in ['URGENT','KEEP','MERGE','HUMAN_DECISION_REQUIRED']:
for r in pri['categories'].get(cat,[]):
if r['path'] in seen: continue
if 'ARCHIVE' in r['categories'] and 'KEEP' not in r['categories']: continue
selected.append(r); seen.add(r['path'])
if len(selected)>=MAX_PROJECTS: break
if len(selected)>=MAX_PROJECTS: break
index=['# SMALL_FIX','',f'Generated: {datetime.now(timezone.utc).isoformat()}','',f'Projects selected: {len(selected)}','', 'Mode: READ_ONLY_SMALL_FIX — patch artifacts only; no source edits.', '']
jobs=[]
for i,row in enumerate(selected,1):
s=slug(row['path'])
plan_path=PLANS/f'{i:02d}_{s}.md'; report_path=REPORTS/f'{i:02d}_{s}_small_fix_report.md'; patch_path=PATCHES/f'{i:02d}_{s}_small_fix.patch.md'
plan_path.write_text(plan(row),encoding='utf-8'); report_path.write_text(report(row),encoding='utf-8'); patch_path.write_text(patch(row),encoding='utf-8')
job={
'job_id':f'smallfix-2026-05-04-{i:02d}-{s.lower()}',
'project_name':row['name'],'project_path':row['path'],'mode':'review_only',
'allowed_actions':['read files','inspect root cause evidence','write ProjectIntakeTower small_fix report only','write ProjectIntakeTower patch artifact only','run non-destructive verifier discovery'],
'forbidden_actions':['modify source project files','commit','push','merge','delete files','install dependencies','create GitHub issues','print secrets','refactor unrelated code'],
'allowed_files':[f'{row["path"]} read-only','ProjectIntakeTower/small_fix write scope'],
'expected_artifacts':[str(plan_path),str(report_path),str(patch_path)],
'verifier_commands':['python3 ProjectIntakeTower/verifier/verify_small_fix.py'],
'stop_conditions':['root cause unclear','source edit required','dependency install required','human product decision required'],
'objective':f'Produce one minimal small-fix patch artifact for {row["name"]}; do not apply it.',
'fix_class':classify_fix(row),'suggested_verifier':verifier(row)
}
jp=JOBS/f'smallfix_{i:02d}_{s}.json'; jp.write_text(json.dumps(job,indent=2,ensure_ascii=False),encoding='utf-8'); jobs.append(str(jp))
index += [f'## {i}. {row["name"]}',f'Path: `{row["path"]}`',f'Fix class: {classify_fix(row)}',f'Plan: `{plan_path}`',f'Report: `{report_path}`',f'Patch: `{patch_path}`','']
result={'status':'succeeded','mode':'READ_ONLY_SMALL_FIX','projects':len(selected),'plans':len(list(PLANS.glob('*.md'))),'reports':len(list(REPORTS.glob('*_small_fix_report.md'))),'patch_artifacts':len(list(PATCHES.glob('*_small_fix.patch.md'))),'jobs_created':len(jobs),'jobs':jobs}
(OUT/'SMALL_FIX.md').write_text('\n'.join(index),encoding='utf-8'); (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()
|