From bbd53ada0d3853866b1393089aa11f362f2fc6db Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 31 Mar 2026 16:56:03 -0700 Subject: [PATCH] chore: remove unused FIX-BLOCKS.md and uvscripts dev utility --- FIX-BLOCKS.md | 122 -------------- uvscripts/gh_fetch.py | 373 ------------------------------------------ 2 files changed, 495 deletions(-) delete mode 100644 FIX-BLOCKS.md delete mode 100755 uvscripts/gh_fetch.py diff --git a/FIX-BLOCKS.md b/FIX-BLOCKS.md deleted file mode 100644 index f5dd481ec..000000000 --- a/FIX-BLOCKS.md +++ /dev/null @@ -1,122 +0,0 @@ -# Pre-Publish BLOCK Issues: Fix ALL Before Release - -Two independent pre-publish reviews (Opus 4.6 + GPT-5.4) both concluded **BLOCK -- do not publish**. You must fix ALL blocking issues below using UltraBrain parallel agents. Work TDD-style: write/update tests first, then fix, verify tests pass. - -## Strategy - -Use ultrawork (ulw) to spawn UltraBrain agents in parallel. Each UB agent gets a non-overlapping scope. After all agents complete, run bun test to verify everything passes. Commit atomically per fix group. - ---- - -## CRITICAL BLOCKERS (must fix -- 6 items) - -### C1: Hashline Backward Compatibility -**Problem:** Strict whitespace hashing in hashline changes LINE#ID values for indented lines. Breaks existing anchors in cached/persisted edit operations. -**Fix:** Add a compatibility shim -- when lookup by new hash fails, fall back to legacy hash (without strict whitespace). Or version the hash format. -**Files:** Look for hashline-related files in src/tools/ or src/shared/ - -### C2: OpenAI-Only Model Catalog Broken with OpenCode-Go -**Problem:** isOpenAiOnlyAvailability() does not exclude availability.opencodeGo. When OpenCode-Go is present, OpenAI-only detection is wrong -- models get misrouted. -**Fix:** Add !availability.opencodeGo check to isOpenAiOnlyAvailability(). -**Files:** Model/provider system files -- search for isOpenAiOnlyAvailability - -### C3: CLI/Runtime Model Table Divergence -**Problem:** Model tables disagree between CLI install-time and runtime: -- ultrabrain: gpt-5.3-codex in CLI vs gpt-5.4 in runtime -- atlas: claude-sonnet-4-5 in CLI vs claude-sonnet-4-6 in runtime -- unspecified-high also diverges -**Fix:** Reconcile all model tables. Pick the correct model for each and make CLI + runtime match. -**Files:** Search for model table definitions, agent configs, CLI model references - -### C4: atlas/metis/sisyphus-junior Missing OpenAI Fallbacks -**Problem:** These agents can resolve to opencode/glm-4.7-free or undefined in OpenAI-only environments. No valid OpenAI fallback paths exist. -**Fix:** Add valid OpenAI model fallback paths for all agents that need them. -**Files:** Agent config/model resolution code - -### C5: model_fallback Default Mismatch -**Problem:** Schema and docs say model_fallback defaults to false, but runtime treats unset as true. Silent behavior change for all users. -**Fix:** Align -- either update schema/docs to say true, or fix runtime to default to false. Check what the intended behavior is from git history. -**Files:** Schema definition, runtime config loading - -### C6: background_output Default Changed -**Problem:** background_output now defaults to full_session=true. Old callers get different output format without code changes. -**Fix:** Either document this change clearly, or restore old default and make full_session opt-in. -**Files:** Background output handling code - ---- - -## HIGH PRIORITY (strongly recommended -- 4 items) - -### H1: Runtime Fallback session-status-handler Race -**Problem:** When fallback model is already pending, the handler cannot advance the chain on subsequent cooldown events. -**Fix:** Allow override like message-update-handler does. -**Files:** Search for session-status-handler, message-update-handler - -### H2: Atlas Final-Wave Approval Gate Logic -**Problem:** Approval gate logic does not match real Prometheus plan structure (nested checkboxes, parallel execution). Trigger logic is wrong. -**Fix:** Update to handle real plan structures. -**Files:** Atlas agent code, approval gate logic - -### H3: delegate-task-english-directive Dead Code -**Problem:** Not dispatched from tool-execute-before.ts + wrong hook signature. Either wire properly or remove entirely. -**Fix:** Remove if not needed (cleaner). If needed, fix dispatch + signature. -**Files:** src/hooks/, tool-execute-before.ts - -### H4: Auto-Slash-Command Session-Lifetime Dedup -**Problem:** Dedup uses session lifetime, suppressing legitimate repeated identical commands. -**Fix:** Change to short TTL (e.g., 30 seconds) instead of session lifetime. -**Files:** Slash command handling code - ---- - -## ADDITIONAL BLOCKERS FROM GPT-5.4 REVIEW - -### G1: Package Identity Split-Brain -**Problem:** Installer writes oh-my-openagent but doctor, auto-update, version lookup, publish workflow still reference oh-my-opencode. Half-migrated state. -**Fix:** Audit ALL references to package name. Either complete the migration consistently or revert to single name for this release. -**Files:** Installer, doctor, auto-update, version lookup, publish workflow -- grep for both package names - -### G2: OpenCode-Go --opencode-go Value Validation -**Problem:** No validation for --opencode-go CLI value. No detection of existing OpenCode-Go installations. -**Fix:** Add value validation + existing install detection. -**Files:** CLI option handling code - -### G3: Skill/Hook Reference Errors -**Problem:** -- work-with-pr references non-existent git tool category -- github-triage references TaskCreate/TaskUpdate which are not real tool names -**Fix:** Fix tool references to use actual tool names. -**Files:** Skill definition files in .opencode/skills/ - -### G4: Stale Context-Limit Cache -**Problem:** Shared context-limit resolver caches provider config. When config changes, stale removed limits persist and corrupt compaction/truncation decisions. -**Fix:** Add cache invalidation when provider config changes, or make the resolver stateless. -**Files:** Context-limit resolver, compaction code - -### G5: disabled_hooks Schema vs Runtime Contract Mismatch -**Problem:** Schema is strict (rejects unknown hook names) but runtime is permissive (ignores unknown). Contract disagreement. -**Fix:** Align -- either make both strict or both permissive. -**Files:** Hook schema definition, runtime hook loading - ---- - -## EXECUTION INSTRUCTIONS - -1. Spawn UltraBrain agents to fix these in parallel -- group by file proximity: - - UB-1: C1 (hashline) + H4 (slash-command dedup) - - UB-2: C2 + C3 + C4 (model/provider system) + G2 - - UB-3: C5 + C6 (config defaults) + G5 - - UB-4: H1 + H2 (runtime handlers + Atlas gate) - - UB-5: H3 + G3 (dead code + skill references) - - UB-6: G1 (package identity -- full audit) - - UB-7: G4 (context-limit cache) - -2. Each UB agent MUST: - - Write or update tests FIRST (TDD) - - Implement the fix - - Run bun test on affected test files - - Commit with descriptive message - -3. After all UB agents complete, run full bun test to verify no regressions. - -ulw diff --git a/uvscripts/gh_fetch.py b/uvscripts/gh_fetch.py deleted file mode 100755 index 0b06bd500..000000000 --- a/uvscripts/gh_fetch.py +++ /dev/null @@ -1,373 +0,0 @@ -#!/usr/bin/env -S uv run --script -# /// script -# requires-python = ">=3.11" -# dependencies = [ -# "typer>=0.12.0", -# "rich>=13.0.0", -# ] -# /// -""" -GitHub Issues/PRs Fetcher with Exhaustive Pagination. - -Fetches ALL issues and/or PRs from a GitHub repository using gh CLI. -Implements proper pagination to ensure no items are missed. - -Usage: - ./gh_fetch.py issues # Fetch all issues - ./gh_fetch.py prs # Fetch all PRs - ./gh_fetch.py all # Fetch both issues and PRs - ./gh_fetch.py issues --hours 48 # Issues from last 48 hours - ./gh_fetch.py prs --state open # Only open PRs - ./gh_fetch.py all --repo owner/repo # Specify repository -""" - -import asyncio -import json -from datetime import UTC, datetime, timedelta -from enum import Enum -from typing import Annotated - -import typer -from rich.console import Console -from rich.panel import Panel -from rich.progress import Progress, TaskID -from rich.table import Table - -app = typer.Typer( - name="gh_fetch", - help="Fetch GitHub issues/PRs with exhaustive pagination.", - no_args_is_help=True, -) -console = Console() - -BATCH_SIZE = 500 # Maximum allowed by GitHub API - - -class ItemState(str, Enum): - ALL = "all" - OPEN = "open" - CLOSED = "closed" - - -class OutputFormat(str, Enum): - JSON = "json" - TABLE = "table" - COUNT = "count" - - -async def run_gh_command(args: list[str]) -> tuple[str, str, int]: - """Run gh CLI command asynchronously.""" - proc = await asyncio.create_subprocess_exec( - "gh", - *args, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - stdout, stderr = await proc.communicate() - return stdout.decode(), stderr.decode(), proc.returncode or 0 - - -async def get_current_repo() -> str: - """Get the current repository from gh CLI.""" - stdout, stderr, code = await run_gh_command(["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"]) - if code != 0: - console.print(f"[red]Error getting current repo: {stderr}[/red]") - raise typer.Exit(1) - return stdout.strip() - - -async def fetch_items_page( - repo: str, - item_type: str, # "issue" or "pr" - state: str, - limit: int, - search_filter: str = "", -) -> list[dict]: - """Fetch a single page of issues or PRs.""" - cmd = [ - item_type, - "list", - "--repo", - repo, - "--state", - state, - "--limit", - str(limit), - "--json", - "number,title,state,createdAt,updatedAt,labels,author,body", - ] - if search_filter: - cmd.extend(["--search", search_filter]) - - stdout, stderr, code = await run_gh_command(cmd) - if code != 0: - console.print(f"[red]Error fetching {item_type}s: {stderr}[/red]") - return [] - - try: - return json.loads(stdout) if stdout.strip() else [] - except json.JSONDecodeError: - console.print(f"[red]Error parsing {item_type} response[/red]") - return [] - - -async def fetch_all_items( - repo: str, - item_type: str, - state: str, - hours: int | None, - progress: Progress, - task_id: TaskID, -) -> list[dict]: - """Fetch ALL items with exhaustive pagination.""" - all_items: list[dict] = [] - page = 1 - - # First fetch - progress.update(task_id, description=f"[cyan]Fetching {item_type}s page {page}...") - items = await fetch_items_page(repo, item_type, state, BATCH_SIZE) - fetched_count = len(items) - all_items.extend(items) - - console.print(f"[dim]Page {page}: fetched {fetched_count} {item_type}s[/dim]") - - # Continue pagination if we got exactly BATCH_SIZE (more pages exist) - while fetched_count == BATCH_SIZE: - page += 1 - progress.update(task_id, description=f"[cyan]Fetching {item_type}s page {page}...") - - # Use created date of last item to paginate - last_created = all_items[-1].get("createdAt", "") - if not last_created: - break - - search_filter = f"created:<{last_created}" - items = await fetch_items_page(repo, item_type, state, BATCH_SIZE, search_filter) - fetched_count = len(items) - - if fetched_count == 0: - break - - # Deduplicate by number - existing_numbers = {item["number"] for item in all_items} - new_items = [item for item in items if item["number"] not in existing_numbers] - all_items.extend(new_items) - - console.print( - f"[dim]Page {page}: fetched {fetched_count}, added {len(new_items)} new (total: {len(all_items)})[/dim]" - ) - - # Safety limit - if page > 20: - console.print("[yellow]Safety limit reached (20 pages)[/yellow]") - break - - # Filter by time if specified - if hours is not None: - cutoff = datetime.now(UTC) - timedelta(hours=hours) - cutoff_str = cutoff.isoformat() - - original_count = len(all_items) - all_items = [ - item - for item in all_items - if item.get("createdAt", "") >= cutoff_str or item.get("updatedAt", "") >= cutoff_str - ] - filtered_count = original_count - len(all_items) - if filtered_count > 0: - console.print(f"[dim]Filtered out {filtered_count} items older than {hours} hours[/dim]") - - return all_items - - -def display_table(items: list[dict], item_type: str) -> None: - """Display items in a Rich table.""" - table = Table(title=f"{item_type.upper()}s ({len(items)} total)") - table.add_column("#", style="cyan", width=6) - table.add_column("Title", style="white", max_width=50) - table.add_column("State", style="green", width=8) - table.add_column("Author", style="yellow", width=15) - table.add_column("Labels", style="magenta", max_width=30) - table.add_column("Updated", style="dim", width=12) - - for item in items[:50]: # Show first 50 - labels = ", ".join(label.get("name", "") for label in item.get("labels", [])) - updated = item.get("updatedAt", "")[:10] - author = item.get("author", {}).get("login", "unknown") - - table.add_row( - str(item.get("number", "")), - (item.get("title", "")[:47] + "...") if len(item.get("title", "")) > 50 else item.get("title", ""), - item.get("state", ""), - author, - (labels[:27] + "...") if len(labels) > 30 else labels, - updated, - ) - - console.print(table) - if len(items) > 50: - console.print(f"[dim]... and {len(items) - 50} more items[/dim]") - - -@app.command() -def issues( - repo: Annotated[str | None, typer.Option("--repo", "-r", help="Repository (owner/repo)")] = None, - state: Annotated[ItemState, typer.Option("--state", "-s", help="Issue state filter")] = ItemState.ALL, - hours: Annotated[ - int | None, - typer.Option("--hours", "-h", help="Only issues from last N hours (created or updated)"), - ] = None, - output: Annotated[OutputFormat, typer.Option("--output", "-o", help="Output format")] = OutputFormat.TABLE, -) -> None: - """Fetch all issues with exhaustive pagination.""" - - async def async_main() -> None: - target_repo = repo or await get_current_repo() - - console.print(f""" -[cyan]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[/cyan] -[cyan]Repository:[/cyan] {target_repo} -[cyan]State:[/cyan] {state.value} -[cyan]Time filter:[/cyan] {f"Last {hours} hours" if hours else "All time"} -[cyan]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[/cyan] -""") - - with Progress(console=console) as progress: - task: TaskID = progress.add_task("[cyan]Fetching issues...", total=None) - - items = await fetch_all_items(target_repo, "issue", state.value, hours, progress, task) - - progress.update(task, description="[green]Complete!", completed=100, total=100) - - console.print( - Panel( - f"[green]✓ Found {len(items)} issues[/green]", - title="[green]Pagination Complete[/green]", - border_style="green", - ) - ) - - if output == OutputFormat.JSON: - console.print(json.dumps(items, indent=2, ensure_ascii=False)) - elif output == OutputFormat.TABLE: - display_table(items, "issue") - else: # COUNT - console.print(f"Total issues: {len(items)}") - - asyncio.run(async_main()) - - -@app.command() -def prs( - repo: Annotated[str | None, typer.Option("--repo", "-r", help="Repository (owner/repo)")] = None, - state: Annotated[ItemState, typer.Option("--state", "-s", help="PR state filter")] = ItemState.OPEN, - hours: Annotated[ - int | None, - typer.Option("--hours", "-h", help="Only PRs from last N hours (created or updated)"), - ] = None, - output: Annotated[OutputFormat, typer.Option("--output", "-o", help="Output format")] = OutputFormat.TABLE, -) -> None: - """Fetch all PRs with exhaustive pagination.""" - - async def async_main() -> None: - target_repo = repo or await get_current_repo() - - console.print(f""" -[cyan]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[/cyan] -[cyan]Repository:[/cyan] {target_repo} -[cyan]State:[/cyan] {state.value} -[cyan]Time filter:[/cyan] {f"Last {hours} hours" if hours else "All time"} -[cyan]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[/cyan] -""") - - with Progress(console=console) as progress: - task: TaskID = progress.add_task("[cyan]Fetching PRs...", total=None) - - items = await fetch_all_items(target_repo, "pr", state.value, hours, progress, task) - - progress.update(task, description="[green]Complete!", completed=100, total=100) - - console.print( - Panel( - f"[green]✓ Found {len(items)} PRs[/green]", - title="[green]Pagination Complete[/green]", - border_style="green", - ) - ) - - if output == OutputFormat.JSON: - console.print(json.dumps(items, indent=2, ensure_ascii=False)) - elif output == OutputFormat.TABLE: - display_table(items, "pr") - else: # COUNT - console.print(f"Total PRs: {len(items)}") - - asyncio.run(async_main()) - - -@app.command(name="all") -def fetch_all( - repo: Annotated[str | None, typer.Option("--repo", "-r", help="Repository (owner/repo)")] = None, - state: Annotated[ItemState, typer.Option("--state", "-s", help="State filter")] = ItemState.ALL, - hours: Annotated[ - int | None, - typer.Option("--hours", "-h", help="Only items from last N hours (created or updated)"), - ] = None, - output: Annotated[OutputFormat, typer.Option("--output", "-o", help="Output format")] = OutputFormat.TABLE, -) -> None: - """Fetch all issues AND PRs with exhaustive pagination.""" - - async def async_main() -> None: - target_repo = repo or await get_current_repo() - - console.print(f""" -[cyan]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[/cyan] -[cyan]Repository:[/cyan] {target_repo} -[cyan]State:[/cyan] {state.value} -[cyan]Time filter:[/cyan] {f"Last {hours} hours" if hours else "All time"} -[cyan]Fetching:[/cyan] Issues AND PRs -[cyan]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[/cyan] -""") - - with Progress(console=console) as progress: - issues_task: TaskID = progress.add_task("[cyan]Fetching issues...", total=None) - prs_task: TaskID = progress.add_task("[cyan]Fetching PRs...", total=None) - - # Fetch in parallel - issues_items, prs_items = await asyncio.gather( - fetch_all_items(target_repo, "issue", state.value, hours, progress, issues_task), - fetch_all_items(target_repo, "pr", state.value, hours, progress, prs_task), - ) - - progress.update( - issues_task, - description="[green]Issues complete!", - completed=100, - total=100, - ) - progress.update(prs_task, description="[green]PRs complete!", completed=100, total=100) - - console.print( - Panel( - f"[green]✓ Found {len(issues_items)} issues and {len(prs_items)} PRs[/green]", - title="[green]Pagination Complete[/green]", - border_style="green", - ) - ) - - if output == OutputFormat.JSON: - result = {"issues": issues_items, "prs": prs_items} - console.print(json.dumps(result, indent=2, ensure_ascii=False)) - elif output == OutputFormat.TABLE: - display_table(issues_items, "issue") - console.print("") - display_table(prs_items, "pr") - else: # COUNT - console.print(f"Total issues: {len(issues_items)}") - console.print(f"Total PRs: {len(prs_items)}") - - asyncio.run(async_main()) - - -if __name__ == "__main__": - app()