Skip to content

Commit 006d468

Browse files
committed
update forever loop
1 parent 624c71f commit 006d468

File tree

8 files changed

+251
-71
lines changed

8 files changed

+251
-71
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,8 @@ run-review:
583583
# Poll Review pool column for eligible PRs and dispatch run-review
584584
run-review-forever:
585585
@. scripts/make_helpers.sh; \
586-
REPO=$$(gh repo view --json nameWithOwner --jq .nameWithOwner); \
586+
REPO=$$(gh repo view --json nameWithOwner --jq .nameWithOwner) || { echo "Failed to detect repo (gh repo view failed)"; exit 1; }; \
587+
if [ -z "$$REPO" ]; then echo "Failed to detect repo (empty result)"; exit 1; fi; \
587588
MAKE=$(MAKE) watch_and_dispatch review run-review "Review pool PRs" "$$REPO"
588589

589590
# Request Copilot code review on the current PR

scripts/make_helpers.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ run_agent() {
6161

6262
# --- Project board ---
6363

64-
# Detect the next eligible item and preserve retryable state in a queue.
64+
# Detect the next eligible item from the current board snapshot.
6565
# poll_project_items <mode> <state-file> [repo] [number] [format]
6666
poll_project_items() {
6767
mode=$1

scripts/pipeline_board.py

Lines changed: 75 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -428,22 +428,24 @@ def item_identity(item: dict) -> str:
428428

429429
def load_state(state_file: Path) -> dict:
430430
if not state_file.exists():
431-
return {"visible": {}, "pending": []}
431+
return {"retries": {}}
432432

433433
raw = state_file.read_text().strip()
434434
if not raw:
435-
return {"visible": {}, "pending": []}
435+
return {"retries": {}}
436436

437437
data = json.loads(raw)
438438
if not isinstance(data, dict):
439439
raise ValueError(f"State file must contain a JSON object: {state_file}")
440440

441-
visible = data.get("visible", {})
442-
pending = data.get("pending", [])
443-
if not isinstance(visible, dict) or not isinstance(pending, list):
441+
retries = data.get("retries", {})
442+
if not isinstance(retries, dict):
444443
raise ValueError(f"Invalid poll state format: {state_file}")
445444

446-
return {"visible": visible, "pending": [str(item_id) for item_id in pending]}
445+
normalized_retries: dict[str, int] = {}
446+
for item_id, count in retries.items():
447+
normalized_retries[str(item_id)] = int(count)
448+
return {"retries": normalized_retries}
447449

448450

449451
def save_state(state_file: Path, state: dict) -> None:
@@ -963,22 +965,36 @@ def select_entry_from_entries(
963965
state_file: Path,
964966
target_number: int | None = None,
965967
) -> dict | None:
966-
state = load_state(state_file)
967-
previous_visible = state["visible"]
968-
969-
pending = [item_id for item_id in state["pending"] if item_id in current_visible]
970-
entered = sorted(
971-
(item_id for item_id in current_visible if item_id not in previous_visible),
972-
key=lambda item_id: (current_visible[item_id]["number"], item_id),
968+
del state_file
969+
return select_current_entry_from_entries(
970+
"review",
971+
current_visible,
972+
target_number=target_number,
973973
)
974-
for item_id in entered:
975-
if item_id not in pending:
976-
pending.append(item_id)
977974

978-
state["visible"] = current_visible
979-
state["pending"] = pending
980-
save_state(state_file, state)
981975

976+
def current_entry_sort_key(
977+
mode: str,
978+
entry: dict,
979+
item_id: str,
980+
) -> tuple[int, int, str] | tuple[int, str]:
981+
if mode == "ready":
982+
title = entry.get("title") or ""
983+
if title.startswith("[Model]"):
984+
kind_priority = 0
985+
elif title.startswith("[Rule]"):
986+
kind_priority = 1
987+
else:
988+
kind_priority = 2
989+
return (kind_priority, int(entry["number"]), item_id)
990+
return (int(entry["number"]), item_id)
991+
992+
993+
def select_current_entry_from_entries(
994+
mode: str,
995+
current_visible: dict[str, dict],
996+
target_number: int | None = None,
997+
) -> dict | None:
982998
if target_number is not None:
983999
matching_item_id = next(
9841000
(
@@ -990,17 +1006,40 @@ def select_entry_from_entries(
9901006
)
9911007
if matching_item_id is None:
9921008
return None
993-
entry = dict(current_visible[matching_item_id])
994-
entry["item_id"] = matching_item_id
995-
return entry
1009+
return {
1010+
**current_visible[matching_item_id],
1011+
"item_id": matching_item_id,
1012+
}
9961013

997-
if not pending:
1014+
if not current_visible:
9981015
return None
9991016

1000-
item_id = pending[0]
1001-
entry = dict(current_visible[item_id])
1002-
entry["item_id"] = item_id
1003-
return entry
1017+
item_id = min(
1018+
current_visible,
1019+
key=lambda candidate_id: current_entry_sort_key(
1020+
mode,
1021+
current_visible[candidate_id],
1022+
candidate_id,
1023+
),
1024+
)
1025+
return {
1026+
**current_visible[item_id],
1027+
"item_id": item_id,
1028+
}
1029+
1030+
1031+
def select_polled_entry_from_entries(
1032+
mode: str,
1033+
current_visible: dict[str, dict],
1034+
state_file: Path,
1035+
target_number: int | None = None,
1036+
) -> dict | None:
1037+
del state_file
1038+
return select_current_entry_from_entries(
1039+
mode,
1040+
current_visible,
1041+
target_number=target_number,
1042+
)
10041043

10051044

10061045
def select_next_entry(
@@ -1024,7 +1063,8 @@ def select_next_entry(
10241063
batch_pr_fetcher=batch_pr_fetcher,
10251064
repo_root=repo_root,
10261065
)
1027-
return select_entry_from_entries(
1066+
return select_polled_entry_from_entries(
1067+
mode,
10281068
current_visible,
10291069
state_file,
10301070
target_number=target_number,
@@ -1033,9 +1073,9 @@ def select_next_entry(
10331073

10341074
def ack_item(state_file: Path, item_id: str) -> None:
10351075
state = load_state(state_file)
1036-
state["pending"] = [
1037-
pending_id for pending_id in state["pending"] if pending_id != item_id
1038-
]
1076+
retries = state.get("retries", {})
1077+
retries.pop(str(item_id), None)
1078+
state["retries"] = retries
10391079
save_state(state_file, state)
10401080

10411081

@@ -1219,7 +1259,8 @@ def claim_entry_from_entries(
12191259
target_number: int | None = None,
12201260
mover: Callable[[str, str], None] | None = None,
12211261
) -> dict | None:
1222-
next_entry = select_entry_from_entries(
1262+
next_entry = select_polled_entry_from_entries(
1263+
mode,
12231264
current_visible,
12241265
state_file,
12251266
target_number=target_number,
@@ -1650,7 +1691,8 @@ def main(argv: list[str] | None = None) -> int:
16501691
batch_pr_fetcher=batch_fetch_prs_with_reviews,
16511692
)
16521693
)
1653-
next_item = select_entry_from_entries(
1694+
next_item = select_polled_entry_from_entries(
1695+
args.mode,
16541696
review_entries_map,
16551697
args.state_file,
16561698
target_number=args.number,

scripts/pipeline_pr.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -535,8 +535,7 @@ def fetch_pr_data(repo: str, pr_number: int) -> dict:
535535
"--json",
536536
(
537537
"number,title,body,labels,files,additions,deletions,commits,"
538-
"headRefName,baseRefName,headRefOid,url,state,mergeable,author,"
539-
"closingIssuesReferences"
538+
"headRefName,baseRefName,headRefOid,url,state,mergeable,author"
540539
),
541540
)
542541

scripts/project_board_poll.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def process_snapshot(
123123

124124
def parse_args(argv: list[str]) -> argparse.Namespace:
125125
parser = argparse.ArgumentParser(
126-
description="Track newly eligible board items for forever pollers."
126+
description="Select eligible board items from the current project-board snapshot."
127127
)
128128
subparsers = parser.add_subparsers(dest="command", required=True)
129129

0 commit comments

Comments
 (0)