Bead: bd-19e Author: RedStone (claude-code / opus-4.5) Date: 2026-01-23 Status: Complete
| File | Key Functions/Types | Purpose |
|---|---|---|
cmd/bv/main.go:6583-6592 |
newRobotEncoder(w io.Writer) |
Central JSON encoder factory |
cmd/bv/main.go:57-169 |
25+ --robot-* flags |
Robot mode flag definitions |
// cmd/bv/main.go:6583-6592
func newRobotEncoder(w io.Writer) *json.Encoder {
encoder := json.NewEncoder(w)
if os.Getenv("BV_PRETTY_JSON") == "1" {
encoder.SetIndent("", " ")
}
return encoder
}| Flag | Line | Data Type | Purpose |
|---|---|---|---|
--robot-triage |
61 | TriageResult |
Unified triage for agents |
--robot-next |
64 | TopPick |
Single top recommendation |
--robot-insights |
58 | []Insight |
Graph analysis insights |
--robot-plan |
59 | []PlanItem |
Execution plan |
--robot-priority |
60 | []Recommendation |
Priority recommendations |
--robot-recipes |
66 | []RecipeSummary |
Available recipes |
--robot-diff |
65 | DiffResult |
Changes since historical point |
--robot-label-health |
67 | LabelHealth |
Label health metrics |
--robot-label-flow |
68 | LabelFlow |
Cross-label dependencies |
--robot-label-attention |
69 | []LabelAttention |
Attention-ranked labels |
--robot-alerts |
71 | []Alert |
Drift + proactive alerts |
--robot-metrics |
72 | Metrics |
Performance metrics |
--robot-suggest |
74 | []Suggestion |
Smart suggestions |
--robot-graph |
79 | Graph |
Dependency graph |
--robot-search |
100 | []SearchResult |
Semantic search results |
--robot-drift |
116 | DriftResult |
Drift check results |
--robot-history |
117 | []Correlation |
Bead-commit correlations |
--robot-orphans |
130 | []OrphanCandidate |
Orphan commit candidates |
--robot-file-beads |
133 | []FileBead |
Beads that touched a file |
--robot-file-hotspots |
135 | []FileHotspot |
High-activity files |
--robot-impact |
138 | ImpactAnalysis |
File modification impact |
--robot-file-relations |
140 | []FileRelation |
Co-change detection |
--robot-related |
144 | RelatedWork |
Related beads |
--robot-blocker-chain |
149 | BlockerChain |
Full blocker chain |
--robot-impact-network |
151 | ImpactNetwork |
Bead impact network |
--robot-causality |
154 | CausalChain |
Temporal causality |
--robot-sprint-list |
156 | []Sprint |
Sprint list |
--robot-sprint-show |
157 | SprintDetails |
Sprint details |
--robot-forecast |
159 | Forecast |
ETA forecast |
--robot-capacity |
164 | CapacitySimulation |
Capacity projection |
--robot-burndown |
168 | BurndownData |
Sprint burndown |
type TriageResult struct {
Meta TriageMeta `json:"meta"`
QuickRef QuickRef `json:"quick_ref"`
Recommendations []Recommendation `json:"recommendations"`
QuickWins []QuickWin `json:"quick_wins"`
BlockersToClear []BlockerItem `json:"blockers_to_clear"`
ProjectHealth ProjectHealth `json:"project_health"`
Alerts []Alert `json:"alerts,omitempty"`
Commands CommandHelpers `json:"commands"`
RecommendationsByTrack []TrackRecommendationGroup `json:"recommendations_by_track,omitempty"`
RecommendationsByLabel []LabelRecommendationGroup `json:"recommendations_by_label,omitempty"`
}// Add near line 57 with other robot flags
outputFormat := flag.String("format", "",
"Structured output format for --robot-* commands: json or toon")// In newRobotEncoder or new helper
func getRobotFormat() string {
if format := os.Getenv("BV_OUTPUT_FORMAT"); format != "" {
return format
}
if format := os.Getenv("TOON_DEFAULT_FORMAT"); format != "" {
return format
}
return "json"
}- CLI flag
--format toon(highest) - Environment variable
BV_OUTPUT_FORMAT=toon - Environment variable
TOON_DEFAULT_FORMAT=toon - Default:
json
Key Decision: Do not embed a Go encoder. Shell out to the toon_rust tru
binary (installed as tru to avoid conflicting with coreutils tr) for
canonical TOON output across tools. Never use the Node.js toon CLI.
Use the helper from /data/projects/templates/toon_go_template.go to resolve
TOON_TRU_BIN and verify the binary:
// pkg/robot/toon.go
package robot
func encodeToon(payload any) (string, error) {
jsonBytes, err := json.Marshal(payload)
if err != nil {
return "", err
}
trPath, err := getToonBinary()
if err != nil {
return "", err
}
cmd := exec.Command(trPath)
cmd.Stdin = bytes.NewReader(jsonBytes)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("toon_rust encode failed: %s", strings.TrimSpace(stderr.String()))
}
return stdout.String(), nil
}// pkg/robot/format.go
type Format string
const (
FormatJSON Format = "json"
FormatTOON Format = "toon"
FormatAuto Format = "auto" // Try TOON, fall back to JSON on errors
)
func Encode(payload any, format Format, w io.Writer) error {
switch format {
case FormatTOON:
output, err := encodeToon(payload)
if err != nil {
return err // Caller decides fallback
}
_, err = w.Write([]byte(output))
return err
case FormatAuto:
output, err := encodeToon(payload)
if err != nil {
// Fall back to JSON if toon_rust fails
return json.NewEncoder(w).Encode(payload)
}
_, err = w.Write([]byte(output))
return err
default:
return json.NewEncoder(w).Encode(payload)
}
}func newRobotEncoder(w io.Writer, format string) *robotEncoder {
return &robotEncoder{
w: w,
format: Format(format),
pretty: os.Getenv("BV_PRETTY_JSON") == "1",
}
}
type robotEncoder struct {
w io.Writer
format Format
pretty bool
}
func (e *robotEncoder) Encode(v any) error {
switch e.format {
case FormatTOON, FormatAuto:
return robot.Encode(v, e.format, e.w)
default:
enc := json.NewEncoder(e.w)
if e.pretty {
enc.SetIndent("", " ")
}
return enc.Encode(v)
}
}Files written to disk (not stdout) should remain JSON:
triage.jsonin--agent-briefinsights.jsonin--agent-brief- Interactive graph exports
Rationale:
- File exports may be consumed by other tools expecting JSON
- TOON is primarily for reducing tokens in agent context windows
- Files aren't affected by token limits
TOON format applies only to robot mode stdout output when --format toon is specified.
Add to flag definitions:
outputFormat := flag.String("format", "",
"Structured output format for --robot-* commands: json or toon")### Token-Optimized Output (TOON)
For AI agents, TOON format reduces token usage by ~50%:
```bash
# TOON output (compact, agent-friendly)
bv --robot-triage --format toon
bv --robot-insights --format toon
# Environment variable
export BV_OUTPUT_FORMAT=toon
bv --robot-next # Uses TOON automatically
### --robot-help Output
Add to robot help text:
OUTPUT FORMAT --format toon Token-optimized output (~50% fewer tokens than JSON) BV_OUTPUT_FORMAT=toon Environment variable alternative
---
## 6. Sample Outputs for Fixtures
### Fixture: `bv --robot-triage --format toon`
**JSON (current):**
```json
{
"meta": {
"version": "2.0",
"generated_at": "2026-01-23T22:00:00Z",
"phase2_ready": true,
"issue_count": 158,
"compute_time_ms": 42
},
"quick_ref": {
"open_count": 95,
"actionable_count": 49,
"blocked_count": 47,
"in_progress_count": 8,
"top_picks": [
{"id": "bd-r9m", "title": "Define TOON conventions", "score": 0.95, "reasons": ["foundation", "unblocks 3"], "unblocks": 3}
]
},
"recommendations": [...]
}
Expected TOON:
meta:
version: 2.0
generated_at: 2026-01-23T22:00:00Z
phase2_ready: true
issue_count: 158
compute_time_ms: 42
quick_ref:
open_count: 95
actionable_count: 49
blocked_count: 47
in_progress_count: 8
top_picks[1]{id,reasons,score,title,unblocks}:
bd-r9m,"[\"foundation\",\"unblocks 3\"]",0.95,Define TOON conventions,3
recommendations[N]{...}:
...
JSON:
{"id":"bd-r9m","title":"Define TOON conventions","score":0.95,"reasons":["foundation","unblocks 3"],"unblocks":3}TOON:
id: bd-r9m
title: Define TOON conventions
score: 0.95
reasons[2]: foundation,unblocks 3
unblocks: 3
Expected TOON (tabular):
[5]{category,id,message,severity}:
graph,insight-1,Cycle detected in dependencies,warning
health,insight-2,47 blocked issues (30%),info
velocity,insight-3,Closure rate increased 20%,info
priority,insight-4,3 high-priority blockers,warning
stale,insight-5,12 issues unchanged >30d,info
| File | Change |
|---|---|
pkg/robot/toon.go |
NEW: toon_rust tru wrapper (see templates/toon_go_template.go) |
pkg/robot/format.go |
NEW: Format enum, Encode function |
cmd/bv/main.go |
Add --format flag, modify newRobotEncoder |
The existing pattern is consistent across all 35+ robot commands:
encoder := newRobotEncoder(os.Stdout)
if err := encoder.Encode(output); err != nil { ... }After modifying newRobotEncoder to accept format parameter:
encoder := newRobotEncoder(os.Stdout, *robotFormat)All 35+ callsites need this one-line change.
- Round-trip test: Encode JSON → TOON → decode → compare to original
- Regression test:
--format jsonoutput unchanged - Token count test: Verify TOON uses fewer tokens
- JSON output remains the default
-
BV_PRETTY_JSON=1behavior unchanged - All existing robot flags work identically
- File exports (--agent-brief, --export-*) remain JSON
- New flag is optional and additive
- Environment variable support for automation
Based on typical bv output patterns:
| Command | JSON tokens (est.) | TOON tokens (est.) | Savings |
|---|---|---|---|
--robot-triage (full) |
~3000 | ~1400 | 53% |
--robot-next |
~80 | ~40 | 50% |
--robot-insights (10) |
~600 | ~280 | 53% |
--robot-plan (20 items) |
~1200 | ~550 | 54% |
--robot-priority (10) |
~800 | ~380 | 52% |
--robot-alerts (5) |
~400 | ~190 | 52% |
Aggregate estimate: 50-55% token reduction across robot commands.
For agent-heavy workflows querying bv frequently (triage, next, priority), this significantly reduces context window consumption.
Use the helper from /data/projects/templates/toon_go_template.go to resolve the
toon_rust tru binary via TOON_TRU_BIN, and shell out for
encoding. This keeps the encoder canonical and avoids a Go reimplementation.
If TOON becomes a shared Go module:
import "github.com/Dicklesworthstone/toon-go"Recommendation: Option A for now. It keeps bv aligned with toon_rust and avoids divergence from the canonical implementation.
// pkg/robot/toon_test.go
func TestTriageResultTOON(t *testing.T) {
triage := analysis.TriageResult{...}
output, err := encodeToon(triage)
require.NoError(t, err)
// Verify output structure
}
func TestTabularArray(t *testing.T) {
recs := []analysis.Recommendation{{...}, {...}}
output, err := encodeToon(recs)
require.NoError(t, err)
require.Contains(t, output, "[2]{") // Tabular header
}
func TestRoundTrip(t *testing.T) {
// Encode to TOON, decode with tru, compare to original JSON
}#!/bin/bash
set -euo pipefail
# Compare JSON vs TOON semantic equivalence
bv --robot-triage > /tmp/triage.json
bv --robot-triage --format toon > /tmp/triage.toon
TOON_TRU_BIN="${TOON_TRU_BIN:-tru}"
"$TOON_TRU_BIN" --decode /tmp/triage.toon > /tmp/triage_decoded.json
diff <(jq -S . /tmp/triage.json) <(jq -S . /tmp/triage_decoded.json)
echo "TOON round-trip OK"We no longer copy the ntm encoder. Use the toon_rust tru binary instead:
- Full spec support: primitives, arrays, nested objects
- Tabular arrays: Uniform object arrays →
[N]{fields}: rows - Canonical output: consistent across tools/languages
This covers all bv robot output shapes:
TriageResult→ nested object ✓[]Recommendation→ tabular array ✓TopPick→ simple object ✓ProjectHealth→ nested object ✓
No custom encoder needed. Wire encodeToon into newRobotEncoder and let toon_rust handle the format.