forked from Keelrleek/Harbor
Harbor as of 2026-10-05: the coordination hub for the Open Sea's decomp team
Messages, threads, acknowledgements, claims, bands, tasks, review items and stop flags for a team of AI agents; the chat page and board; host hooks and MCP server; autopilot; tools/watch.py. Put under version control after the weekend's changes so they have history and can be rolled back. Local only.
This commit is contained in:
commit
dbfd1720c5
43 files changed
+12440
No files matched your search
+122
@@ -0,0 +1,122 @@
|
||||
"""Small shared helpers: times, scopes, the compressed-text detector and Harbor's error type."""
|
||||
import datetime
|
||||
import fnmatch
|
||||
import re
|
||||
import time
|
||||
|
||||
# Run-together words such as "full222", "stub23HarnessBug" or "correctOffsetsWereWrong". Hex tokens (commit hashes,
|
||||
# addresses) and ordinary identifiers (PascalCase names, g_snake_case globals) are not counted.
|
||||
SQUASHED = re.compile(r"[A-Za-z]{3,}\d{2,}[A-Za-z]*|\b[a-z]{3,}[A-Z][a-z]{2,}[A-Z][a-z]{2,}[A-Za-z]*\b")
|
||||
HEXLIKE = re.compile(r"(?:0x)?[0-9A-Fa-f]+")
|
||||
PRIORITIES = {"urgent": 0, "high": 1, "normal": 2, "low": 3}
|
||||
PRIORITY_NAMES = {v: k for k, v in PRIORITIES.items()}
|
||||
|
||||
|
||||
class HarborError(Exception):
|
||||
"""A refused operation; the message says why and what to do instead."""
|
||||
|
||||
|
||||
def squashed_tokens(text):
|
||||
found = []
|
||||
for token in re.split(r"[\s,;:()\[\]{}<>\"'`/|=+]+", text):
|
||||
token = token.strip(".!?-")
|
||||
if not token or HEXLIKE.fullmatch(token) or "_" in token:
|
||||
continue
|
||||
if SQUASHED.search(token):
|
||||
found.append(token)
|
||||
return found
|
||||
|
||||
|
||||
def utc(ts=None):
|
||||
return datetime.datetime.fromtimestamp(time.time() if ts is None else ts, datetime.timezone.utc)
|
||||
|
||||
|
||||
def iso(dt):
|
||||
return dt.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
|
||||
def parse_iso(text):
|
||||
return datetime.datetime.strptime(text, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=datetime.timezone.utc)
|
||||
|
||||
|
||||
def _span(text):
|
||||
m = re.fullmatch(r"\s*(\d+(?:\.\d+)?)\s*([mhd])\s*", str(text))
|
||||
if not m:
|
||||
return None
|
||||
return datetime.timedelta(seconds=float(m.group(1)) * {"m": 60, "h": 3600, "d": 86400}[m.group(2)])
|
||||
|
||||
|
||||
def parse_since(text, now):
|
||||
"""'8h', '90m', '2d' or an ISO time -> ISO time in the past."""
|
||||
span = _span(text)
|
||||
if span is not None:
|
||||
return iso(now - span)
|
||||
try:
|
||||
return iso(parse_iso(str(text).strip()))
|
||||
except ValueError:
|
||||
raise HarborError(f"since {text!r}: use 8h, 90m, 2d or 2026-10-02T13:25:00Z")
|
||||
|
||||
|
||||
def parse_until(text, now):
|
||||
"""'30m', '2h', '1d' or an ISO time -> ISO time in the future (due dates, 'away until')."""
|
||||
span = _span(text)
|
||||
if span is not None:
|
||||
return iso(now + span)
|
||||
try:
|
||||
return iso(parse_iso(str(text).strip()))
|
||||
except ValueError:
|
||||
raise HarborError(f"time {text!r}: use 30m, 2h, 1d or 2026-10-03T13:00:00Z")
|
||||
|
||||
|
||||
def parse_priority(value):
|
||||
if isinstance(value, int) and value in PRIORITY_NAMES:
|
||||
return value
|
||||
key = str(value or "normal").lower()
|
||||
if key not in PRIORITIES:
|
||||
raise HarborError(f"priority must be one of {', '.join(PRIORITIES)}")
|
||||
return PRIORITIES[key]
|
||||
|
||||
|
||||
def parse_scope(scope):
|
||||
"""'fn:0x529350' / '0x529350' / 'range:0x440000-0x47FFFF' -> (canonical, lo, hi); others keep lo/hi None."""
|
||||
text = scope.strip()
|
||||
m = re.fullmatch(r"(?:fn:)?(0x[0-9A-Fa-f]+)", text)
|
||||
if m:
|
||||
value = int(m.group(1), 16)
|
||||
return f"fn:0x{value:06X}", value, value
|
||||
m = re.fullmatch(r"range:(0x[0-9A-Fa-f]+)-(0x[0-9A-Fa-f]+)", text)
|
||||
if m:
|
||||
lo, hi = int(m.group(1), 16), int(m.group(2), 16)
|
||||
if lo > hi:
|
||||
raise HarborError(f"range {text}: start is after end")
|
||||
return f"range:0x{lo:06X}-0x{hi:06X}", lo, hi
|
||||
m = re.fullmatch(r"(file|name|topic|task):(.+)", text)
|
||||
if m:
|
||||
value = m.group(2).strip()
|
||||
f = re.match(r"(?:.*[/\\])?([0-9A-Fa-f]{6})_[^/\\]*\.c$", value) if m.group(1) == "file" else None
|
||||
if f: # a function's file: it stands for that address
|
||||
address = int(f.group(1), 16)
|
||||
return f"file:{value}", address, address
|
||||
return f"{m.group(1)}:{value}", None, None
|
||||
raise HarborError(f"scope {scope!r}: use fn:0xADDR, range:0xLO-0xHI, name:Symbol, file:path, task:text or topic:text")
|
||||
|
||||
|
||||
def scopes_overlap(a, b):
|
||||
"""a, b: (scope, lo, hi)."""
|
||||
if a[1] is not None and b[1] is not None:
|
||||
return a[1] <= b[2] and b[1] <= a[2]
|
||||
if a[1] is not None or b[1] is not None:
|
||||
return False
|
||||
ka, va = a[0].split(":", 1)
|
||||
kb, vb = b[0].split(":", 1)
|
||||
if ka != kb:
|
||||
return False
|
||||
if ka == "file":
|
||||
va, vb = va.replace("\\", "/").lower(), vb.replace("\\", "/").lower()
|
||||
return va == vb or fnmatch.fnmatch(va, vb) or fnmatch.fnmatch(vb, va)
|
||||
return va.lower() == vb.lower()
|
||||
|
||||
|
||||
def first_line(text, limit=160):
|
||||
line = (text or "").strip().split("\n", 1)[0]
|
||||
return line if len(line) <= limit else line[:limit - 1] + "…"
|
||||
Reference in new issue
Block a user