Repository navigation
feat(cli): --notify-level to control watch round noise (#803) - #805
tamirdresher wants to merge 6 commits into
Conversation
- Default 'important': only print rounds with actual work items - 'all': old behavior (every round including empty) - 'none': suppress all round output - Add machine name + repo name to round headers for attribution - Configurable via .squad/config.json watch.notifyLevel - 5 new tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🛫 PR Readiness Check
|
| Status | Check | Details |
|---|---|---|
| ❌ | Single commit | 6 commits — consider squashing before review |
| ✅ | Not in draft | Ready for review |
| ✅ | Branch up to date | Up to date with dev |
| ❌ | Copilot review | No Copilot review yet — it may still be processing |
| ✅ | Changeset present | Changeset file found |
| ✅ | Scope clean | No .squad/ or docs/proposals/ files |
| ✅ | No merge conflicts | No merge conflicts |
| ❌ | Copilot threads resolved | 3 unresolved Copilot thread(s) — fix and resolve before merging |
| ❌ | CI passing | 15 check(s) still running |
This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.
There was a problem hiding this comment.
Pull request overview
Adds a new watch/triage verbosity control to reduce “empty round” spam in squad watch, and improves round output attribution for forwarded logs.
Changes:
- Introduces
notifyLevel(all|important|none) inWatchConfig, with defaultimportant. - Extends
reportBoard()to acceptReportOptions(notifyLevel + optional machine/repo attribution) and wires it intorunWatch(). - Adds
--notify-levelCLI parsing plus a changeset entry and new tests.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
test/watch-notify-level.test.ts |
Adds unit tests for reportBoard() behavior under different notifyLevel modes and attribution fields. |
packages/squad-cli/src/cli/commands/watch/index.ts |
Adds ReportOptions + notify-level gating to reportBoard() and includes (hostname · repo) in round headers. |
packages/squad-cli/src/cli/commands/watch/config.ts |
Adds notifyLevel to config schema/defaults and normalizes it from .squad/config.json. |
packages/squad-cli/src/cli-entry.ts |
Parses --notify-level and passes it into loadWatchConfig() overrides. |
.changeset/quiet-notifications.md |
Documents the new flag/config and the default behavior change. |
| export function reportBoard(state: BoardState, round: number, options?: ReportOptions): void { | ||
| const level = options?.notifyLevel ?? 'important'; | ||
| if (level === 'none') return; | ||
|
|
||
| const total = Object.values(state).reduce((a, b) => a + b, 0); | ||
|
|
||
| // In 'important' mode, suppress empty rounds entirely | ||
| if (total === 0 && level === 'important') return; | ||
|
|
||
| if (total === 0) { | ||
| console.log(`${DIM}📋 Board is clear — Ralph is idling${RESET}`); |
There was a problem hiding this comment.
reportBoard() now defaults to notifyLevel: 'important', which means calling it without options suppresses all output for empty board states. Because reportBoard is exported and used directly in the test suite, this default is a breaking behavioral change and will cause callers that expect the "Board is clear" message to stop seeing any output. Consider keeping reportBoard's default at 'all' (preserve the function's historical behavior) and have runWatch() pass the desired config.notifyLevel explicitly (defaulting to 'important' at the watch-command/config layer).
| if (total === 0) { | ||
| console.log(`${DIM}📋 Board is clear — Ralph is idling${RESET}`); | ||
| return; | ||
| } | ||
| console.log(`\n${BOLD}🔄 Ralph — Round ${round}${RESET}`); | ||
|
|
||
| // Build context tag for attribution | ||
| const ctx = [options?.machineName, options?.repoName].filter(Boolean).join(' · '); | ||
| const ctxSuffix = ctx ? ` ${DIM}(${ctx})${RESET}` : ''; | ||
|
|
||
| console.log(`\n${BOLD}🔄 Ralph — Round ${round}${RESET}${ctxSuffix}`); |
There was a problem hiding this comment.
In notifyLevel: 'all' mode, the empty-board path logs only "Board is clear — Ralph is idling" and does not include the round header or the (machine · repo) attribution. That undermines the stated goal of making forwarded output attributable (and makes it hard to correlate to a specific round). Consider including the same context tag (and optionally the round number) in the clear-board message when machineName/repoName are provided, or printing a round header even for empty rounds in all mode.
| /** | ||
| * Controls how verbose watch round reporting is. | ||
| * - 'all' — print every round (current behavior) | ||
| * - 'important' — only print rounds with actual work (default) | ||
| * - 'none' — suppress all round reporting | ||
| */ | ||
| notifyLevel?: 'all' | 'important' | 'none'; | ||
| /** Verbose diagnostics (--verbose flag). */ | ||
| verbose?: boolean; | ||
| } |
There was a problem hiding this comment.
WatchConfig introduces a verbose option and documents it as coming from a --verbose flag, but there is no CLI parsing for --verbose and the value is not referenced anywhere in the watch implementation. This is misleading documentation and effectively a dead config knob. Either remove verbose from WatchConfig/normalize logic for now, or implement the flag parsing and wire config.verbose into actual diagnostics output.
| it('suppresses empty rounds in important mode (default)', () => { | ||
| const logs: string[] = []; | ||
| const origLog = console.log; | ||
| console.log = (...args: unknown[]) => logs.push(args.join(' ')); | ||
| try { | ||
| reportBoard(emptyState(), 42, { notifyLevel: 'important' }); | ||
| expect(logs).toHaveLength(0); | ||
| } finally { | ||
| console.log = origLog; | ||
| } |
There was a problem hiding this comment.
These tests reassign console.log directly. Vitest commonly runs test files in parallel workers, so mutating the global console.log function can create cross-test interference/flakiness (and it’s inconsistent with existing tests in this repo that use vi.spyOn(console, 'log')). Prefer vi.spyOn(console, 'log').mockImplementation(...) plus vi.restoreAllMocks() in afterEach to keep the capture isolated and automatically restored.
| - `--notify-level important` (default): only print rounds with actual work items | ||
| - `--notify-level all`: print every round including empty (old behavior) | ||
| - `--notify-level none`: suppress all round output | ||
| - Add machine name (`os.hostname()`) and repo name to round headers for attribution | ||
| - Configurable via `.squad/config.json` watch section: `"notifyLevel": "important"` | ||
| - Empty rounds silenced by default — no more "Round 160, Round 161" spam |
There was a problem hiding this comment.
This changeset states machine/repo attribution is added to "round headers", but in notifyLevel: 'all' mode empty rounds currently print only the clear-board message without any (machine · repo) context. Either update the implementation so empty-round output also includes attribution, or tweak the changeset wording so it doesn’t overstate the attribution coverage.
- notification-level.md: full feature doc for --notify-level - ralph.md: add --notify-level + --cleanup to capabilities table - 030-v092-whats-coming.md: blog post covering all 10 features in next release Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
captureBoardOutput now defaults to 'all' mode to preserve existing test behavior. The production default is 'important' but tests need to verify clear-board message formatting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CSpell docs-quality CI was failing because 'WCBED' is an unknown word. Replaced CPC-tamir-WCBED with DEVBOX-01 in the blog post and notification-level feature doc. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add JSDoc to reportBoard() documenting default notifyLevel='important' - Include machine/repo context in 'Board is clear' message for all modes - Add comment noting verbose flag parsing is in PR #782 - Replace console.log direct mutation with vi.spyOn in tests - Update changeset to clarify attribution is in round headers and clear message Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Superseded by #830 — combined into a single watch next-gen PR for easier review. |
|
Closing — superseded by #830 (watch-next-gen combined PR) |
What
Adds
--notify-levelflag and config option to control how verbose watch round reporting is. Default changed from 'all' to 'important' — empty rounds are now silenced.Why
Watch console output forwarded to Teams/webhook produces spam: 'Round 160, Round 161...' with no machine or repo attribution. Users can't tell what's happening or where. Closes #803
How
notifyLevelfield inWatchConfig:all|important(default) |nonereportBoard()acceptsReportOptionswith notifyLevel, machineName, repoNameimportantmode: empty rounds produce zero output(hostname · repo)for attribution--notify-level important.squad/config.json→{ watch: { notifyLevel: 'important' } }Testing
npm run buildpassesnpm testpasses (5 new tests)Docs
Exports
N/A
Breaking Changes
Behavioral: Default changed from printing every round to only printing rounds with work. Use
--notify-level allto restore old behavior.Waivers
None