File size: 6,676 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 158 159 160 161 162 | #!/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/'doc_reality_sync'
PLANS=OUT/'plans'
REPORTS=OUT/'reports'
PATCHES=OUT/'patches'
JOBS=ROOT/'jobs/pending'
for d in [OUT,PLANS,REPORTS,PATCHES,JOBS]: d.mkdir(parents=True,exist_ok=True)
MAX_PROJECTS=25
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 doc_status(row):
has_readme=bool(row.get('readme_present'))
dirty=row.get('dirty_count',0)
plans=row.get('plan_hit_count',0)
todos=row.get('todo_fixme_count',0)
cats=row.get('categories',[])
issues=[]
if not has_readme: issues.append('README missing/unknown')
if dirty: issues.append(f'{dirty} uncommitted changes may make docs stale')
if plans>=3: issues.append(f'{plans} old plans/handoffs may conflict with current state')
if todos>=20: issues.append(f'{todos} TODO/FIXME signals need doc triage')
if 'MERGE' in cats: issues.append('duplicate/merge candidate docs may describe wrong source of truth')
if 'ARCHIVE' in cats: issues.append('archive candidate docs should mark stale status')
return issues or ['docs appear low-risk but need reality smoke check']
def plan(row):
issues=doc_status(row)
return f"""# DOC_REALITY_SYNC 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_DOC_REALITY_SYNC.
Do not edit project docs directly. Produce a reality report and optional README patch artifact under ProjectIntakeTower.
## Suspected doc/reality gaps
""" + ''.join(f'- {x}\n' for x in issues) + f"""
## Required checks
1. Read README / AGENTS / CLAUDE / docs index if present.
2. Compare claimed status/features/run commands against actual files/manifests/tests.
3. Identify stale claims, missing setup steps, broken commands, unclear source-of-truth.
4. Produce `reports/{slug(row['path'])}_doc_reality_report.md`.
5. If useful, produce `patches/{slug(row['path'])}_README.patch.md` as a patch artifact only.
## Forbidden
- No source edits.
- No dependency installs.
- No commit/push/merge.
- No GitHub issue creation.
- No secrets.
## Output standard
Each report must answer:
- What docs claim.
- What repo reality shows.
- What is stale/missing/wrong.
- Minimal doc patch proposal.
- Verifier command for checking the claim.
"""
def report_template(row):
return f"""# DOC_REALITY_SYNC Report — {row['name']}
Project: `{row['path']}`
Status: draft_report_needs_worker
## Docs claim
TODO(worker): summarize README/docs claims with file references.
## Repo reality
TODO(worker): compare against actual files, manifests, tests, git state.
## Mismatches
TODO(worker): list stale/missing/wrong doc claims.
## Minimal patch proposal
TODO(worker): write exact README/docs patch suggestion here, not applied.
## Verifier
TODO(worker): command or file-exists/schema check proving docs match reality.
## Safety
No source docs were edited by this intake artifact.
"""
def patch_template(row):
return f"""# README Patch Artifact — {row['name']}
Project: `{row['path']}`
Status: placeholder_patch_not_applied
```diff
# Worker writes proposed README/docs diff here.
```
Human gate required before applying.
"""
def main():
pri=load(TRIAGE/'PROJECT_PRIORITIZATION.json')
selected=[]; seen=set()
for cat in ['KEEP','URGENT','MERGE','TOKEN_BURN_CANDIDATE','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=['# DOC_REALITY_SYNC','',f'Generated: {datetime.now(timezone.utc).isoformat()}','',f'Projects selected: {len(selected)}','', 'Mode: READ_ONLY_DOC_REALITY_SYNC — reports/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}_doc_reality_report.md'
patch_path=PATCHES/f'{i:02d}_{s}_README.patch.md'
plan_path.write_text(plan(row),encoding='utf-8')
report_path.write_text(report_template(row),encoding='utf-8')
patch_path.write_text(patch_template(row),encoding='utf-8')
job={
'job_id':f'docsync-2026-05-04-{i:02d}-{s.lower()}',
'project_name':row['name'], 'project_path':row['path'], 'mode':'review_only',
'allowed_actions':['read docs/source manifests','compare doc claims to repo reality','write ProjectIntakeTower doc reality report only','write ProjectIntakeTower patch artifact only'],
'forbidden_actions':['modify source project files','commit','push','merge','delete files','install dependencies','create GitHub issues','print secrets'],
'allowed_files':[f'{row["path"]} read-only','ProjectIntakeTower/doc_reality_sync write scope'],
'expected_artifacts':[str(plan_path),str(report_path),str(patch_path)],
'verifier_commands':['python3 ProjectIntakeTower/verifier/verify_doc_reality_sync.py'],
'stop_conditions':['source edit required','dependency install required','credentials required','unclear source-of-truth requires Samuel'],
'objective':f'Compare documentation claims to repository reality for {row["name"]}; produce report and patch artifact only.',
'source_plan':str(plan_path)
}
jp=JOBS/f'docsync_{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'Plan: `{plan_path}`',f'Report: `{report_path}`',f'Patch artifact: `{patch_path}`','']
result={'status':'succeeded','mode':'READ_ONLY_DOC_REALITY_SYNC','projects':len(selected),'plans':len(list(PLANS.glob('*.md'))),'reports':len(list(REPORTS.glob('*_doc_reality_report.md'))),'patch_artifacts':len(list(PATCHES.glob('*_README.patch.md'))),'jobs_created':len(jobs),'jobs':jobs}
(OUT/'DOC_REALITY_SYNC.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()
|