Signups count by the venture's name, not only its slug (#159)
ci / test (push) Successful in 35s

Co-authored-by: teahubxyz <boring+teahub@agentmail.to>
This commit was merged in pull request #159.
This commit is contained in:
teahubxyz authored and time committed 2026-10-19 00:10:37 +00:00
1 parent ef00a80f7f
commit 3d1865ecb9
3 files changed
+19 -2

No files matched your search

+7
View File
@@ -1029,6 +1029,13 @@ assert gv.count_signups(w79)[0] == 3, "a 👍 on the signal issue counts; a hear
assert gv.signup_for(f79.rows[1], w79) and not gv.signup_for(f79.rows[4], w79) and not gv.signup_for(f79.rows[5], w79)
# the field wins over the title: a form answered for another venture never counts here, whatever its title
assert not gv.signup_for({"title": "Early access: Star checker", "body": "### Venture\n\nother\n\n### Plan\n\nFree\n\n### What would you use it for?\n\nx"}, w79)
# the field is typed by a person: the name, any spelling of the slug, the home folder or an old slug all count
form79 = lambda v: {"title": "x", "body": f"### Venture\n\n{v}\n\n### Plan\n\nFree\n\n### What would you use it for?\n\nx"}
assert all(gv.signup_for(form79(v), w79) for v in ("Star Checker", "star checker!", "STAR-CHECKER", "starchecker"))
assert gv.signup_for(form79("Cartridge"), {"slug": "box-art-night", "name": "Cartridge"})
assert gv.signup_for(form79("cartridge"), {"slug": "box-art-night", "name": "Box", "home": "cartridge"})
assert gv.signup_for(form79("deck preview"), {"slug": "mdecks", "name": ".mdeck", "aliases": ["deck-preview"]})
assert not gv.signup_for(form79("Star"), w79) and not gv.signup_for(form79("!!"), w79)
# the helpers the port leans on: removing a label goes by id; the browser pass clones with the token like every worker
gv.tm.unlabel(5, "needs-human")
assert f79.calls[-1][:2] == ("DELETE", "/repos/o/r/issues/5/labels/103"), f79.calls[-1]
+11 -2
View File
@@ -1277,14 +1277,23 @@ def from_form(body: str) -> bool:
return all(re.search(rf"^###\s*{re.escape(label)}\s*$", body or "", re.M) for label in FIELDS.values())
def _key(s) -> str:
return re.sub(r"[^a-z0-9]+", "", _s(s).lower())
def names_of(w: dict) -> set[str]:
"""Every way a person may name a venture in the form: its slug, its name, its home folder, its old slugs."""
return {k for k in map(_key, (w.get("slug"), w.get("name"), w.get("home"), *(w.get("aliases") or []))) if k}
def signup_for(i: dict, w: dict) -> bool:
"""Is this early-access issue for venture `w`? The form's Venture field, or, when that was left empty, the title the
join link prefilled. The title counts only on a body the form wrote: an issue someone typed by hand with the same
title is not a signup. Either way it is one account, counted once, like a 👍 on the signal issue."""
body = i.get("body") or ""
f = form_fields(body)
if f["venture"].strip():
return f["venture"].strip().lower() == w["slug"]
if f["venture"].strip(): # typed by a person: "Cartridge", "Loot Drop" and "loot-drop" all count
return _key(f["venture"]) in names_of(w)
return from_form(body) and _s(i.get("title")).lower() == f"early access: {_s(w.get('name'))}".lower()
+1
View File
@@ -24,6 +24,7 @@
- The second fix round escalates to a different model from the first round: `claude:opus` with the Claude seats (it was `venice:kimi code`, and grok 4.7 before that). `TIME_VENTURE_ESCALATE` overrides it.
### Added
- A signup's Venture field matches the venture's slug, name, home folder or an old slug, ignoring case, spaces and punctuation. The field is typed by a person (Teahub does not prefill it from the link), who writes "Cartridge" or "Loot Drop", not `box-art-night`; those signups were not counted.
- A worker's tool call that is cut off at the output limit, or arrives with malformed arguments, is an error the worker reads on its next step instead of a crash that throws the run away. The first Claude venture builds (time#148, #149) died on `'content'`: a whole launch page written in one `write_file` ran past the output limit and arrived with its path and no content. The error says so and asks for smaller files.
- Ponytail on prod: a seat worker (and so the venture builder) carries Ponytail's rules in its system prompt: the smallest complete change, reuse before new code, a check for new logic, never cut validation, security or accessibility, and one closing line on what it skipped. Vendored from DietrichGebert/ponytail (MIT) at `.github/time/rules/ponytail.md`, upstream commit 09c434a. Reviewers, Astra and the venture board never get it (a coding ruleset distorts answers that aren't a diff). `TIME_PONYTAIL=off` turns it off. It ran on hb-5's seats since 2026-10-12; prod had never had it.
- Time retries its own parked cards. A card parked on `error` or `stuck` with no open PR is dispatched again on the next beat, up to `TIME_AUTO_RETRY` times (default 1; 0 leaves it to a human), instead of waiting for a human `/time retry`; the beats carry `auto_retry`, a seat worker resumes the card's branch with half as many turns again (`TIME_WORKER_STEPS` x 1.5), and a card with an open PR still goes to review. Dry-run retries nothing. The `stuck` and `error` comments say what Time will do next.