Skip to content

Named verbosity channels: selective output with -v CHANNEL #65

Description

@djdarcy

Named verbosity channels: selective output with -v CHANNEL

Problem

The current verbosity system (-v, -vv, -vvv) is a single numeric dial — turning it up reveals everything at that level. Users can't selectively enable one category of verbose output without getting all the others.

Motivating example: When running a comparison search, the output only shows variable bindings:

C:\code\Prime-Square-Sum>prime-square-sum.py --expr "for_any tri(n) == trisum(m)" --max n:50 --max m:50
Found: m=3, n=2
Found: m=4, n=2
Found: m=5, n=2
Found: m=10, n=36

Users often want to see what values satisfied the comparison — e.g., tri(2) = 3 == trisum(3) = 3 — to understand the relationship, not just the variable assignments. But there's no way to get this without enabling -v, which also dumps expression parsing, variable enumeration, bounds, timing, and other noise.

Proposed solution

Extend the verbosity system to support named channels alongside numeric levels:

# Numeric levels (existing behavior, unchanged)
-v              # level 1 — expression, variables, bounds, timing
-vv             # level 2 — config, algorithm selection
-vvv            # level 3 — debug

# Named channels (new)
-v vals          # show computed LHS/RHS values on match output
-v parse         # show parse tree details
-v iter          # show iteration progress
-v timing        # show timing breakdowns
-v config        # show configuration resolution
-v algorithm     # show algorithm selection details
-v hint          # show all hints regardless of context

# Combined: numeric level + specific channels
-vv vals         # level 2 + vals channel
-v vals timing   # level 1 + vals + timing channels

Channel activation rules

A message with emit(level, msg, channel='foo') displays if ANY of:

  1. Numeric verbosity >= level (existing behavior)
  2. Channel 'foo' is explicitly enabled via -v foo
  3. --quiet is NOT set

This means -v vals at verbosity level 0 would show ONLY the vals channel output (plus level-0 messages), without triggering all the level-1 noise.

First use case: vals channel

The vals channel would display computed LHS and RHS values alongside match output:

# Default (no -v)
Found: m=3, n=2

# With -v vals
Found: m=3, n=2 → tri(2) = 3 == trisum(3) = 3

# With -v vals (does_exist, single match)
Found: n=7 → primesum(7,2) = 666 == 666

Implementation for vals: find_matches() would need to evaluate LHS and RHS independently (currently it only evaluates the full comparison as a boolean) and include them in the yield dict (e.g., __lhs_value__, __rhs_value__). format_match() would then check if vals channel is enabled and append the value display.

Current channel inventory

These channels already exist internally (used in emit() calls) but are not user-accessible:

Channel Current usage Level
parse Expression string display 1
iter Variables, bounds 1
timing Match count, elapsed time 1
config Config loading, sieve settings, silence hint 1-2
algorithm Algorithm selection, memory limits 2
hint Contextual tips and suggestions 0
progress Match count during long runs 1
general Default/uncategorized varies
vals NEW — computed LHS/RHS values 1

CLI design

The -v flag currently uses action='count'. To support named channels, parsing would need to change:

# Option A: Extend -v to accept optional channel names
parser.add_argument('-v', '--verbose', nargs='*', action='append')
# -v → [None]  (level 1)
# -v -v → [None, None]  (level 2)
# -v vals → ['vals']  (level 0 + vals channel)
# -vv vals → parsed as level 2 + vals

# Option B: Separate flag for channels
parser.add_argument('-v', '--verbose', action='count', default=0)
parser.add_argument('--show', action='append', metavar='CHANNEL')
# -v --show vals
# --show vals --show timing

# Option C: Combined syntax with colon
parser.add_argument('-v', '--verbose', action='append')
# -v         (level 1)
# -v:vals    (enable vals channel)
# -vv:vals   (level 2 + vals)

Option A is cleanest for users but requires custom argument parsing. Option B is simplest to implement. Option C mirrors common logging conventions.

OutputManager changes

class OutputManager:
    def __init__(self, verbosity=0, quiet=False, channels=None, file=None):
        self.verbosity = verbosity
        self.quiet = quiet
        self.enabled_channels = set(channels or [])
        # ...

    def emit(self, level, message, *, channel='general', **kwargs):
        if self.quiet:
            return
        if self.verbosity < level and channel not in self.enabled_channels:
            return
        text = message.format(**kwargs) if kwargs else message
        print(text, file=self.file)

Documentation

This feature warrants its own documentation page (docs/verbosity.md or docs/output-channels.md) explaining:

  • The numeric level system and what each level shows
  • Available named channels and what they control
  • Combined usage patterns
  • Examples for common workflows (debugging, value inspection, performance analysis)
  • How channels interact with --quiet and --format json

Acceptance criteria

  • OutputManager supports enabled_channels set
  • emit() checks channel membership as alternative to numeric level
  • CLI flag syntax chosen and implemented (Option A, B, or C)
  • vals channel implemented as first named channel
    • find_matches() yields __lhs_value__ and __rhs_value__ for comparison matches
    • format_match() appends value display when vals channel is active
  • Existing numeric -v/-vv/-vvv behavior unchanged
  • Documentation page for output channels
  • Tests for channel-based filtering

Related issues

Analysis

See 2026-02-10__16-08-44__full-postmortem_issue-63-dynamic-cli-help-defaults.md for the session context where this feature was identified.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions