diff --git a/crates/skilllite-commands/src/evolution_status.rs b/crates/skilllite-commands/src/evolution_status.rs index e5b31aa..330d04d 100644 --- a/crates/skilllite-commands/src/evolution_status.rs +++ b/crates/skilllite-commands/src/evolution_status.rs @@ -145,6 +145,14 @@ fn existing_workspace_skills_root(workspace_root: &Path) -> Option { skills_root.is_dir().then_some(skills_root) } +fn shorten_status_reason(reason: &str) -> String { + if reason.chars().count() > 50 { + format!("{}...", reason.chars().take(47).collect::()) + } else { + reason.to_string() + } +} + /// Build evolution status snapshot (workspace `.env` + process env; no desktop UI overrides). pub fn build_evolution_status_snapshot(params: &EvolutionStatusParams) -> EvolutionStatusSnapshot { let workspace_root = resolve_workspace_root(¶ms.workspace); @@ -405,11 +413,7 @@ fn cmd_status_human(workspace: &str) -> Result<()> { t if t.contains("rolled_back") => "🔙", _ => " ", }; - let reason_short = if reason.len() > 50 { - format!("{}...", &reason[..47]) - } else { - reason - }; + let reason_short = shorten_status_reason(&reason); println!(" {} {} {} {}", icon, date, etype, reason_short); if !target.is_empty() { println!(" └─ target: {}", target); @@ -497,6 +501,29 @@ mod workspace_scope_tests { let _ = std::fs::remove_dir_all(env_workspace); let _ = std::fs::remove_dir_all(target_workspace); } + + #[test] + fn status_human_handles_non_ascii_event_reasons() { + let workspace = temp_workspace("utf8-reason"); + let chat_root = workspace.join("chat"); + let conn = skilllite_evolution::feedback::open_evolution_db(&chat_root).expect("open db"); + let reason = + "用户手动触发进化,原因包含中文和emoji🙂,需要展示最近事件摘要而不能因为截断崩溃。" + .repeat(2); + skilllite_evolution::log_evolution_event( + &conn, + &chat_root, + "manual_evolution_run_triggered", + "all", + &reason, + "txn-utf8", + ) + .expect("insert log event"); + + cmd_status(false, workspace.to_string_lossy().as_ref(), None).expect("status succeeds"); + + let _ = std::fs::remove_dir_all(workspace); + } } #[cfg(test)] diff --git a/tasks/TASK-2026-069-evolution-status-utf8-reason/CONTEXT.md b/tasks/TASK-2026-069-evolution-status-utf8-reason/CONTEXT.md new file mode 100644 index 0000000..3325b34 --- /dev/null +++ b/tasks/TASK-2026-069-evolution-status-utf8-reason/CONTEXT.md @@ -0,0 +1,28 @@ +# Technical Context + +## Current State + +- Relevant crates/files: `crates/skilllite-commands/src/evolution_status.rs`. +- Current behavior: The JSON status path serializes full event reasons safely, but the human status path computes `reason_short` with `&reason[..47]` when `reason.len() > 50`. + +## Architecture Fit + +- Layer boundaries involved: CLI entry calls `skilllite-commands`; persistence remains in `skilllite-evolution`. +- Interfaces to preserve: `cmd_status(json, workspace, periodic_anchor_unix)` and `EvolutionStatusSnapshot` shape. + +## Dependency and Compatibility + +- New dependencies: None. +- Backward compatibility notes: No schema, command flag, or JSON contract changes. + +## Design Decisions + +- Decision: Add a local `shorten_status_reason` helper that checks character count and builds the preview with `.chars().take(...)`. + - Rationale: This directly removes the panic source while preserving the existing output intent. + - Alternatives considered: Reuse `evolution.rs` private `truncate_chars` or make a shared utility. + - Why rejected: Sharing would broaden the edit beyond the critical path and require extra module surface changes. + +## Open Questions + +- [x] Does the fix need docs updates? No; behavior is a crash fix with unchanged user-facing command semantics. +- [x] Does the regression need LLM/API credentials? No; it seeds the local SQLite event log directly. diff --git a/tasks/TASK-2026-069-evolution-status-utf8-reason/PRD.md b/tasks/TASK-2026-069-evolution-status-utf8-reason/PRD.md new file mode 100644 index 0000000..80c5f4b --- /dev/null +++ b/tasks/TASK-2026-069-evolution-status-utf8-reason/PRD.md @@ -0,0 +1,37 @@ +# PRD + +## Background + +Recent critical-bug work fixed UTF-8 truncation crashes in evolution logging and LLM error summarization. During review of the latest evolution workspace DB scoping changes, the human `evolution status` path still contained byte slicing for event reasons read from SQLite. Those reasons can include Chinese or emoji text from manual evolution runs or desktop-triggered summaries. + +## Objective + +Prevent `skilllite evolution status` from crashing when rendering long non-ASCII event reasons, without changing persisted data or desktop JSON contracts. + +## Functional Requirements + +- FR-1: Human status output must shorten long event reasons using UTF-8-safe character iteration. +- FR-2: The existing recent event table should continue to display an ellipsis for long reasons. +- FR-3: A regression test must seed a real evolution log event with non-ASCII text and call the status command path. + +## Non-Functional Requirements + +- Security: No change to authorization, sandbox, or policy behavior. +- Performance: Reason truncation remains bounded and negligible relative to DB reads. +- Compatibility: JSON output and DB schema remain unchanged; human-only formatting may only change by avoiding invalid byte slicing. + +## Constraints + +- Technical: Follow Rust UTF-8 safety conventions and avoid new dependencies. +- Timeline: N/A for automation; complete in the current investigation loop. + +## Success Metrics + +- Metric: `cmd_status(false, workspace)` with a long Chinese/emoji reason. +- Baseline: Panics at a non-character byte boundary. +- Target: Returns `Ok(())` and prints a shortened reason. + +## Rollout + +- Rollout plan: Ship as a small bug-fix PR with regression coverage. +- Rollback plan: Revert the single code/test commit if unexpected output regressions appear. diff --git a/tasks/TASK-2026-069-evolution-status-utf8-reason/REVIEW.md b/tasks/TASK-2026-069-evolution-status-utf8-reason/REVIEW.md new file mode 100644 index 0000000..b0dd7e0 --- /dev/null +++ b/tasks/TASK-2026-069-evolution-status-utf8-reason/REVIEW.md @@ -0,0 +1,29 @@ +# Review Report + +## Scope Reviewed + +- Files/modules: `crates/skilllite-commands/src/evolution_status.rs`; recent evolution workspace and UTF-8 truncation fixes. +- Commits/changes: Investigation started from `897b00f` and adjacent UTF-8 commits; fixed unsafe status reason truncation. + +## Findings + +- Critical: Human `evolution status` can panic on long non-ASCII event reasons because it byte-slices `reason[..47]`. +- Major: None. +- Minor: None. + +## Quality Gates + +- Architecture boundary checks: `pass` +- Security invariants: `pass` +- Required tests executed: `pass` +- Docs sync (EN/ZH): `pass` + +## Test Evidence + +- Commands run: `cargo fmt --check`; `cargo clippy --all-targets -- -D warnings`; `cargo test -p skilllite-commands`; `cargo test -p skilllite-commands --features agent status_human_handles_non_ascii_event_reasons`; `cargo clippy --all-targets --all-features -- -D warnings`; `cargo test`; `python3 scripts/validate_tasks.py`. +- Key outputs: `cargo fmt --check` passed after rustfmt; default clippy passed after Rust toolchain update to 1.96.0; focused agent regression test passed with `1 passed; 0 failed`; all-features clippy passed; full `cargo test` passed; task validation passed for 69 task directories. + +## Decision + +- Merge readiness: `ready` +- Follow-up actions: None for this bug. diff --git a/tasks/TASK-2026-069-evolution-status-utf8-reason/STATUS.md b/tasks/TASK-2026-069-evolution-status-utf8-reason/STATUS.md new file mode 100644 index 0000000..5f8d753 --- /dev/null +++ b/tasks/TASK-2026-069-evolution-status-utf8-reason/STATUS.md @@ -0,0 +1,37 @@ +# Status Journal + +## Timeline + +- 2026-06-11: + - Progress: Found unsafe byte slicing in `evolution_status.rs` human recent-event reason preview while reviewing recent evolution commits. PRD and context drafted before implementation. Implemented UTF-8-safe reason preview and added a regression test that seeds a real non-ASCII evolution log event before calling human status output. + - Blockers: None. + - Next step: Done; open PR and report findings. + +## Checkpoints + +- [x] PRD drafted before implementation (or `N/A` recorded) +- [x] Context drafted before implementation (or `N/A` recorded) +- [x] Implementation complete +- [x] Tests passed +- [x] Review complete +- [x] Board updated + +## Validation Evidence + +- `cargo fmt --check`: + - Initial run failed on one rustfmt line wrap; `cargo fmt` applied formatting. + - Re-run passed with exit code 0. +- `cargo clippy --all-targets -- -D warnings`: + - Initial run was blocked before code analysis by Cargo 1.83.0 lacking edition 2024 support. + - After `rustup update stable && rustup default stable`, re-run passed: `Finished dev profile ... target(s) in 29.21s`. +- `cargo test -p skilllite-commands`: + - Passed: `test result: ok. 23 passed; 0 failed`. + - Note: default features do not compile the agent-gated evolution status test. +- `cargo test -p skilllite-commands --features agent status_human_handles_non_ascii_event_reasons`: + - Passed: `1 passed; 0 failed`. +- `cargo clippy --all-targets --all-features -- -D warnings`: + - Passed: `Finished dev profile ... target(s) in 7.95s`. +- `cargo test`: + - Passed; final doctest outputs all reported `test result: ok`. +- `python3 scripts/validate_tasks.py`: + - Passed before and after final task updates: `Task validation passed (69 task directories checked).` diff --git a/tasks/TASK-2026-069-evolution-status-utf8-reason/TASK.md b/tasks/TASK-2026-069-evolution-status-utf8-reason/TASK.md new file mode 100644 index 0000000..9f63859 --- /dev/null +++ b/tasks/TASK-2026-069-evolution-status-utf8-reason/TASK.md @@ -0,0 +1,50 @@ +# TASK Card + +## Metadata + +- Task ID: `TASK-2026-069` +- Title: Fix UTF-8 crash in evolution status reasons +- Status: `done` +- Priority: `P0` +- Owner: `agent` +- Contributors: Cursor automation +- Created: `2026-06-11` +- Target milestone: critical bug investigation + +## Problem + +`skilllite evolution status` human output shortens recent event reasons with byte slicing. A long non-ASCII reason stored in `evolution_log` can panic when byte index 47 lands inside a UTF-8 character, making the status command unusable for affected workspaces. + +## Scope + +- In scope: replace the unsafe human status reason preview with UTF-8-safe truncation and add a regression test that exercises the status command with non-ASCII event text. +- Out of scope: broader evolution workspace scoping, JSON status schema changes, and unrelated rendering cleanup. + +## Acceptance Criteria + +- [x] Human `evolution status` does not panic on long CJK/emoji event reasons. +- [x] Regression test covers the actual status rendering path, not only a helper. +- [x] Required Rust and task validation commands pass. + +## Risks + +- Risk: Changing preview length semantics could alter human-only output formatting. + - Impact: Low; JSON output and persisted data are unchanged. + - Mitigation: Keep the same ASCII preview threshold shape and only change the truncation mechanism. + +## Validation Plan + +- Required tests: focused `skilllite-commands` test, full workspace tests per policy. +- Commands to run: `cargo fmt --check`; `cargo clippy --all-targets -- -D warnings`; `cargo test -p skilllite-commands`; `cargo test`; `python3 scripts/validate_tasks.py`. +- Manual checks: Re-read modified files and task board after updates. + +## Regression Scope + +- Areas likely affected: `skilllite evolution status` human recent-event output. +- Explicit non-goals: no evolution DB schema changes, no desktop JSON payload changes, no behavior changes for backlog/proposal commands. + +## Links + +- Source TODO section: N/A; found during critical bug investigation automation. +- Related PRs/issues: Recent UTF-8 truncation fixes `TASK-2026-066`, `TASK-2026-067`; workspace DB scoping fix `TASK-2026-068`. +- Related docs: N/A; user-facing semantics are unchanged. diff --git a/tasks/board.md b/tasks/board.md index df73272..2406bb8 100644 --- a/tasks/board.md +++ b/tasks/board.md @@ -1,6 +1,6 @@ # Task Board -Last updated: 2026-06-10 (TASK-2026-068 evolution workspace db scope done) +Last updated: 2026-06-11 (TASK-2026-069 evolution status UTF-8 reason fix done) ## In Progress @@ -17,6 +17,7 @@ Last updated: 2026-06-10 (TASK-2026-068 evolution workspace db scope done) ## Done +- `TASK-2026-069-evolution-status-utf8-reason` - Status: `done` - Owner: `agent` - `TASK-2026-068-evolution-workspace-db-scope` - Status: `done` - Owner: `agent` - `TASK-2026-067-utf8-llm-error-truncate` - Status: `done` - Owner: `agent` - `TASK-2026-066-utf8-evolution-log-truncate` - Status: `done` - Owner: `agent`