Skip to content
This repository was archived by the owner on Apr 11, 2026. It is now read-only.

Commit a22b9aa

Browse files
z23ccclaude
andcommitted
feat(compress): port rtk TOML filter engine to flowctl-core
Ported rtk's (https://github.com/rtk-ai/rtk, Apache 2.0) TOML filter DSL to flowctl-core/src/compress.rs (684 lines). Integrated into output pipeline for 3 POC commands via TTY auto-detection. Measured savings vs pretty baseline (tiktoken cl100k_base equivalent): epics: 1973 → 769 bytes (-61%) tasks: 1539 → 594 bytes (-61%) status: 97 → 25 bytes (-74%) Scope: - NEW flowctl-core/src/compress.rs (ported engine, 8-stage pipeline) - NEW flowctl-core/LICENSE-APACHE-rtk (Apache 2.0 attribution) - NEW flowctl-core/src/filters/{epics,tasks,status}.toml (filter configs) - MOD flowctl-cli/src/output.rs (added pretty_output() compact path) - MOD flowctl-cli/src/commands/query.rs (cmd_epics, cmd_tasks wired) - MOD flowctl-cli/src/commands/admin/status.rs (cmd_status wired) - FIX flowctl/tests/cmd/mcp_initialize.toml (stale version 0.1.26 → 0.1.29) Filter pipeline (8 stages): strip_ansi → replace → match_output → strip/keep → truncate → head/tail → max_lines → on_empty. Inline [[tests.X]] pattern from rtk preserved (12 compress tests, all passing). Zero changes to flowctl-db/-service/-daemon/-scheduler, ID system, spec format, hooks, skills, agents. Backward compat preserved: --json without --compact output unchanged (2418 bytes same as baseline). cargo test --all: green. Task: fn-12-token-compact-rtk-guard.16 (POC) Audit: docs/token-optimization-audit.md (541-line analysis, 47 items) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 26a06b7 commit a22b9aa

13 files changed

Lines changed: 1686 additions & 22 deletions

File tree

docs/token-optimization-audit.md

Lines changed: 541 additions & 0 deletions
Large diffs are not rendered by default.

flowctl/Cargo.lock

Lines changed: 56 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flowctl/crates/flowctl-cli/src/commands/admin/status.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::process::Command;
77

88
use serde_json::json;
99

10-
use crate::output::{error_exit, json_output};
10+
use crate::output::{error_exit, json_output, pretty_output};
1111

1212
use flowctl_core::types::{
1313
CONFIG_FILE, EPICS_DIR, MEMORY_DIR, META_FILE, REVIEWS_DIR, SCHEMA_VERSION,
@@ -180,19 +180,26 @@ pub fn cmd_status(json: bool, interrupted: bool) {
180180
} else if !flow_exists {
181181
println!(".flow/ not initialized");
182182
} else {
183-
println!(
183+
use std::fmt::Write as _;
184+
let mut buf = String::new();
185+
writeln!(
186+
buf,
184187
"Epics: {} open, {} done",
185188
epic_counts["open"], epic_counts["done"]
186-
);
187-
println!(
189+
)
190+
.ok();
191+
writeln!(
192+
buf,
188193
"Tasks: {} todo, {} in_progress, {} done, {} blocked",
189194
task_counts["todo"],
190195
task_counts["in_progress"],
191196
task_counts["done"],
192197
task_counts["blocked"]
193-
);
194-
println!();
195-
println!("No active runs");
198+
)
199+
.ok();
200+
writeln!(buf).ok();
201+
writeln!(buf, "No active runs").ok();
202+
pretty_output("status", &buf);
196203
}
197204
}
198205

flowctl/crates/flowctl-cli/src/commands/query.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::path::{Path, PathBuf};
88

99
use serde_json::json;
1010

11-
use crate::output::{error_exit, json_output};
11+
use crate::output::{error_exit, json_output, pretty_output};
1212

1313
use flowctl_core::frontmatter;
1414
use flowctl_core::id::{is_epic_id, is_task_id, parse_id};
@@ -437,7 +437,9 @@ pub fn cmd_epics(json: bool) {
437437
} else if epics_out.is_empty() {
438438
println!("No epics found.");
439439
} else {
440-
println!("Epics ({}):\n", epics_out.len());
440+
use std::fmt::Write as _;
441+
let mut buf = String::new();
442+
writeln!(buf, "Epics ({}):\n", epics_out.len()).ok();
441443
for e in &epics_out {
442444
let tasks = e["tasks"].as_u64().unwrap_or(0);
443445
let done = e["done"].as_u64().unwrap_or(0);
@@ -446,14 +448,17 @@ pub fn cmd_epics(json: bool) {
446448
} else {
447449
"0/0".to_string()
448450
};
449-
println!(
451+
writeln!(
452+
buf,
450453
" [{}] {}: {} ({} tasks done)",
451454
e["status"].as_str().unwrap_or(""),
452455
e["id"].as_str().unwrap_or(""),
453456
e["title"].as_str().unwrap_or(""),
454457
progress
455-
);
458+
)
459+
.ok();
456460
}
461+
pretty_output("epics", &buf);
457462
}
458463
}
459464

@@ -486,8 +491,10 @@ pub fn cmd_tasks(
486491
let status_filter = status.as_ref().map(|s| format!(" with status '{}'", s)).unwrap_or_default();
487492
println!("No tasks found{}{}.", scope, status_filter);
488493
} else {
494+
use std::fmt::Write as _;
489495
let scope = epic.as_ref().map(|e| format!(" for {}", e)).unwrap_or_default();
490-
println!("Tasks{} ({}):\n", scope, tasks_out.len());
496+
let mut buf = String::new();
497+
writeln!(buf, "Tasks{} ({}):\n", scope, tasks_out.len()).ok();
491498
for t in &tasks {
492499
let deps = if t.depends_on.is_empty() {
493500
String::new()
@@ -499,11 +506,14 @@ pub fn cmd_tasks(
499506
} else {
500507
String::new()
501508
};
502-
println!(
509+
writeln!(
510+
buf,
503511
" [{}] {}: {}{}{}",
504512
t.status, t.id, t.title, domain_tag, deps
505-
);
513+
)
514+
.ok();
506515
}
516+
pretty_output("tasks", &buf);
507517
}
508518
}
509519

flowctl/crates/flowctl-cli/src/output.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,33 @@ pub fn init_compact(explicit: bool) {
3838
COMPACT.store(enabled, Ordering::Relaxed);
3939
}
4040

41+
/// Is compact mode currently active?
42+
pub fn is_compact() -> bool {
43+
COMPACT.load(Ordering::Relaxed)
44+
}
45+
46+
/// Print pretty (non-JSON) output, routed through a built-in compress filter when
47+
/// compact mode is active. Falls back to the raw text if the named filter does not
48+
/// exist (backward-compat — passthrough on miss).
49+
///
50+
/// The `filter_name` must correspond to a filter embedded in `flowctl-core/src/filters/`.
51+
pub fn pretty_output(filter_name: &str, text: &str) {
52+
if is_compact() {
53+
if let Some(filtered) = flowctl_core::compress::apply_filter(filter_name, text) {
54+
if filtered.is_empty() {
55+
// empty filter output — nothing to print
56+
return;
57+
}
58+
println!("{}", filtered);
59+
return;
60+
}
61+
}
62+
print!("{}", text);
63+
if !text.ends_with('\n') {
64+
println!();
65+
}
66+
}
67+
4168
/// Strip compact fields from a JSON value (recursive into arrays/objects).
4269
fn strip_compact(val: &mut Value) {
4370
match val {
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[package]
22
name = "flowctl-core"
33
version = "0.1.0"
4-
description = "Core types, ID parsing, and state machine for flowctl"
4+
description = "Core types, ID parsing, and state machine for flowctl (includes Apache 2.0 licensed code ported from rtk)"
55
edition.workspace = true
66
rust-version.workspace = true
7-
license.workspace = true
7+
license = "MIT OR Apache-2.0"
88

99
[dependencies]
1010
serde = { workspace = true }
@@ -14,5 +14,6 @@ thiserror = { workspace = true }
1414
chrono = { workspace = true }
1515
regex = { workspace = true }
1616
petgraph = { workspace = true }
17+
toml = "0.8"
1718

1819
[dev-dependencies]

0 commit comments

Comments
 (0)