Files
Kigi-CLI/crates/codegen/kigi-tui/src/app/acp_handler/tests/goals.rs
T
ZacharyZhang-NY a02b555e66 docs(comments): rewrite comments across all crates to the guidelines
Sweep every first-party crate source (1956 .rs files) to the project comment
guidelines: delete redundant restatements, decorative banners, change
narration, and end-of-line comments; keep and tighten the crucial ones
(invariants, bug rationale, SAFETY blocks, ported-source attribution).

No functional code changed. Every edit is proven comment-only against the
prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a
separate doctest-fence check. Where removing a comment made rustfmt or clippy
want to re-lay-out adjacent code, the minimal triggering comment is restored so
code tokens stay byte-identical.

Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy
--workspace --all-targets (0 warnings).

Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for
these guidelines (flags banners, end-of-line comments, change narration, and
commented-out code).
2026-07-23 16:55:39 -04:00

424 lines
17 KiB
Rust

#![cfg_attr(rustfmt, rustfmt::skip)]
use super::*;
#[test]
fn goal_updated_ignores_unknown_json_fields_via_serde() {
// Serde-side half of the forward-compat story: a payload that
// carries an extra JSON field absent on today's
// `SessionUpdate::GoalUpdated` (no `deny_unknown_fields` on the
// variant) must still deserialize and drive a full
// `GoalDisplayState`. This guards against someone later adding
// `#[serde(deny_unknown_fields)]` to the variant, which would
// silently break wire compatibility with older shells.
//
// The complementary Rust-level half — that the destructure with
// trailing `..` keeps absent additive `Option<T>` fields landing
// as `None` in the mapped `GoalDisplayState` — is exercised by
// `goal_updated_absent_optional_fields_deserialize_to_none`.
let mut app = make_app_with_agent("sess-A");
let raw_payload = serde_json::json!({
"sessionId": "sess-A",
"update": {
"sessionUpdate": "goal_updated",
"goal_id": "g-ext",
"objective": "build forward-compat tolerance",
"status": "active",
"phase": "executing",
"token_budget": 200_000,
"tokens_used": 12_345,
"elapsed_ms": 750,
"total_deliverables": 2,
"completed_deliverables": 1,
"current_deliverable_idx": 1,
"current_deliverable_title": "Wire compat",
"current_subagent_role": "verifier",
"total_worker_rounds": 5,
"total_verify_rounds": 2,
"token_baseline": 100,
"finished_subagent_tokens": 99,
"live_subagent_tokens": 4_321,
"live_tokens_by_model": [["kigi-4", 6_000], ["kigi-3", 4_000]],
"live_context_pct": 42,
"live_turn_count": 7,
"live_tool_call_count": 11,
"last_event": "verify_started",
"last_event_detail": "round 2 of 3",
"last_event_timestamp": "2026-05-24T00:00:00Z",
"future_field_for_pr5": "ignored-by-todays-pager"
}
});
let raw = serde_json::value::to_raw_value(&raw_payload).unwrap();
let request = acp::ExtNotification::new("kigi/session_notification", raw.into());
let (tx, _rx) = tokio::sync::oneshot::channel();
let msg = AcpClientMessage::ExtNotification(kigi_acp_lib::AcpArgs {
request,
response_tx: tx,
});
let affected = handle(msg, &mut app);
assert!(
affected,
"GoalUpdated for the active agent must request a redraw"
);
let agent = app.agents.get(&AgentId(0)).unwrap();
let goal = agent
.goal_state
.as_ref()
.expect("GoalUpdated should populate goal_state even with unknown wire fields");
assert_eq!(goal.goal_id, "g-ext");
assert_eq!(goal.objective, "build forward-compat tolerance");
assert_eq!(goal.status, GoalDisplayStatus::Active);
assert_eq!(goal.phase, GoalDisplayPhase::Executing);
assert_eq!(goal.token_budget, Some(200_000));
assert_eq!(goal.tokens_used, 12_345);
assert_eq!(goal.elapsed_ms, 750);
assert_eq!(goal.total_deliverables, 2);
assert_eq!(goal.completed_deliverables, 1);
assert_eq!(goal.current_deliverable_id, Some(1));
assert_eq!(
goal.current_deliverable_title.as_deref(),
Some("Wire compat")
);
assert_eq!(goal.current_subagent_role.as_deref(), Some("verifier"));
assert_eq!(goal.total_worker_rounds, 5);
assert_eq!(goal.total_verify_rounds, 2);
assert_eq!(goal.token_baseline, 100);
assert_eq!(goal.finished_subagent_tokens, 99);
assert_eq!(goal.live_subagent_tokens, Some(4_321));
assert_eq!(
goal.live_tokens_by_model,
vec![("kigi-4".to_owned(), 6_000), ("kigi-3".to_owned(), 4_000)],
"populated per-model breakdown must round-trip wire->display"
);
assert_eq!(goal.live_context_pct, Some(42));
assert_eq!(goal.live_turn_count, Some(7));
assert_eq!(goal.live_tool_call_count, Some(11));
assert_eq!(goal.last_event.as_deref(), Some("verify_started"));
assert_eq!(goal.last_event_detail.as_deref(), Some("round 2 of 3"));
assert_eq!(
goal.last_event_timestamp.as_deref(),
Some("2026-05-24T00:00:00Z")
);
assert_eq!(goal.pause_message, None);
assert_eq!(goal.classifier_runs_attempted, None);
assert_eq!(goal.classifier_max_runs, None);
assert_eq!(goal.last_classifier_verdict, None);
assert_eq!(goal.last_classifier_details_path, None);
assert!(!goal.verifying_completion);
assert!(!goal.planning);
assert!(
goal.deliverables.is_empty(),
"deliverables is wire-compat-only in the simplified goal model"
);
}
#[test]
fn goal_complete_transition_pushes_end_to_end_marker_once() {
let mut app = make_app_with_agent("sess-A");
let send = |app: &mut AppView, status: &str, elapsed_ms: u64| {
let raw_payload = serde_json::json!({
"sessionId": "sess-A",
"update": {
"sessionUpdate": "goal_updated",
"goal_id": "g1",
"objective": "obj",
"status": status,
"phase": "executing",
"tokens_used": 0,
"elapsed_ms": elapsed_ms,
"total_deliverables": 0,
"completed_deliverables": 0,
"total_worker_rounds": 0,
"total_verify_rounds": 0,
"token_baseline": 0,
"finished_subagent_tokens": 0,
}
});
let raw = serde_json::value::to_raw_value(&raw_payload).unwrap();
let (tx, _rx) = tokio::sync::oneshot::channel();
handle(
AcpClientMessage::ExtNotification(kigi_acp_lib::AcpArgs {
request: acp::ExtNotification::new("kigi/session_notification", raw.into()),
response_tx: tx,
}),
app,
);
};
let goal_markers = |app: &AppView| -> Vec<std::time::Duration> {
let sb = &app.agents.get(&AgentId(0)).unwrap().scrollback;
(0..sb.len())
.filter_map(|i| match sb.get(i).map(|e| &e.block) {
Some(RenderBlock::SessionEvent(b)) => match &b.event {
SessionEvent::GoalCompleted { elapsed } => Some(*elapsed),
_ => None,
},
_ => None,
})
.collect()
};
send(&mut app, "active", 1_000);
assert!(goal_markers(&app).is_empty(), "no marker while Active");
send(&mut app, "complete", 619_000);
assert_eq!(
goal_markers(&app),
vec![std::time::Duration::from_millis(619_000)],
"transition to Complete pushes one e2e marker with the goal's total time",
);
send(&mut app, "complete", 620_000);
assert_eq!(
goal_markers(&app).len(),
1,
"repeat Complete must not push a second marker",
);
}
#[test]
fn goal_elapsed_is_monotonic_across_updates() {
// The displayed elapsed must never tick backward when a notification's
// authoritative base is below the already-extrapolated value;
// `elapsed_floor_ms` clamps it.
let mut app = make_app_with_agent("sess-A");
assert!(send_goal_update(&mut app, "g1", "active", 10_000));
let a = app
.agents
.get(&AgentId(0))
.unwrap()
.goal_state
.as_ref()
.unwrap()
.live_elapsed_ms();
assert!(a >= 10_000);
// Same goal, but a LOWER authoritative base (extrapolation outran the
// shell's flush point).
send_goal_update(&mut app, "g1", "active", 8_000);
let b = app
.agents
.get(&AgentId(0))
.unwrap()
.goal_state
.as_ref()
.unwrap()
.live_elapsed_ms();
assert!(b >= a, "elapsed must not tick backward: {b} < {a}");
assert!(b >= 10_000);
}
#[test]
fn cleared_goal_is_not_resurrected_by_late_update() {
// After a goal is cleared, a late in-flight GoalUpdated for the same
// goal_id (queued before the clear) must be dropped so the "Done"
// chip / modal stay cleared and don't resurrect.
let mut app = make_app_with_agent("sess-A");
send_goal_update(&mut app, "g1", "complete", 5_000);
assert!(
app.agents.get(&AgentId(0)).unwrap().goal_state.is_some(),
"goal present after complete"
);
// Clear (the cleared event itself carries an empty goal_id).
send_goal_update(&mut app, "", "cleared", 0);
assert!(
app.agents.get(&AgentId(0)).unwrap().goal_state.is_none(),
"chip cleared on cleared status"
);
let affected = send_goal_update(&mut app, "g1", "complete", 5_000);
assert!(
app.agents.get(&AgentId(0)).unwrap().goal_state.is_none(),
"cleared goal must not resurrect"
);
assert!(!affected, "ignored stale update must not request a redraw");
}
#[test]
fn new_goal_after_clear_is_not_suppressed() {
// A genuinely new goal (different id) after a clear must start
// normally — the cleared-id guard only drops the SAME id.
let mut app = make_app_with_agent("sess-A");
send_goal_update(&mut app, "g1", "active", 1_000);
send_goal_update(&mut app, "", "cleared", 0);
assert!(send_goal_update(&mut app, "g2", "active", 500));
let agent = app.agents.get(&AgentId(0)).unwrap();
assert_eq!(
agent.goal_state.as_ref().expect("new goal present").goal_id,
"g2"
);
}
#[test]
fn goal_switch_resets_elapsed_floor() {
// A NEW goal (different id) must start its own clock and NOT inherit
// the prior goal's carried elapsed floor.
let mut app = make_app_with_agent("sess-A");
send_goal_update(&mut app, "g1", "active", 10_000);
send_goal_update(&mut app, "g2", "active", 500);
let elapsed = app
.agents
.get(&AgentId(0))
.unwrap()
.goal_state
.as_ref()
.unwrap()
.live_elapsed_ms();
assert!(
elapsed < 5_000,
"new goal must start from its own base, not the prior 10s floor: {elapsed}"
);
}
#[test]
fn goal_updated_resolves_details_path_existence_on_receipt() {
// The handler resolves last_classifier_details_path's existence ONCE
// on receipt into the cached bool (no per-frame stat).
let mut app = make_app_with_agent("sess-A");
let f = tempfile::NamedTempFile::new().unwrap();
let real_path = f.path().to_string_lossy().into_owned();
let mut update = goal_update_value("g1", "active", 0);
update["last_classifier_details_path"] = serde_json::json!(real_path);
dispatch_goal_update(&mut app, update);
let g = app
.agents
.get(&AgentId(0))
.unwrap()
.goal_state
.as_ref()
.unwrap();
assert!(
g.last_classifier_details_exists,
"existing details path must cache exists = true"
);
assert_eq!(
g.last_classifier_details_path.as_deref(),
Some(real_path.as_str())
);
let mut update = goal_update_value("g1", "active", 0);
update["last_classifier_details_path"] = serde_json::json!("/no/such/details-xyz.md");
dispatch_goal_update(&mut app, update);
let g = app
.agents
.get(&AgentId(0))
.unwrap()
.goal_state
.as_ref()
.unwrap();
assert!(
!g.last_classifier_details_exists,
"missing details path must cache exists = false"
);
}
#[test]
fn goal_updated_absent_optional_fields_deserialize_to_none() {
// Rust-level forward-compat half: every additive
// `Option<T>` field on `SessionUpdate::GoalUpdated` is allowed to
// be omitted from the wire payload and must surface as `None` in
// the destructured arm — i.e. the pager keeps mapping the known
// subset cleanly when the shell-side struct grows or when an
// older shell omits newer optional fields.
let mut app = make_app_with_agent("sess-A");
let raw_payload = serde_json::json!({
"sessionId": "sess-A",
"update": {
"sessionUpdate": "goal_updated",
"goal_id": "g-min",
"objective": "minimal payload",
"status": "active",
"phase": "idle",
"tokens_used": 0,
"elapsed_ms": 0,
"total_deliverables": 0,
"completed_deliverables": 0,
"total_worker_rounds": 0,
"total_verify_rounds": 0,
"token_baseline": 0,
"finished_subagent_tokens": 0,
}
});
let raw = serde_json::value::to_raw_value(&raw_payload).unwrap();
let request = acp::ExtNotification::new("kigi/session_notification", raw.into());
let (tx, _rx) = tokio::sync::oneshot::channel();
let msg = AcpClientMessage::ExtNotification(kigi_acp_lib::AcpArgs {
request,
response_tx: tx,
});
let affected = handle(msg, &mut app);
assert!(
affected,
"minimal GoalUpdated for the active agent must request a redraw"
);
let agent = app.agents.get(&AgentId(0)).unwrap();
let goal = agent
.goal_state
.as_ref()
.expect("GoalUpdated must populate goal_state even with all Option fields omitted");
assert_eq!(goal.goal_id, "g-min");
assert_eq!(goal.objective, "minimal payload");
assert_eq!(goal.status, GoalDisplayStatus::Active);
assert_eq!(goal.phase, GoalDisplayPhase::Idle);
assert_eq!(goal.tokens_used, 0);
assert_eq!(goal.elapsed_ms, 0);
assert_eq!(goal.total_deliverables, 0);
assert_eq!(goal.completed_deliverables, 0);
assert_eq!(goal.total_worker_rounds, 0);
assert_eq!(goal.total_verify_rounds, 0);
assert_eq!(goal.token_baseline, 0);
assert_eq!(goal.finished_subagent_tokens, 0);
assert_eq!(goal.token_budget, None, "token_budget");
assert_eq!(goal.current_deliverable_id, None, "current_deliverable_id");
assert_eq!(
goal.current_deliverable_title, None,
"current_deliverable_title"
);
assert_eq!(goal.current_subagent_role, None, "current_subagent_role");
assert_eq!(goal.live_subagent_tokens, None, "live_subagent_tokens");
assert!(
goal.live_tokens_by_model.is_empty(),
"omitted live_tokens_by_model must default to empty via #[serde(default)]"
);
assert_eq!(goal.live_context_pct, None, "live_context_pct");
assert_eq!(goal.live_turn_count, None, "live_turn_count");
assert_eq!(goal.live_tool_call_count, None, "live_tool_call_count");
assert_eq!(goal.last_event, None, "last_event");
assert_eq!(goal.last_event_detail, None, "last_event_detail");
assert_eq!(goal.last_event_timestamp, None, "last_event_timestamp");
assert_eq!(goal.pause_message, None, "pause_message");
assert_eq!(
goal.classifier_runs_attempted, None,
"classifier_runs_attempted"
);
assert_eq!(goal.classifier_max_runs, None, "classifier_max_runs");
assert_eq!(
goal.last_classifier_verdict, None,
"last_classifier_verdict"
);
assert_eq!(
goal.last_classifier_details_path, None,
"last_classifier_details_path"
);
assert!(
!goal.verifying_completion,
"verifying_completion defaults to false"
);
assert!(!goal.planning, "planning defaults to false");
assert!(
goal.deliverables.is_empty(),
"deliverables is wire-compat-only in the simplified goal model"
);
}