File size: 3,963 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
#!/usr/bin/env python3
from intake_common import *


def job_for(p):
    pid = p["project_id"]
    job_id = f"pit-{pid}-review"
    outdir = f"jobs/done/{job_id}"
    return {
        "job_id": job_id,
        "project_name": p["name"],
        "project_path": p["path"],
        "mode": "review_only",
        "task_type": "review",
        "objective": f"Read-only handoff for {p['name']}: collapse open mental tabs into one verified next action.",
        "allowed_actions": [
            "read non-secret project files",
            "read ProjectIntakeTower evidence and project card",
            "run non-destructive metadata commands only when needed",
            "write result artifacts inside ProjectIntakeTower/jobs/done/<job_id>/ only",
        ],
        "forbidden_actions": [
            "modify project files",
            "delete files",
            "commit",
            "push",
            "merge",
            "read or print secrets",
            "create GitHub issues",
            "run dependency installs or build steps that write into the project",
        ],
        "allowed_files": [
            f"{p['path']}/** read-only, excluding secrets and ignored dependency/build directories",
            f"{str(ROOT)}/inventory/project_cards/{pid}.md read-only",
            f"{str(ROOT)}/{p.get('evidence_file')} read-only",
            f"{str(ROOT)}/{outdir}/** write",
        ],
        "expected_artifacts": [
            f"{outdir}/result.json",
            f"{outdir}/handoff.md",
        ],
        "required_artifacts": [
            f"{outdir}/result.json",
            f"{outdir}/handoff.md",
        ],
        "verifier_commands": [
            f"python3 verifier/verify_artifacts.py --job jobs/pending/{job_id}.json --require-done"
        ],
        "verifier": {"type": "command", "command": f"python3 verifier/verify_artifacts.py --job jobs/pending/{job_id}.json --require-done"},
        "stop_conditions": [
            "credentials required",
            "write outside ProjectIntakeTower needed",
            "destructive action needed",
            "project ownership or intent unclear",
            "secret-like file must be opened to proceed",
        ],
        "scope": {
            "allowed_paths": [p["path"], str(ROOT)],
            "allowed_tools": ["read_file", "search_files", "terminal", "write_file"],
            "allowed_actions": ["read-only inspection", "write handoff artifacts under ProjectIntakeTower/jobs/done"],
            "forbidden_actions": ["modify source project", "commit", "push", "merge", "delete", "read secrets", "create issues"],
        },
        "inputs": {
            "project_card": f"inventory/project_cards/{pid}.md",
            "evidence_file": p.get("evidence_file"),
            "triage_status": p.get("triage_status"),
            "score": p.get("score"),
        },
        "timeout_minutes": 30,
        "max_iterations": 2,
        "if_blocked": "write status=needs_human in result.json and stop",
        "rejoin_policy": "summarize status, changed_files, artifact_paths, verifier result, needs_human, next_action",
    }


def main():
    ensure_dirs()
    # Pending contracts are generated artifacts owned by this harness; clear stale v0 contracts before writing v1.
    for old in PENDING.glob("*.json"):
        old.unlink()
    projects = sorted(load_jsonl(INV / "projects.jsonl"), key=project_sort_key)
    selected = [p for p in projects if p.get("triage_status") in {"needs_human", "active", "quick_win", "review"}]
    if len(selected) < min(20, len(projects)):
        selected = projects[:min(40, len(projects))]
    else:
        selected = selected[:40]
    for p in selected:
        job = job_for(p)
        (PENDING / f"{job['job_id']}.json").write_text(json.dumps(job, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps({"status": "succeeded", "jobs_pending_written": len(selected)}, indent=2))

if __name__ == "__main__":
    main()