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