@paradigma-inc/flywheel 0.1.19 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +8 -0
  2. package/package.json +1 -1
  3. package/skills/flywheel/references/experiment-design-protocol.md +1 -1
  4. package/skills/flywheel/references/flywheel-mcp-tool-map.md +32 -35
  5. package/skills/flywheel/setting-up-flywheel/updating-flywheel-mcp.md +8 -0
  6. package/skills/flywheel-auto/SKILL.md +1 -1
  7. package/skills/flywheel-auto/references/ARTIFACTS.md +0 -1
  8. package/skills/flywheel-auto/references/INTERFACES.md +16 -10
  9. package/skills/flywheel-auto/references/experiment-design-protocol.md +1 -1
  10. package/skills/flywheel-auto/references/flywheel-mcp-tool-map.md +32 -35
  11. package/skills/flywheel-lookahead/SKILL.md +22 -15
  12. package/skills/flywheel-lookahead/agents/openai.yaml +3 -3
  13. package/skills/flywheel-lookahead/evals/evals.json +13 -1
  14. package/skills/flywheel-lookahead/references/ARTIFACTS.md +0 -1
  15. package/skills/flywheel-lookahead/references/INTERFACES.md +16 -10
  16. package/skills/flywheel-lookahead/references/flywheel-mcp-tool-map.md +32 -35
  17. package/skills/flywheel-prove/SKILL.md +163 -0
  18. package/skills/flywheel-prove/agents/interface.yaml +4 -0
  19. package/skills/flywheel-prove/assets/pipeline_template/bin/tproof +3 -0
  20. package/skills/flywheel-prove/assets/pipeline_template/bin/tproof.cmd +2 -0
  21. package/skills/flywheel-prove/assets/pipeline_template/logs/.gitkeep +1 -0
  22. package/skills/flywheel-prove/assets/pipeline_template/pyproject.toml +23 -0
  23. package/skills/flywheel-prove/assets/pipeline_template/scripts/smoke_test.cmd +2 -0
  24. package/skills/flywheel-prove/assets/pipeline_template/scripts/smoke_test.sh +3 -0
  25. package/skills/flywheel-prove/assets/pipeline_template/src/tproof/__init__.py +1 -0
  26. package/skills/flywheel-prove/assets/pipeline_template/src/tproof/cli.py +298 -0
  27. package/skills/flywheel-prove/assets/pipeline_template/src/tproof/constants.py +10 -0
  28. package/skills/flywheel-prove/assets/pipeline_template/src/tproof/layout.py +51 -0
  29. package/skills/flywheel-prove/assets/pipeline_template/src/tproof/leanops.py +116 -0
  30. package/skills/flywheel-prove/assets/pipeline_template/src/tproof/runstore.py +58 -0
  31. package/skills/flywheel-prove/assets/pipeline_template/src/tproof/tasking.py +94 -0
  32. package/skills/flywheel-prove/assets/pipeline_template/workspace/prompts/fill_sorries.txt +3 -0
  33. package/skills/flywheel-prove/references/workflow.md +193 -0
  34. package/skills/flywheel-prove/scripts/scaffold_pipeline.py +111 -0
  35. package/skills/flywheel-reproduce/SKILL.md +28 -23
  36. package/skills/flywheel-reproduce/evals/evals.json +7 -1
  37. package/skills/flywheel-reproduce/references/ARTIFACTS.md +0 -1
  38. package/skills/flywheel-reproduce/references/INTERFACES.md +16 -10
  39. package/skills/flywheel-reproduce/references/experiment-design-protocol.md +1 -1
  40. package/skills/flywheel-reproduce/references/flywheel-mcp-tool-map.md +32 -35
  41. package/skills/flywheel-reproduce/references/source-blog.md +35 -0
  42. package/skills/flywheel-reproduce/references/source-generic.md +30 -0
  43. package/skills/flywheel-reproduce/references/source-notes.md +35 -0
  44. package/skills/flywheel-reproduce/references/source-paper.md +89 -0
  45. package/skills/flywheel-reproduce/references/source-wiki.md +36 -0
  46. package/skills/flywheel-to-graph/SKILL.md +26 -21
  47. package/skills/flywheel-to-graph/evals/evals.json +7 -1
  48. package/skills/flywheel-to-graph/references/ARTIFACTS.md +0 -1
  49. package/skills/flywheel-to-graph/references/INTERFACES.md +16 -10
  50. package/skills/flywheel-to-graph/references/flywheel-mcp-tool-map.md +32 -35
  51. package/skills/flywheel-to-graph/references/source-blog.md +34 -0
  52. package/skills/flywheel-to-graph/references/source-generic.md +29 -0
  53. package/skills/flywheel-to-graph/references/source-notes.md +34 -0
  54. package/skills/flywheel-to-graph/references/source-paper.md +85 -0
  55. package/skills/flywheel-to-graph/references/source-wiki.md +35 -0
  56. package/src/cli.mjs +69 -3
@@ -0,0 +1,298 @@
1
+ import shutil
2
+ from datetime import datetime, timezone
3
+ from pathlib import Path
4
+
5
+ import typer
6
+ from rich import print as rprint
7
+
8
+ from tproof.constants import (
9
+ LEAN_TOOLCHAIN,
10
+ MATHLIB_HASH,
11
+ OPS_AGENT_NOTES,
12
+ OPS_AGENT_ROLE,
13
+ PACKAGE_NAME,
14
+ PROOF_AGENT_NOTES,
15
+ PROOF_AGENT_ROLE,
16
+ )
17
+ from tproof.layout import Paths, default_paths, ensure_layout
18
+ from tproof.leanops import (
19
+ ensure_toolchain_installed,
20
+ lake_build,
21
+ lake_update,
22
+ reindex_allproofs,
23
+ seed_lean_project,
24
+ snapshot_project,
25
+ )
26
+ from tproof.runstore import create_run, load_run, new_run_id, now_utc_iso, save_run
27
+ from tproof.tasking import (
28
+ build_run_brief,
29
+ discover_project_modules,
30
+ find_sorry_hits,
31
+ read_task_prompt,
32
+ )
33
+
34
+ app = typer.Typer(no_args_is_help=True, help="Local Lean theorem proving pipeline CLI")
35
+
36
+
37
+ def _paths() -> Paths:
38
+ paths = default_paths()
39
+ ensure_layout(paths)
40
+ return paths
41
+
42
+
43
+ def _write_log(log_path: Path, text: str) -> None:
44
+ log_path.parent.mkdir(parents=True, exist_ok=True)
45
+ log_path.write_text(text, encoding="utf-8")
46
+
47
+
48
+ def _find_forbidden_staging_artifacts(paths: Paths) -> list[str]:
49
+ staging_dir = paths.workspace_dir / "staging"
50
+ if not staging_dir.is_dir():
51
+ return []
52
+ blocked_suffixes = {".lean", ".md"}
53
+ hits: list[str] = []
54
+ for file_path in sorted(staging_dir.rglob("*")):
55
+ if not file_path.is_file():
56
+ continue
57
+ if file_path.name == ".gitkeep":
58
+ continue
59
+ if file_path.suffix.lower() in blocked_suffixes:
60
+ hits.append(str(file_path.relative_to(paths.root)))
61
+ return hits
62
+
63
+
64
+ def _which(binary: str) -> str | None:
65
+ return shutil.which(binary)
66
+
67
+
68
+ @app.command("doctor")
69
+ def doctor() -> None:
70
+ paths = _paths()
71
+ lean_path = _which("lean")
72
+ lake_path = _which("lake")
73
+ elan_path = _which("elan")
74
+
75
+ if not lean_path or not lake_path or not elan_path:
76
+ raise RuntimeError(
77
+ "Missing required binaries. Ensure `lean`, `lake`, and `elan` are installed and on PATH."
78
+ )
79
+
80
+ from tproof.leanops import run_cmd
81
+
82
+ lean_code, lean_out = run_cmd(["lean", "--version"], cwd=paths.root)
83
+ lake_code, lake_out = run_cmd(["lake", "--version"], cwd=paths.root)
84
+ if lean_code != 0 or lake_code != 0:
85
+ raise RuntimeError("Unable to run `lean --version` or `lake --version`.")
86
+
87
+ rprint(f"[green]lean:[/green] {lean_path}")
88
+ rprint(f"[green]lake:[/green] {lake_path}")
89
+ rprint(f"[green]elan:[/green] {elan_path}")
90
+ rprint(f"[green]Pinned toolchain:[/green] {LEAN_TOOLCHAIN}")
91
+ rprint(f"[green]Pinned mathlib hash:[/green] {MATHLIB_HASH}")
92
+ rprint(f"[green]Proof worker:[/green] {PROOF_AGENT_ROLE}")
93
+ rprint(f"[green]Proof notes:[/green] {PROOF_AGENT_NOTES}")
94
+ rprint(f"[green]Ops worker:[/green] {OPS_AGENT_ROLE}")
95
+ rprint(f"[green]Ops notes:[/green] {OPS_AGENT_NOTES}")
96
+ rprint(f"[green]Lean project path:[/green] {paths.lean_project_dir}")
97
+ rprint(lean_out.strip())
98
+ rprint(lake_out.strip())
99
+
100
+
101
+ @app.command("init")
102
+ def init(build: bool = typer.Option(True, "--build/--no-build")) -> None:
103
+ paths = _paths()
104
+ seed_lean_project(
105
+ paths.lean_project_dir,
106
+ lean_toolchain=LEAN_TOOLCHAIN,
107
+ mathlib_hash=MATHLIB_HASH,
108
+ )
109
+ reindex_allproofs(paths.lean_project_dir)
110
+
111
+ install_code, install_out = ensure_toolchain_installed(LEAN_TOOLCHAIN)
112
+ already_installed = "already installed" in install_out.lower()
113
+ if install_code != 0 and not already_installed:
114
+ raise RuntimeError(f"elan toolchain install failed:\n{install_out}")
115
+
116
+ update_code, update_out = lake_update(paths.lean_project_dir)
117
+ if update_code != 0:
118
+ raise RuntimeError(f"lake update failed:\n{update_out}")
119
+
120
+ logs = [
121
+ "== elan toolchain install ==",
122
+ install_out,
123
+ "== lake update ==",
124
+ update_out,
125
+ ]
126
+ if build:
127
+ build_code, build_out = lake_build(paths.lean_project_dir)
128
+ if build_code != 0:
129
+ raise RuntimeError(f"lake build failed:\n{build_out}")
130
+ logs.extend(["== lake build ==", build_out])
131
+
132
+ log_name = datetime.now(timezone.utc).strftime("init_%Y%m%dT%H%M%SZ.log")
133
+ _write_log(paths.logs_dir / log_name, "\n".join(logs))
134
+ rprint(f"[green]Workspace initialized:[/green] {paths.lean_project_dir}")
135
+ rprint(f"[green]Toolchain:[/green] {LEAN_TOOLCHAIN}")
136
+ rprint(f"[green]Mathlib hash:[/green] {MATHLIB_HASH}")
137
+
138
+
139
+ @app.command("reindex")
140
+ def reindex(project_dir: Path | None = typer.Option(None, "--project-dir")) -> None:
141
+ paths = _paths()
142
+ target = project_dir or paths.lean_project_dir
143
+ all_proofs_path = reindex_allproofs(target)
144
+ rprint(f"[green]Reindexed:[/green] {all_proofs_path}")
145
+
146
+
147
+ @app.command("start-run")
148
+ def start_run(
149
+ prompt_file: Path | None = typer.Option(None, "--prompt-file"),
150
+ prompt_text: str | None = typer.Option(None, "--prompt-text"),
151
+ run_label: str | None = typer.Option(None, "--run-label"),
152
+ context_file: Path | None = typer.Option(None, "--context-file"),
153
+ ) -> None:
154
+ paths = _paths()
155
+ task_prompt = read_task_prompt(prompt_file=prompt_file, prompt_text=prompt_text)
156
+ context_text = context_file.read_text(encoding="utf-8") if context_file else None
157
+ modules = discover_project_modules(paths.lean_project_dir)
158
+ sorry_hits = find_sorry_hits(paths.lean_project_dir)
159
+
160
+ run_id = new_run_id(run_label)
161
+ run = {
162
+ "run_id": run_id,
163
+ "created_at": now_utc_iso(),
164
+ "updated_at": now_utc_iso(),
165
+ "status": "READY_FOR_PROOF",
166
+ "task_prompt": task_prompt,
167
+ "context_file": str(context_file.resolve()) if context_file else None,
168
+ "context_text": context_text,
169
+ "known_modules": modules,
170
+ "sorry_hits": sorry_hits,
171
+ "proof_agent_role": PROOF_AGENT_ROLE,
172
+ "proof_agent_notes": PROOF_AGENT_NOTES,
173
+ "ops_agent_role": OPS_AGENT_ROLE,
174
+ "ops_agent_notes": OPS_AGENT_NOTES,
175
+ "lean_project_dir": str(paths.lean_project_dir),
176
+ }
177
+ create_run(paths, run_id, run)
178
+
179
+ run_dir = paths.runs_dir / run_id
180
+ snapshot_project(paths.lean_project_dir, run_dir / "project_snapshot")
181
+ (run_dir / "task_prompt.txt").write_text(task_prompt, encoding="utf-8")
182
+ if context_text:
183
+ (run_dir / "context.txt").write_text(context_text, encoding="utf-8")
184
+ brief = build_run_brief(
185
+ run_id=run_id,
186
+ task_prompt=task_prompt,
187
+ known_modules=modules,
188
+ sorry_hits=sorry_hits,
189
+ context_text=context_text,
190
+ )
191
+ (run_dir / "run_brief.md").write_text(brief, encoding="utf-8")
192
+
193
+ rprint(f"[green]Run created:[/green] {run_id}")
194
+ rprint(f"[green]Run file:[/green] {run_dir / 'run.json'}")
195
+ rprint(f"[green]Brief file:[/green] {run_dir / 'run_brief.md'}")
196
+ rprint(f"[green]Outstanding sorries:[/green] {len(sorry_hits)}")
197
+
198
+
199
+ @app.command("show-run")
200
+ def show_run(run_id: str = typer.Argument(...)) -> None:
201
+ paths = _paths()
202
+ run = load_run(paths, run_id)
203
+ rprint(run)
204
+
205
+
206
+ @app.command("set-status")
207
+ def set_status(
208
+ run_id: str = typer.Argument(...),
209
+ status: str = typer.Argument(...),
210
+ note: str | None = typer.Option(None, "--note"),
211
+ ) -> None:
212
+ paths = _paths()
213
+ run = load_run(paths, run_id)
214
+ run["status"] = status
215
+ run["updated_at"] = now_utc_iso()
216
+ if note:
217
+ run["note"] = note
218
+ save_run(paths, run_id, run)
219
+ rprint(f"[green]Updated run {run_id} -> {status}[/green]")
220
+
221
+
222
+ @app.command("verify")
223
+ def verify(project_dir: Path | None = typer.Option(None, "--project-dir")) -> None:
224
+ paths = _paths()
225
+ target = project_dir or paths.lean_project_dir
226
+ code, output = lake_build(target)
227
+ sorry_hits = find_sorry_hits(target)
228
+ forbidden_staging_hits = _find_forbidden_staging_artifacts(paths)
229
+ log_name = datetime.now(timezone.utc).strftime("verify_%Y%m%dT%H%M%SZ.log")
230
+ log_path = paths.logs_dir / log_name
231
+ extra = ""
232
+ if sorry_hits:
233
+ extra = "\n\n== unresolved sorry locations ==\n" + "\n".join(sorry_hits)
234
+ if forbidden_staging_hits:
235
+ extra += "\n\n== forbidden artifacts under workspace/staging ==\n" + "\n".join(
236
+ forbidden_staging_hits
237
+ )
238
+ _write_log(log_path, output + extra)
239
+
240
+ if code != 0 or sorry_hits or forbidden_staging_hits:
241
+ rprint(f"[red]Verification failed.[/red] Log: {log_path}")
242
+ if sorry_hits:
243
+ rprint(f"[red]Unresolved sorries:[/red] {len(sorry_hits)}")
244
+ if forbidden_staging_hits:
245
+ rprint(
246
+ "[red]Forbidden staging artifacts:[/red] "
247
+ f"{len(forbidden_staging_hits)} (move them to ProofWorkspace/Final)"
248
+ )
249
+ raise typer.Exit(code=1)
250
+ rprint(f"[green]Verification passed.[/green] Log: {log_path}")
251
+
252
+
253
+ @app.command("smoke-test")
254
+ def smoke_test() -> None:
255
+ paths = _paths()
256
+ init(build=True)
257
+
258
+ smoke_dir = paths.lean_project_dir / PACKAGE_NAME / "Smoke"
259
+ smoke_dir.mkdir(parents=True, exist_ok=True)
260
+ smoke_file = smoke_dir / "Smoke.lean"
261
+ smoke_file.write_text(
262
+ """import Mathlib
263
+
264
+ namespace ProofWorkspace.Smoke
265
+
266
+ theorem smoke_add_comm (a b : Nat) : a + b = b + a := by
267
+ sorry
268
+
269
+ end ProofWorkspace.Smoke
270
+ """,
271
+ encoding="utf-8",
272
+ )
273
+ reindex_allproofs(paths.lean_project_dir)
274
+
275
+ run_id_label = "smoke"
276
+ start_run(
277
+ prompt_text="Fill all sorry placeholders while preserving theorem statements.",
278
+ run_label=run_id_label,
279
+ prompt_file=None,
280
+ context_file=None,
281
+ )
282
+
283
+ try:
284
+ verify(project_dir=paths.lean_project_dir)
285
+ except typer.Exit:
286
+ rprint("[green]Smoke test passed:[/green] pipeline detects unresolved `sorry` correctly.")
287
+ return
288
+ raise RuntimeError(
289
+ "Smoke test expected verification failure before proving, but verify passed."
290
+ )
291
+
292
+
293
+ def main() -> None:
294
+ app()
295
+
296
+
297
+ if __name__ == "__main__":
298
+ main()
@@ -0,0 +1,10 @@
1
+ PACKAGE_NAME = "ProofWorkspace"
2
+ LEAN_TOOLCHAIN = "leanprover/lean4:v4.28.0"
3
+ MATHLIB_HASH = "8f9d9cff6bd728b17a24e163c9402775d9e6a365"
4
+
5
+ PROOF_AGENT_ROLE = "deep-thinking model for complex mathematics"
6
+ PROOF_AGENT_NOTES = (
7
+ "Use a high-capability reasoning model from the active agent stack for theorem proving."
8
+ )
9
+ OPS_AGENT_ROLE = "smaller programming model for scripting/reporting"
10
+ OPS_AGENT_NOTES = "Use a smaller coding-oriented model for automation, logs, and status reporting."
@@ -0,0 +1,51 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+
4
+
5
+ @dataclass(frozen=True)
6
+ class Paths:
7
+ root: Path
8
+ bin_dir: Path
9
+ scripts_dir: Path
10
+ src_dir: Path
11
+ workspace_dir: Path
12
+ prompts_dir: Path
13
+ contexts_dir: Path
14
+ lean_project_dir: Path
15
+ data_dir: Path
16
+ runs_dir: Path
17
+ logs_dir: Path
18
+
19
+
20
+ def default_paths() -> Paths:
21
+ root = Path(__file__).resolve().parents[2]
22
+ workspace_dir = root / "workspace"
23
+ return Paths(
24
+ root=root,
25
+ bin_dir=root / "bin",
26
+ scripts_dir=root / "scripts",
27
+ src_dir=root / "src",
28
+ workspace_dir=workspace_dir,
29
+ prompts_dir=workspace_dir / "prompts",
30
+ contexts_dir=workspace_dir / "contexts",
31
+ lean_project_dir=workspace_dir / "lean_project",
32
+ data_dir=root / "data",
33
+ runs_dir=root / "data" / "runs",
34
+ logs_dir=root / "logs",
35
+ )
36
+
37
+
38
+ def ensure_layout(paths: Paths) -> None:
39
+ for directory in (
40
+ paths.bin_dir,
41
+ paths.scripts_dir,
42
+ paths.src_dir,
43
+ paths.workspace_dir,
44
+ paths.prompts_dir,
45
+ paths.contexts_dir,
46
+ paths.lean_project_dir,
47
+ paths.data_dir,
48
+ paths.runs_dir,
49
+ paths.logs_dir,
50
+ ):
51
+ directory.mkdir(parents=True, exist_ok=True)
@@ -0,0 +1,116 @@
1
+ import shutil
2
+ import subprocess
3
+ from pathlib import Path
4
+
5
+ from tproof.constants import PACKAGE_NAME
6
+
7
+
8
+ def run_cmd(command: list[str], cwd: Path) -> tuple[int, str]:
9
+ proc = subprocess.run(
10
+ command,
11
+ cwd=cwd,
12
+ capture_output=True,
13
+ text=True,
14
+ check=False,
15
+ )
16
+ output = (proc.stdout or "") + (proc.stderr or "")
17
+ return proc.returncode, output
18
+
19
+
20
+ def ensure_toolchain_installed(toolchain: str) -> tuple[int, str]:
21
+ return run_cmd(["elan", "toolchain", "install", toolchain], cwd=Path.cwd())
22
+
23
+
24
+ def lake_update(project_dir: Path) -> tuple[int, str]:
25
+ return run_cmd(["lake", "update"], cwd=project_dir)
26
+
27
+
28
+ def lake_build(project_dir: Path) -> tuple[int, str]:
29
+ return run_cmd(["lake", "build"], cwd=project_dir)
30
+
31
+
32
+ def seed_lean_project(project_dir: Path, lean_toolchain: str, mathlib_hash: str) -> None:
33
+ project_dir.mkdir(parents=True, exist_ok=True)
34
+ package_dir = project_dir / PACKAGE_NAME
35
+ package_dir.mkdir(parents=True, exist_ok=True)
36
+ (package_dir / "Demo").mkdir(parents=True, exist_ok=True)
37
+ (package_dir / "Final").mkdir(parents=True, exist_ok=True)
38
+ (project_dir / "lean-toolchain").write_text(f"{lean_toolchain}\n", encoding="utf-8")
39
+ (project_dir / "lakefile.lean").write_text(
40
+ f"""import Lake
41
+ open Lake DSL
42
+
43
+ package "{PACKAGE_NAME}"
44
+
45
+ require mathlib from git
46
+ "https://github.com/leanprover-community/mathlib4.git" @ "{mathlib_hash}"
47
+
48
+ @[default_target]
49
+ lean_lib "{PACKAGE_NAME}" where
50
+ """,
51
+ encoding="utf-8",
52
+ )
53
+ (project_dir / f"{PACKAGE_NAME}.lean").write_text(
54
+ f"""import {PACKAGE_NAME}.AllProofs
55
+ """,
56
+ encoding="utf-8",
57
+ )
58
+ all_proofs = project_dir / PACKAGE_NAME / "AllProofs.lean"
59
+ if not all_proofs.is_file():
60
+ all_proofs.write_text(
61
+ """/-
62
+ Auto-generated import index for proof modules.
63
+ Regenerate with: tproof reindex
64
+ -/
65
+ """,
66
+ encoding="utf-8",
67
+ )
68
+
69
+ placeholder = project_dir / PACKAGE_NAME / "Demo" / "Placeholder.lean"
70
+ if not placeholder.is_file():
71
+ placeholder.write_text(
72
+ f"""namespace {PACKAGE_NAME}.Demo
73
+
74
+ theorem placeholder : True := by
75
+ trivial
76
+
77
+ end {PACKAGE_NAME}.Demo
78
+ """,
79
+ encoding="utf-8",
80
+ )
81
+
82
+
83
+ def reindex_allproofs(project_dir: Path) -> Path:
84
+ package_dir = project_dir / PACKAGE_NAME
85
+ modules: list[str] = []
86
+ if package_dir.is_dir():
87
+ for lean_file in sorted(package_dir.rglob("*.lean")):
88
+ rel = lean_file.relative_to(project_dir).with_suffix("")
89
+ module_name = ".".join(rel.parts)
90
+ if module_name.endswith(".AllProofs"):
91
+ continue
92
+ modules.append(module_name)
93
+
94
+ all_proofs_path = package_dir / "AllProofs.lean"
95
+ lines = [
96
+ "/-",
97
+ "Auto-generated import index for proof modules.",
98
+ "Regenerate with: tproof reindex",
99
+ "-/",
100
+ "",
101
+ ]
102
+ for module in modules:
103
+ lines.append(f"import {module}")
104
+ lines.append("")
105
+ all_proofs_path.write_text("\n".join(lines), encoding="utf-8")
106
+ return all_proofs_path
107
+
108
+
109
+ def snapshot_project(source_project_dir: Path, destination_dir: Path) -> None:
110
+ if destination_dir.exists():
111
+ shutil.rmtree(destination_dir)
112
+ shutil.copytree(
113
+ source_project_dir,
114
+ destination_dir,
115
+ ignore=shutil.ignore_patterns(".lake", "build", "__pycache__", "*.olean", "*.ilean"),
116
+ )
@@ -0,0 +1,58 @@
1
+ import json
2
+ import re
3
+ import secrets
4
+ from datetime import datetime, timezone
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from tproof.layout import Paths
9
+
10
+
11
+ def now_utc_iso() -> str:
12
+ return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
13
+
14
+
15
+ def _sanitize_label(label: str) -> str:
16
+ cleaned = re.sub(r"[^a-zA-Z0-9_-]+", "-", label.strip()).strip("-")
17
+ return cleaned[:40] if cleaned else "run"
18
+
19
+
20
+ def new_run_id(label: str | None = None) -> str:
21
+ prefix = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
22
+ token = secrets.token_hex(2)
23
+ if label:
24
+ return f"{prefix}_{_sanitize_label(label)}_{token}"
25
+ return f"{prefix}_{token}"
26
+
27
+
28
+ def run_dir(paths: Paths, run_id: str) -> Path:
29
+ return paths.runs_dir / run_id
30
+
31
+
32
+ def run_file(paths: Paths, run_id: str) -> Path:
33
+ return run_dir(paths, run_id) / "run.json"
34
+
35
+
36
+ def write_json(path: Path, payload: dict[str, Any]) -> None:
37
+ path.parent.mkdir(parents=True, exist_ok=True)
38
+ path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
39
+
40
+
41
+ def read_json(path: Path) -> dict[str, Any]:
42
+ return json.loads(path.read_text(encoding="utf-8"))
43
+
44
+
45
+ def create_run(paths: Paths, run_id: str, payload: dict[str, Any]) -> None:
46
+ run_dir(paths, run_id).mkdir(parents=True, exist_ok=True)
47
+ write_json(run_file(paths, run_id), payload)
48
+
49
+
50
+ def load_run(paths: Paths, run_id: str) -> dict[str, Any]:
51
+ path = run_file(paths, run_id)
52
+ if not path.is_file():
53
+ raise FileNotFoundError(f"Unknown run id '{run_id}'. Missing file: {path}")
54
+ return read_json(path)
55
+
56
+
57
+ def save_run(paths: Paths, run_id: str, payload: dict[str, Any]) -> None:
58
+ write_json(run_file(paths, run_id), payload)
@@ -0,0 +1,94 @@
1
+ import re
2
+ from pathlib import Path
3
+
4
+ from tproof.constants import (
5
+ OPS_AGENT_NOTES,
6
+ OPS_AGENT_ROLE,
7
+ PACKAGE_NAME,
8
+ PROOF_AGENT_NOTES,
9
+ PROOF_AGENT_ROLE,
10
+ )
11
+
12
+
13
+ def read_task_prompt(prompt_file: Path | None, prompt_text: str | None) -> str:
14
+ if prompt_file and prompt_text:
15
+ raise ValueError("Pass either --prompt-file or --prompt-text, not both.")
16
+ if prompt_file:
17
+ return prompt_file.read_text(encoding="utf-8").strip()
18
+ if prompt_text:
19
+ return prompt_text.strip()
20
+ raise ValueError("Pass one of --prompt-file or --prompt-text.")
21
+
22
+
23
+ def discover_project_modules(project_dir: Path) -> list[str]:
24
+ package_dir = project_dir / PACKAGE_NAME
25
+ if not package_dir.is_dir():
26
+ return []
27
+
28
+ modules: list[str] = []
29
+ for lean_file in sorted(package_dir.rglob("*.lean")):
30
+ rel = lean_file.relative_to(project_dir).with_suffix("")
31
+ module_name = ".".join(rel.parts)
32
+ if module_name.endswith(".AllProofs"):
33
+ continue
34
+ modules.append(module_name)
35
+ return modules
36
+
37
+
38
+ def find_sorry_hits(project_dir: Path) -> list[str]:
39
+ word = re.compile(r"\bsorry\b")
40
+ hits: list[str] = []
41
+ package_dir = project_dir / PACKAGE_NAME
42
+ if not package_dir.is_dir():
43
+ return hits
44
+
45
+ for lean_file in sorted(package_dir.rglob("*.lean")):
46
+ rel = lean_file.relative_to(project_dir)
47
+ lines = lean_file.read_text(encoding="utf-8").splitlines()
48
+ for line_number, line in enumerate(lines, start=1):
49
+ if word.search(line):
50
+ hits.append(f"{rel}:{line_number}")
51
+ return hits
52
+
53
+
54
+ def build_run_brief(
55
+ *,
56
+ run_id: str,
57
+ task_prompt: str,
58
+ known_modules: list[str],
59
+ sorry_hits: list[str],
60
+ context_text: str | None,
61
+ ) -> str:
62
+ module_block = "\n".join(f"- {module}" for module in known_modules) or "- (none)"
63
+ sorry_block = "\n".join(f"- {hit}" for hit in sorry_hits) or "- (none)"
64
+ context_block = context_text.strip() if context_text else "(none)"
65
+
66
+ return f"""# Run Brief: {run_id}
67
+
68
+ ## Task
69
+ {task_prompt}
70
+
71
+ ## Context
72
+ {context_block}
73
+
74
+ ## Known Modules
75
+ {module_block}
76
+
77
+ ## Outstanding `sorry` Locations
78
+ {sorry_block}
79
+
80
+ ## Model Routing (Required)
81
+ 1. Mathematical proving worker:
82
+ - Profile: `{PROOF_AGENT_ROLE}`
83
+ - Guidance: {PROOF_AGENT_NOTES}
84
+ - Scope: theorem proving, proof search, lemma decomposition, difficult Lean terms.
85
+ 2. Programming/ops/reporting worker:
86
+ - Profile: `{OPS_AGENT_ROLE}`
87
+ - Guidance: {OPS_AGENT_NOTES}
88
+ - Scope: scripts, repo hygiene, status summaries, verification logs, run metadata updates.
89
+
90
+ ## Execution Pattern
91
+ 1. Ask the proving worker to edit files under `workspace/lean_project/`.
92
+ 2. Ask the ops worker to run `uv run -m tproof.cli verify` and summarize failures.
93
+ 3. Iterate until verification passes with no `sorry`.
94
+ """
@@ -0,0 +1,3 @@
1
+ Fill all sorry placeholders in this Lean project.
2
+ Keep theorem statements unchanged.
3
+ Prefer using existing lemmas from this project and Mathlib.