Files
rune/scripts/gen_sleep_time_truth.py
T
Nick 617d2d8ece feat(sleep_time): close PARTIAL gaps — dichotomy wave
Port the 18 missing sleep_time.py functions name-preserving to
src/go/sleep-time (kernels.go + kernel_dispatch.go) under the pure/IO
split law: deterministic cores in Go, IO (db, files, subprocess, http,
flock) behind injectable seams. 38-case branch truth (truth_gaps.json)
frozen from the live oracle with a pinned clock and deep-copied inputs,
replayed as a hard Go gate (truth_gaps_test.go) — 100% live parity on
the new fns. Wire live RUNE_SLEEP_TIME_CORE delegation in the facade
(_go_kernel JSON surface, 8 kernel call sites) with the python pin and
binary-missing fallbacks; both modes green (pytest, both ways). Go fn
coverage 8/26 -> 26/27 (the 27th is _go_kernel, the delegation glue
itself).
2026-10-13 06:49:19 +01:00

560 lines
24 KiB
Python

#!/usr/bin/env python3
"""gen_sleep_time_truth.py — factory batch 11 + factory-gap-sleep truth
battery (live oracle). Emits:
src/go/sleep-time/truth_transcript.json (batch 11: extract_todos/transcript)
src/go/sleep-time/truth_gaps.json (gap-closing wave: the 18 missing
functions, every branch, pinned clock, deep-copied inputs)
"""
import copy
import datetime as _dt
import io
import json
import os
import sqlite3
import sys
import tempfile
import types
import urllib.request
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
sys.path.insert(0, str(REPO / "src" / "rune"))
import sleep_time as st # noqa: E402
def make_parts_db(db_path):
db = sqlite3.connect(db_path)
db.execute("CREATE TABLE part (session_id TEXT, data TEXT, time_created TEXT)")
rows = [
("s1", '{"tool": "todowrite", "state": {"input": {"todos": [{"content": "first", "status": "completed", "priority": "high"}]}}}', "2026-10-07T10:00:00"),
("s1", '{"tool": "bash", "state": {"input": {"command": "ls"}}}', "2026-10-07T10:01:00"),
("s1", '{"tool": "todowrite", "state": {"input": {"todos": [{"content": "second", "status": "in_progress", "priority": "med"}, {"content": "third", "status": "pending", "priority": "low"}]}}}', "2026-10-07T10:02:00"),
("s1", '{"tool": "todowrite", "state": {"input": {"todos": []}}}', "2026-10-07T10:03:00"), # empty — skipped
("s1", '{"tool": "read_file", "state": {"input": {}}}', "2026-10-07T10:04:00"), # non-todowrite
("s2", '{"tool": "todowrite", "state": {"input": {"todos": [{"content": "other session", "status": "pending", "priority": ""}]}}}', "2026-10-07T11:00:00"),
]
db.executemany("INSERT INTO part VALUES (?,?,?)", rows)
db.commit()
return db
def main():
tmp = Path(tempfile.mkdtemp(prefix="st-truth-"))
db = make_parts_db(str(tmp / "p.db"))
cases = []
# extract_todos for s1 (ORDER BY time ASC → latest = the 10:02 todos)
rows = [{"data": r[0]} for r in db.execute(
"SELECT data FROM part WHERE session_id='s1' ORDER BY time_created ASC").fetchall()]
cases.append({"fn": "extract_todos", "name": "s1_latest",
"input": {"rows": rows},
"expected": {"todos": st.extract_todos(
_FakeConn(db, "s1"), "s1")}})
rows2 = [{"data": r[0]} for r in db.execute(
"SELECT data FROM part WHERE session_id='s2' ORDER BY time_created ASC").fetchall()]
cases.append({"fn": "extract_todos", "name": "s2",
"input": {"rows": rows2},
"expected": {"todos": st.extract_todos(
_FakeConn(db, "s2"), "s2")}})
rows3 = [{"data": r[0]} for r in db.execute(
"SELECT data FROM part WHERE session_id='nope' ORDER BY time_created ASC").fetchall()]
cases.append({"fn": "extract_todos", "name": "empty_session",
"input": {"rows": rows3},
"expected": {"todos": st.extract_todos(
_FakeConn(db, "nope"), "nope")}})
# write_transcript byte format
st.TRANSCRIPTS_DIR = str(tmp / "transcripts")
Path(st.TRANSCRIPTS_DIR).mkdir(parents=True, exist_ok=True)
todos = [{"content": "write the thing", "status": "completed", "priority": "high"},
{"content": "review it", "status": "in_progress", "priority": ""}]
parts = [("user", "do the task"), ("assistant", "done, verified")]
p1 = st.write_transcript("s1", "My Session: v2!", parts, todos)
cases.append({"fn": "transcript", "name": "full_shape",
"input": {},
"expected": {"content": Path(p1).read_text(),
"filename_stem": Path(p1).name}})
p2 = st.write_transcript("s2", "no todos", parts, None)
cases.append({"fn": "transcript", "name": "no_todos",
"input": {},
"expected": {"content": Path(p2).read_text()}})
out = {"oracle": "sleep_time.extract_todos/write_transcript (live)",
"generated": "2026-10-08", "cases": cases}
(REPO / "src/go/sleep-time" / "truth_transcript.json").write_text(
json.dumps(out, indent=1) + "\n")
print(f"wrote {len(cases)} cases")
# ---------------------------------------------------------------------------
# factory-gap-sleep: branch truth for the 18 gap functions (live oracle).
# Pinned clock: st.datetime.now / st.time.time are frozen; now is always an
# explicit kernel argument. Inputs are deep-copied before touching the oracle.
# ---------------------------------------------------------------------------
PINNED_DT = _dt.datetime(2026, 10, 12, 3, 4, 5, tzinfo=_dt.timezone.utc)
PINNED_SEC = 1792472645.25
class _PinnedDatetime(_dt.datetime):
@classmethod
def now(cls, tz=None):
if tz is not None:
return PINNED_DT.astimezone(tz)
return PINNED_DT.replace(tzinfo=None)
def _capture_urlopen(captured, response_body):
def fake_urlopen(req, timeout=None):
captured.append(json.loads(req.data.decode()))
return io.BytesIO(response_body)
return fake_urlopen
def gen_gaps():
cases = []
tmp = Path(tempfile.mkdtemp(prefix="st-gaps-"))
# ── _is_transient: first marker, last marker, mixed case, clean, empty ──
for name, text, want in [
("first_marker", "the health status page was checked", True),
("last_marker", "nothing here about verification of the fix", True),
("mixed_case", "TODO LIST is long", True),
("clean", "the user prefers concise answers about rust", False),
("empty", "", False),
]:
got = st._is_transient(copy.deepcopy(text))
cases.append({"fn": "is_transient", "name": name,
"input": {"text": text},
"expected": {"transient": got}})
assert got == want, (name, got)
# ── retain_to_store ──
r = st.retain_to_store(copy.deepcopy("doc-1"), copy.deepcopy("body text"))
cases.append({"fn": "retain_to_store", "name": "constant",
"input": {"document_id": "doc-1", "content": "body text"},
"expected": {"result": r}})
# ── handle_hook ──
r1 = st.handle_hook("on_schedule", copy.deepcopy({"k": 1}))
r2 = st.handle_hook("on_tick")
cases.append({"fn": "handle_hook", "name": "with_data",
"input": {"event": "on_schedule", "data": {"k": 1}},
"expected": r1})
cases.append({"fn": "handle_hook", "name": "no_data",
"input": {"event": "on_tick"}, "expected": r2})
# ── should_defer (heavy-job probe patched: IO stays on the caller) ──
orig_heavy = st._heavy_job_running
st._heavy_job_running = lambda: True
try:
d1, r1s = st.should_defer()
finally:
st._heavy_job_running = orig_heavy
st._heavy_job_running = lambda: False
try:
d2, r2s = st.should_defer()
finally:
st._heavy_job_running = orig_heavy
cases.append({"fn": "should_defer", "name": "heavy_running",
"input": {"heavy_running": True},
"expected": {"defer": d1, "reason": r1s}})
cases.append({"fn": "should_defer", "name": "clear",
"input": {"heavy_running": False},
"expected": {"defer": d2, "reason": r2s}})
# ── write_status: pinned clock, phase/note present and None ──
orig_status = st.STATUS_FILE
st.STATUS_FILE = str(tmp / "status.json")
try:
old_datetime = st.datetime
st.datetime = _PinnedDatetime
try:
st.write_status("mining", "extract", "title one")
body1 = json.loads(Path(st.STATUS_FILE).read_text())
st.write_status("idle", None, None)
body2 = json.loads(Path(st.STATUS_FILE).read_text())
finally:
st.datetime = old_datetime
finally:
st.STATUS_FILE = orig_status
now_ms = PINNED_DT.timestamp() * 1000
cases.append({"fn": "status_body", "name": "full",
"input": {"now_ms": now_ms, "state": "mining",
"phase": "extract", "note": "title one"},
"expected": body1})
cases.append({"fn": "status_body", "name": "nulls",
"input": {"now_ms": now_ms, "state": "idle",
"phase": None, "note": None},
"expected": body2})
# ── write_reflection: append format + filename ──
orig_reflect = st.REFLECT_DIR
st.REFLECT_DIR = str(tmp / "reflect")
os.makedirs(st.REFLECT_DIR, exist_ok=True)
try:
lines = ["## Sleep-time run x", "- session: 3 parts", ""]
p1 = st.write_reflection("2026-10-12", copy.deepcopy(lines))
content1 = Path(p1).read_text()
p2 = st.write_reflection("2026-10-12", ["second run"])
content2 = Path(p2).read_text()
finally:
st.REFLECT_DIR = orig_reflect
cases.append({"fn": "write_reflection", "name": "append_lines",
"input": {"date": "2026-10-12", "lines": lines},
"expected": {"content": content1, "filename": Path(p1).name}})
cases.append({"fn": "write_reflection", "name": "second_append",
"input": {"date": "2026-10-12", "lines": ["second run"]},
"expected": {"content": content2, "filename": Path(p2).name}})
# ── fetch_sessions: by-id branch SQL + the since cutoff (pinned clock) ──
class _CaptureConn:
def __init__(self):
self.calls = []
def execute(self, sql, params):
self.calls.append((sql, list(params)))
class Cur:
def fetchall(self):
return [("s1", "t", "d", 1, 2)]
return Cur()
cc = _CaptureConn()
old_datetime = st.datetime
st.datetime = _PinnedDatetime
try:
rows = st.fetch_sessions(cc, hours=24)
finally:
st.datetime = old_datetime
sql, params = cc.calls[0]
cases.append({"fn": "fetch_sessions_window", "name": "since_branch",
"input": {"now_ms": now_ms, "hours": 24,
"sql_spaceless": " ".join(sql.split())},
"expected": {"since": params[0], "rows": [list(rows[0])]}})
cc2 = _CaptureConn()
st.fetch_sessions(cc2, session_id="abc-123")
sql2, params2 = cc2.calls[0]
cases.append({"fn": "fetch_sessions_by_id", "name": "id_branch",
"input": {"session_id": "abc-123",
"sql": sql2},
"expected": {"params": params2}})
# ── reflect_local: default query, payload shape, trim 800, error branch ──
captured = []
long_text = "x" * 900
orig_urlopen = urllib.request.urlopen
urllib.request.urlopen = _capture_urlopen(
captured, json.dumps({"response": " " + long_text + "\n"}).encode())
try:
out = st.reflect_local()
finally:
urllib.request.urlopen = orig_urlopen
payload1 = captured[0]
cases.append({"fn": "reflect_default_query", "name": "query_none",
"input": {},
"expected": {"query": payload1["prompt"]}})
cases.append({"fn": "reflect_payload", "name": "default",
"input": {"model": payload1["model"], "num_ctx": payload1["options"]["num_ctx"]},
"expected": {"payload": payload1, "output": out}})
captured.clear()
urllib.request.urlopen = _capture_urlopen(
captured, json.dumps({"response": "no response key here"}).replace("no response key here", "").encode() or b"{}")
urllib.request.urlopen = _capture_urlopen(captured, b"{}")
try:
out2 = st.reflect_local("explicit query?")
finally:
urllib.request.urlopen = orig_urlopen
cases.append({"fn": "reflect_payload", "name": "empty_response",
"input": {"model": captured[0]["model"], "query": "explicit query?",
"num_ctx": captured[0]["options"]["num_ctx"]},
"expected": {"payload": captured[0], "output": out2}})
captured.clear()
orig_urlopen = urllib.request.urlopen
urllib.request.urlopen = _capture_urlopen(captured, b"not json at all")
try:
out3 = st.reflect_local("q")
finally:
urllib.request.urlopen = orig_urlopen
cases.append({"fn": "reflect_error", "name": "bad_json",
"input": {},
"expected": {"prefix": "[reflect failed: ", "output": out3}})
# ── _recent_interaction_material: branch-crafted transcripts ──
texts = {
"a_normal.md": ("# t\n\n### user\n\nplease look at the deploy logs "
"carefully today\n\n### assistant\n\nI checked the "
"deploy logs and found the crash quickly\n"),
"b_short.md": "### user\n\nshort\n\n### assistant\n\nok\n",
"c_mempalace.md": "### user\n\nthe mempalace archive wing is huge "
"and full of interesting material\n",
"d_tool_prefix.md": "### assistant\n\ntool output was captured here "
"and it is long enough to matter\n",
"e_long.md": "### user\n\n" + ("y" * 500) + "\n",
}
tdir = tmp / "transcripts"
tdir.mkdir(parents=True)
for i, (fn, txt) in enumerate(sorted(texts.items())):
(tdir / fn).write_text(txt)
# explicit mtimes: creation order ascending -> mtime-desc = reverse
os.utime(tdir / fn, (PINNED_SEC - 100 - i, PINNED_SEC - 100 - i))
orig_transcripts = st.TRANSCRIPTS_DIR
orig_env = os.environ.get("MEMORY_REFLECT_TRANSCRIPTS")
st.TRANSCRIPTS_DIR = str(tdir)
try:
os.environ["MEMORY_REFLECT_TRANSCRIPTS"] = "6"
m_all = st._recent_interaction_material(5000)
m_cap = st._recent_interaction_material(80)
os.environ["MEMORY_REFLECT_TRANSCRIPTS"] = "2"
m_two = st._recent_interaction_material(5000)
finally:
st.TRANSCRIPTS_DIR = orig_transcripts
if orig_env is None:
os.environ.pop("MEMORY_REFLECT_TRANSCRIPTS", None)
else:
os.environ["MEMORY_REFLECT_TRANSCRIPTS"] = orig_env
# the oracle orders files mtime-desc — a_normal carries the newest mtime,
# so that order is the plain sorted() one; the kernel input uses the same
tlist = [{"name": n, "text": texts[n]} for n in sorted(texts)]
cases.append({"fn": "interaction_material", "name": "all_files",
"input": {"texts": copy.deepcopy(tlist), "n": 6, "max_chars": 5000},
"expected": {"material": m_all}})
cases.append({"fn": "interaction_material", "name": "max_chars_cap",
"input": {"texts": copy.deepcopy(tlist), "n": 6, "max_chars": 80},
"expected": {"material": m_cap}})
cases.append({"fn": "interaction_material", "name": "n_cap",
"input": {"texts": copy.deepcopy(tlist), "n": 2, "max_chars": 5000},
"expected": {"material": m_two}})
# ── _ctx_budget: the num_ctx pass-through seam (registry output frozen) ──
for model in ("qwen2.5:3b", "totally-unknown-model"):
budget = st._ctx_budget(model)
cases.append({"fn": "ctx_budget", "name": model.replace(".", "_").replace("-", "_"),
"input": {"model_id": model, "num_ctx": budget},
"expected": {"num_ctx": budget}})
# ── _active_window_seconds: real ro-sqlite, pinned clock ──
dbp = tmp / "oc.db"
db = sqlite3.connect(str(dbp))
db.execute("CREATE TABLE message (time_created INTEGER)")
db.execute("INSERT INTO message VALUES (1792472000000)")
db.commit()
db.close()
orig_db, orig_time_mod = st.DB, st.time
st.DB = str(dbp)
st.time = types.SimpleNamespace(time=lambda: PINNED_SEC)
try:
secs = st._active_window_seconds()
finally:
st.DB, st.time = orig_db, orig_time_mod
cases.append({"fn": "active_window", "name": "has_activity",
"input": {"last_ms": 1792472000000, "now_ms": PINNED_SEC * 1000},
"expected": {"seconds": secs}})
dbp2 = tmp / "oc2.db"
db = sqlite3.connect(str(dbp2))
db.execute("CREATE TABLE message (time_created INTEGER)")
db.commit()
db.close()
st.DB = str(dbp2)
st.time = types.SimpleNamespace(time=lambda: PINNED_SEC)
try:
secs2 = st._active_window_seconds()
finally:
st.DB, st.time = orig_db, orig_time_mod
assert secs2 == float("inf")
cases.append({"fn": "active_window", "name": "idle_forever",
"input": {"last_ms": None, "now_ms": PINNED_SEC * 1000},
"expected": {"seconds": None}})
# ── _heavy_job_running: pgrep stdout present/empty ──
orig_run = st.subprocess.run
st.subprocess.run = lambda *a, **k: types.SimpleNamespace(stdout="1234 pgrep target\n")
try:
h1 = st._heavy_job_running()
st.subprocess.run = lambda *a, **k: types.SimpleNamespace(stdout=" \n")
h2 = st._heavy_job_running()
finally:
st.subprocess.run = orig_run
cases.append({"fn": "heavy_job_running", "name": "found",
"input": {"pgrep_stdout": "1234 pgrep target\n"},
"expected": {"running": h1}})
cases.append({"fn": "heavy_job_running", "name": "blank",
"input": {"pgrep_stdout": " \n"},
"expected": {"running": h2}})
# ── curator_extract_evidence: success + degrade ──
orig_lm = sys.modules.get("local_memory")
ev = [{"fact": "user prefers rust", "target": "operating", "strength": 3}]
sys.modules["local_memory"] = types.SimpleNamespace(
evidence_items=lambda: copy.deepcopy(ev))
try:
items1 = st.curator_extract_evidence()
finally:
if orig_lm is not None:
sys.modules["local_memory"] = orig_lm
else:
del sys.modules["local_memory"]
sys.modules["local_memory"] = types.SimpleNamespace(
evidence_items=lambda: (_ for _ in ()).throw(RuntimeError("boom")))
try:
items2 = st.curator_extract_evidence()
finally:
if orig_lm is not None:
sys.modules["local_memory"] = orig_lm
else:
del sys.modules["local_memory"]
cases.append({"fn": "curator_extract_evidence", "name": "success",
"input": {"items": copy.deepcopy(ev)},
"expected": {"items": items1}})
cases.append({"fn": "curator_extract_evidence", "name": "degrade",
"input": {"error": "boom"},
"expected": {"items": items2}})
# ── curator_run: empty short-circuit, apply cmd, dry-run cmd ──
s0, f0 = st.curator_run([], apply=True)
cases.append({"fn": "curator_run", "name": "empty",
"input": {"items": [], "apply": True},
"expected": {"summary": s0, "ev_file": f0}})
runs = []
def fake_run(cmd, capture_output=None, text=None, timeout=None):
runs.append(list(cmd))
return types.SimpleNamespace(stdout="curator ok output\n", stderr="")
orig_run = st.subprocess.run
st.subprocess.run = fake_run
ev_items = [{"fact": "durable fact one", "strength": 2}]
try:
s1, f1 = st.curator_run(copy.deepcopy(ev_items), apply=True)
s2, f2 = st.curator_run(copy.deepcopy(ev_items), apply=False)
finally:
st.subprocess.run = orig_run
cases.append({"fn": "curator_run", "name": "apply_cmd",
"input": {"items": ev_items, "apply": True,
"python_bin": runs[0][0], "curator_path": runs[0][1],
"stdout": "curator ok output\n", "stderr": ""},
"expected": {"summary": s1, "ev_file_suffix": os.path.basename(f1), "cmd": runs[0]}})
cases.append({"fn": "curator_run", "name": "dry_run_cmd",
"input": {"items": ev_items, "apply": False,
"python_bin": runs[1][0], "curator_path": runs[1][1],
"stdout": "curator ok output\n", "stderr": ""},
"expected": {"summary": s2, "ev_file_suffix": os.path.basename(f2), "cmd": runs[1]}})
# ── run_abstraction: window selection, short-file skip, last-5, parse ──
atmp = Path(tempfile.mkdtemp(prefix="st-gaps-abs-"))
adir = atmp / "transcripts"
adir.mkdir(parents=True)
files = {}
for i in range(7):
name = f"t{i}.md"
content = ("z" * 250) + f" number {i}"
(adir / name).write_text(content)
files[name] = content
(adir / "old.md").write_text("z" * 250)
os.utime(adir / "old.md", (PINNED_SEC - 100000, PINNED_SEC - 100000))
(adir / "short.md").write_text("tiny")
for i in range(7):
os.utime(adir / f"t{i}.md", (PINNED_SEC - 100 - i, PINNED_SEC - 100 - i))
os.utime(adir / "short.md", (PINNED_SEC - 90, PINNED_SEC - 90))
extraction = {}
def fake_abs_run(cmd, capture_output=None, text=None, timeout=None):
src = cmd[cmd.index("--source") + 1]
extraction[src] = 10 - int(src.split(".")[0][1:])
return types.SimpleNamespace(
stdout=json.dumps({"extracted": extraction[src]}), returncode=0)
orig_mem, orig_time_mod, orig_run = st.MEM_DIR, st.time, st.subprocess.run
st.MEM_DIR = str(atmp)
st.time = types.SimpleNamespace(time=lambda: PINNED_SEC)
st.subprocess.run = fake_abs_run
try:
total = st.run_abstraction()
finally:
st.MEM_DIR, st.time, st.subprocess.run = orig_mem, orig_time_mod, orig_run
recent_expected = [f"t{i}.md" for i in range(7)]
cases.append({"fn": "run_abstraction", "name": "window_and_cap",
"input": {
"entries": [
{"name": "old.md", "mtime": PINNED_SEC - 100000},
{"name": "short.md", "mtime": PINNED_SEC - 90},
{"name": "short2.md", "mtime": PINNED_SEC - 95},
] + [{"name": f"t{i}.md", "mtime": PINNED_SEC - 100 - i} for i in range(7)],
"contents": dict(
[(f"t{i}.md", files[f"t{i}.md"]) for i in range(7)]
+ [("old.md", "z" * 250), ("short.md", "tiny"),
("short2.md", "s" * 210)],
),
"extractions": {f"t{i}.md": 10 - i for i in range(7)},
"now_sec": PINNED_SEC,
},
"expected": {"total": total}})
# ── _ensure_dirs ──
# relative names — the replay joins them to its own base (the frozen
# expectation is just "all three exist after the call")
dirs = ["reflect", "transcripts", "index"]
abs_dirs = [str(tmp / d) for d in dirs]
orig_dirs = (st.REFLECT_DIR, st.TRANSCRIPTS_DIR, st.EVIDENCE_DIR)
st.REFLECT_DIR, st.TRANSCRIPTS_DIR, st.EVIDENCE_DIR = abs_dirs
try:
st._ensure_dirs()
finally:
st.REFLECT_DIR, st.TRANSCRIPTS_DIR, st.EVIDENCE_DIR = orig_dirs
cases.append({"fn": "ensure_dirs", "name": "creates_all",
"input": {"dirs": dirs},
"expected": {"exists": [os.path.isdir(d) for d in abs_dirs]}})
# ── acquire_single_lock: free then contended ──
orig_mem = st.MEM_DIR
st.MEM_DIR = str(tmp / "mem")
try:
h = st.acquire_single_lock()
first = h is not None
second = st.acquire_single_lock() is not None
finally:
st.MEM_DIR = orig_mem
cases.append({"fn": "acquire_single_lock", "name": "free_then_contended",
"input": {},
"expected": {"first": first, "second": second}})
assert first and not second
# ── db_connect: read-only DSN ──
cases.append({"fn": "db_connect_dsn", "name": "readonly",
"input": {"db_path": "/data/opencode.db"},
"expected": {"dsn": f"file:{st.DB if False else '/data/opencode.db'}?mode=ro"}})
out = {"oracle": "sleep_time gap fns (live)",
"generated": "2026-10-13", "cases": cases}
(REPO / "src/go/sleep-time" / "truth_gaps.json").write_text(
json.dumps(out, indent=1) + "\n")
print(f"wrote {len(cases)} gap cases")
class _FakeConn:
def __init__(self, db, session_id):
self.db = db
self.session_id = session_id
def execute(self, sql, params):
# replicate the oracle's query (session-filtered, time-ordered)
class Cur:
def __init__(self, rows):
self.rows = rows
def fetchall(self):
return self.rows
return Cur(self.db.execute(sql, params).fetchall())
if __name__ == "__main__":
main()
gen_gaps()