Skip to content

[limen CLI-watch-subcommand] cli watch subcommand#32

Open
4444J99 wants to merge 1 commit into
mainfrom
limen/cli-watch-subcommand-347e
Open

[limen CLI-watch-subcommand] cli watch subcommand#32
4444J99 wants to merge 1 commit into
mainfrom
limen/cli-watch-subcommand-347e

Conversation

@4444J99

@4444J99 4444J99 commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Autonomous limen dispatch of task CLI-watch-subcommand.

Wire scripts/watch.py in as python3 -m limen watch with a --compact one-line mode; add a cli/tests test that watch --once renders without error.

Produced in an isolated worktree off origin — review before merge.

Summary by Sourcery

Add a CLI watch subcommand and supporting module to render a live limen fleet dashboard, including a compact mode and JSON status export.

New Features:

  • Introduce a limen watch CLI command that renders a live fleet dashboard with configurable interval, one-shot mode, compact output, and color controls.
  • Add a watch module that snapshots task status, aggregates per-lane metrics, and writes a structured fleet-status.json log for external consumers.
  • Provide a standalone scripts/watch.py entry point for invoking the dashboard outside the installed CLI.

Tests:

  • Add CLI tests to verify watch --once runs without error, produces the expected output, and emits the fleet-status.json log.
  • Add a test to ensure watch --once --compact emits a single-line, compact summary of fleet status.

limen task CLI-watch-subcommand
@sourcery-ai

sourcery-ai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a new limen watch CLI subcommand and underlying watch dashboard module, including compact/JSON output and tests, and wires it to a standalone script entry point.

Sequence diagram for new limen watch CLI subcommand

sequenceDiagram
    actor User
    participant CLI as limen_cli_main
    participant WatchCmd as watch_subcommand
    participant Watch as run_watch
    participant Snap as snapshot
    participant Render as render_render_compact
    participant JSON as emit_json

    User->>CLI: limen watch [--once --interval --compact --no-color]
    CLI->>WatchCmd: watch(once, interval, compact, no_color)
    WatchCmd->>Watch: run_watch(root, tasks_path, once, interval, compact, color)

    loop each_refresh_frame
        Watch->>Snap: snapshot(tasks_path)
        Snap-->>Watch: limen, board, per_lane, working, procs, budget
        Watch->>JSON: emit_json(root, board, per_lane, working, procs, budget_spent, budget_daily)
        alt compact
            Watch->>Render: render_compact(board, per_lane, procs, budget_spent, budget_daily)
        else full
            Watch->>Render: render(limen, board, per_lane, working, procs, interval, color)
        end
        Render-->>Watch: frame
        alt once
            Watch->>User: click.echo(frame)
            Watch-->>Watch: return
        else continuous
            Watch->>User: write frame to stdout
            Watch->>Watch: time.sleep(interval)
        end
    end
Loading

File-Level Changes

Change Details Files
Introduce a watch subcommand in the main limen CLI that delegates to the new watch dashboard implementation.
  • Import the new run_watch helper from the watch module into the CLI entrypoint.
  • Define a Click watch command with options for one-shot mode, refresh interval, compact output, and disabling color.
  • Resolve root and tasks path using existing helpers and call run_watch with CLI-derived arguments.
cli/src/limen/cli.py
Implement the watch dashboard logic, including snapshotting fleet status, rendering full/compact views, emitting JSON, and providing a standalone CLI entrypoint.
  • Define constants for lane metadata, ANSI styles, and active statuses used for rendering the dashboard.
  • Add helpers to inspect running processes per lane, compute task snapshot data, and build textual and compact representations of fleet status.
  • Implement JSON emission of fleet status to logs/fleet-status.json with per-lane aggregates and working task summaries.
  • Provide run_watch loop that repeatedly snapshots, writes JSON, and outputs either full-screen or compact frames, honoring once, interval, and color flags.
  • Add internal helpers and a Click main command to resolve root/tasks from CLI options or environment, then invoke run_watch, with graceful KeyboardInterrupt handling.
cli/src/limen/watch.py
Add tests verifying limen watch works in once and compact modes and writes status JSON.
  • Introduce a helper to write a minimal tasks.yaml board including portal budget metadata for tests.
  • Test that watch --once --no-color runs successfully, renders the full dashboard header, omits task title details, and creates logs/fleet-status.json.
  • Test that watch --once --compact produces exactly one output line with the expected compact summary fields.
cli/tests/test_watch.py
Provide a scripts/watch.py helper script that runs the watch CLI module directly via Python paths.
  • Compute repository and CLI src roots from the script location and prepend the CLI src path to sys.path if present.
  • Import limen.watch.main and execute it under a standard Python __main__ guard to act as a standalone CLI entrypoint.
scripts/watch.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The root/tasks resolution logic in watch.py (_resolve_root, _resolve_tasks_path) largely duplicates behavior already present in cli.py; consider reusing or centralizing these helpers to avoid divergence over time.
  • emit_json currently swallows all exceptions with a bare except Exception:; it would be safer to catch only filesystem-related errors (e.g. OSError) or at least log unexpected failures so issues writing fleet-status.json are visible.
  • The process filtering in proc_counts relies on substring checks of ps output and may be fragile (e.g. matching unrelated commands containing opencode or limen watch in arguments); consider tightening the matching or using a more robust mechanism if available on your target platforms.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The root/tasks resolution logic in `watch.py` (`_resolve_root`, `_resolve_tasks_path`) largely duplicates behavior already present in `cli.py`; consider reusing or centralizing these helpers to avoid divergence over time.
- `emit_json` currently swallows all exceptions with a bare `except Exception:`; it would be safer to catch only filesystem-related errors (e.g. `OSError`) or at least log unexpected failures so issues writing `fleet-status.json` are visible.
- The process filtering in `proc_counts` relies on substring checks of `ps` output and may be fragile (e.g. matching unrelated commands containing `opencode` or `limen watch` in arguments); consider tightening the matching or using a more robust mechanism if available on your target platforms.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a live dashboard feature via a new watch subcommand for the Limen fleet CLI. It displays real-time task statuses, active processes, and budget usage in either a detailed or compact terminal view, while periodically exporting this data to a JSON log. Feedback on the implementation highlights opportunities to improve robustness and usability in run_watch, specifically by handling KeyboardInterrupt and FileNotFoundError gracefully, and ensuring ANSI clear sequences are disabled when the --no-color flag is set.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread cli/src/limen/watch.py
Comment on lines +213 to +226
while True:
limen, board, per_lane, working, procs, budget = snapshot(tasks_path)
emit_json(root, board, per_lane, working, procs, budget.track.spent, budget.daily)
frame = (
render_compact(board, per_lane, procs, budget.track.spent, budget.daily)
if compact
else render(limen, board, per_lane, working, procs, interval=interval, color=color)
)
if once:
click.echo(frame)
return
sys.stdout.write(("" if compact else CLEAR) + frame + "\n")
sys.stdout.flush()
time.sleep(interval)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Robustness and Usability Improvements in run_watch

There are three key issues in the current implementation of run_watch:

  1. Unhandled KeyboardInterrupt: When running in watch mode, pressing Ctrl-C will raise a KeyboardInterrupt and print a traceback to the user. This should be caught to exit watch mode gracefully.
  2. Unhandled FileNotFoundError: If tasks.yaml does not exist or is deleted/renamed while the watch loop is running, the command will crash with a traceback. It should handle this gracefully by printing a clean error message and either exiting (if --once is set) or retrying in the next iteration.
  3. ANSI Sequences with --no-color: The CLEAR ANSI escape sequence (\033[2J\033[H) is written to stdout even when color is False (i.e., --no-color is passed). This violates the user's preference and can corrupt output logs if redirected.
    try:
        while True:
            try:
                limen, board, per_lane, working, procs, budget = snapshot(tasks_path)
                emit_json(root, board, per_lane, working, procs, budget.track.spent, budget.daily)
                frame = (
                    render_compact(board, per_lane, procs, budget.track.spent, budget.daily)
                    if compact
                    else render(limen, board, per_lane, working, procs, interval=interval, color=color)
                )
                if once:
                    click.echo(frame)
                    return
                sys.stdout.write(("" if compact or not color else CLEAR) + frame + "\n")
                sys.stdout.flush()
            except FileNotFoundError:
                error_msg = f"Error: tasks.yaml not found at {tasks_path}"
                if once:
                    click.echo(error_msg, err=True)
                    sys.exit(1)
                sys.stdout.write(("" if compact or not color else CLEAR) + error_msg + "\n")
                sys.stdout.flush()
            except Exception as e:
                error_msg = f"Error loading tasks: {e}"
                if once:
                    click.echo(error_msg, err=True)
                    sys.exit(1)
                sys.stdout.write(("" if compact or not color else CLEAR) + error_msg + "\n")
                sys.stdout.flush()
            time.sleep(interval)
    except KeyboardInterrupt:
        if not once and not compact:
            click.echo("\nExiting watch mode.")

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89006f30a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/src/limen/watch.py
}
try:
(root / "logs").mkdir(exist_ok=True)
(root / "logs" / "fleet-status.json").write_text(json.dumps(data, indent=2))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore the generated watch feed

When LIMEN_ROOT points at a normal git checkout, every limen watch frame, including --once, writes logs/fleet-status.json. I checked the repository ignore rules and logs/ is not ignored, so simply viewing the dashboard leaves an untracked generated file in operators' worktrees and makes it easy to accidentally commit transient fleet state. Please either write this feed under an ignored/cache path or add an ignore rule for the generated file.

Useful? React with 👍 / 👎.

Comment thread cli/src/limen/watch.py
"codex": ("\033[36m", "codex"),
"claude": ("\033[35m", "claude"),
"opencode": ("\033[32m", "opencode"),
"agy": ("\033[33m", "antigravity"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Track the default agy worker process

The existing local-lane path routes Antigravity work as agy and dispatches the default agy -p executable, but this watcher scans ps for the substring antigravity. On machines using the default lane command, active Antigravity jobs will leave live_procs at 0 and the dashboard/JSON feed will report the agy lane as idle while it is doing work.

Useful? React with 👍 / 👎.

Comment thread cli/src/limen/watch.py
Comment on lines +111 to +112
for lane, (color_code, process_name) in LANES.items():
counts = per_lane.get(lane, Counter())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include unrouted any tasks in the lane view

When tasks are still queued with the schema-supported target_agent: any (for example from scripts/mine-backlog.py before routing), snapshot() counts them under per_lane['any'], but the dashboard only renders fixed entries from LANES. In that state the global open/in-flight totals include these tasks while no lane row or working-task detail shows where they are queued, so operators can miss unrouted backlog or active any claims.

Useful? React with 👍 / 👎.

Comment thread cli/src/limen/watch.py
) -> str:
budget = limen.portal.budget
total = sum(board.values())
done = board.get("done", 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Count archived tasks as completed

The rest of the product treats archived as completed work after a done task is closed, but this dashboard only counts the literal done status. Once operators archive completed tasks through the QA flow, the watch header and per-lane progress bars under-report completion and make total progress appear to regress even though the work is finished.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant