Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.5.78-dev (unreleased)

### Features

- **`board update-check` + silent startup hook** (#43) — Ported the bash-only version check from `bin/cnb` into a reusable Python module (`lib/update_check.py`) and wired it into `bin/board`'s main(), so tongxue who run `board` directly (skipping the `cnb` wrapper) now also detect a stale install. Detection routes a single board message to the device-supervisor tongxue (one notification per `current→latest` pair); each tongxue does not self-update. Skipped in venv. `CNB_SKIP_UPDATE_CHECK=1` disables the hook for tests / quick runs. New `board update-check [--force]` command for manual triggering and debugging. The bash check in `bin/cnb` is intentionally retained for now — consolidating it into the Python path interfered with downstream subcommand stdout capture on Linux CI; leaving both paths in place is the safer ship.

## 0.5.76-dev (unreleased)

### Bug Fixes
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.5.76-dev
0.5.79-dev

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep version files in sync

VERSION is the canonical input for bin/sync-version --check, which expects package.json to match it and pyproject.toml to contain the PEP 440 form. This change sets VERSION to 0.5.79-dev while the other two files are 0.5.78, so the documented PR/release check fails and the installed runtime version can diverge from the package metadata.

Useful? React with 👍 / 👎.

28 changes: 28 additions & 0 deletions bin/board
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Uses a declarative command registry with lazy module imports instead of
if/elif chains and ad-hoc lambda dicts.
"""

import os
import sys
from dataclasses import dataclass, field
from importlib import import_module
Expand Down Expand Up @@ -318,6 +319,14 @@ COMMANDS: list[Command] = [
needs_identity=False,
aliases=["m"],
),
Command(
"update-check",
"lib.update_check",
"cmd_update_check",
"check for stale cnb install",
"update-check [--force]",
needs_identity=False,
),
# ── maintenance ──
Command(
"prune",
Expand Down Expand Up @@ -400,6 +409,24 @@ def print_help() -> None:
# ---------------------------------------------------------------------------


def _maybe_check_update(env: ClaudesEnv, cmd_name: str) -> None:
"""Silent best-effort version check. Never blocks the dispatch.

Skipped for `update-check` itself (the command does its own check) and when
the explicit opt-out env is set.
"""
if os.environ.get("CNB_SKIP_UPDATE_CHECK") == "1":
return
if cmd_name == "update-check":
return
try:
from lib.update_check import _read_local_version, check_update

check_update(env, _read_local_version(env.install_home))
except Exception:
pass


def main() -> None:
env = ClaudesEnv.load()
db = BoardDB(env)
Expand All @@ -426,6 +453,7 @@ def main() -> None:
if identity:
validate_identity(db, identity)

_maybe_check_update(env, cmd.name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Skip the update hook for notification sends

When a stale cache exists and no update-notified key has been written yet, this hook also runs for board --as dispatcher send ...; the update notifier itself sends its message by spawning bin/board --as dispatcher send, so the child process re-enters _maybe_check_update before dispatching the send and recursively spawns more board sends until timeouts rather than delivering the notification. This affects both the new Python notifier and the existing bin/cnb bash notifier whenever they try to send the first stale-version message; skip the hook for send or set CNB_SKIP_UPDATE_CHECK=1 on the internal send.

Useful? React with 👍 / 👎.

_dispatch(cmd, db, identity, rest)


Expand Down
5 changes: 5 additions & 0 deletions lib/concerns/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ def get_dev_sessions(cfg: DispatcherConfig) -> list[str]:
return [line[len(pfx) :] for line in raw.splitlines() if line.startswith(pfx) and line[len(pfx) :] not in protected]


def has_lead_session(cfg: DispatcherConfig) -> bool:
sess = f"{cfg.prefix}-lead"
return tmux_ok("has-session", "-t", sess)


def pane_md5(sess: str) -> str:
content = tmux_run("capture-pane", "-t", sess, "-p") or ""
return hashlib.md5(content.encode()).hexdigest()
Expand Down
34 changes: 30 additions & 4 deletions lib/concerns/nudge_coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from .base import Concern
from .config import DispatcherConfig
from .helpers import db, get_dev_sessions, is_claude_running, log, tmux, tmux_ok, tmux_send
from .helpers import db, get_dev_sessions, has_lead_session, is_claude_running, log, tmux, tmux_ok, tmux_send


@dataclass
Expand Down Expand Up @@ -117,14 +117,21 @@ def _try_queued_flush(self, name: str) -> bool:
return True

def _try_idle(self, name: str) -> bool:
sess = f"{self.cfg.prefix}-{name}"
# Workers (dev) idle is normal — they wait for lead to assign work.
# Do not nudge dev idle. Only inbox / queued_flush apply to workers.
# Lead idle is handled separately in tick() with a different message.
return False

def _try_lead_idle(self) -> bool:
sess = f"{self.cfg.prefix}-lead"
if not self.idle.is_idle(sess):
return False
if _already_queued(sess, "推进你的活跃 KR"):
if _already_queued(sess, "扫描团队"):
return False
tmux_send(
sess,
f"继续工作。检查你的 OKR ({self.cfg.okr_dir}/{name}.md),推进你的活跃 KR。自己决定优先级。",
"lead 不能 idle。扫描团队状态:谁空闲、谁阻塞、PR queue、master CI、open issues。"
"主动给空闲员工派下一个 issue,不要等他们汇报。",
)
return True

Expand Down Expand Up @@ -163,6 +170,25 @@ def check_session(self, name: str, now: int) -> None:
"""Check and nudge a specific session immediately."""
self._process_session(name, now)

def _process_lead(self, now: int) -> None:
if not has_lead_session(self.cfg):
return
sess = f"{self.cfg.prefix}-lead"
if not (tmux_ok("has-session", "-t", sess) and is_claude_running(sess)):
return
if "lead" in self._records:
self._check_effectiveness("lead")
if not self._can_nudge("lead", now):
return
for nudge_type, try_fn in [
("inbox", lambda n="lead": self._try_inbox(n)),
("lead_idle", lambda: self._try_lead_idle()),
]:
if try_fn():
self._record("lead", nudge_type, now)
break

def tick(self, now: int) -> None:
for name in get_dev_sessions(self.cfg):
self._process_session(name, now)
self._process_lead(now)
Loading
Loading