From 6531d428a78adc65212048d39014f8ee67be9b87 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 29 May 2026 11:08:54 +0000 Subject: [PATCH 1/2] fix(evolution): make manual trigger logging utf8 safe Co-authored-by: EXboy --- .../src/evolution_desktop.rs | 35 +++++++++++-- .../CONTEXT.md | 27 ++++++++++ .../PRD.md | 36 +++++++++++++ .../REVIEW.md | 29 +++++++++++ .../STATUS.md | 17 +++++++ .../TASK.md | 50 +++++++++++++++++++ tasks/board.md | 4 +- 7 files changed, 191 insertions(+), 7 deletions(-) create mode 100644 tasks/TASK-2026-066-utf8-evolution-log-truncate/CONTEXT.md create mode 100644 tasks/TASK-2026-066-utf8-evolution-log-truncate/PRD.md create mode 100644 tasks/TASK-2026-066-utf8-evolution-log-truncate/REVIEW.md create mode 100644 tasks/TASK-2026-066-utf8-evolution-log-truncate/STATUS.md create mode 100644 tasks/TASK-2026-066-utf8-evolution-log-truncate/TASK.md diff --git a/crates/skilllite-commands/src/evolution_desktop.rs b/crates/skilllite-commands/src/evolution_desktop.rs index 62d12a67..48a93d69 100644 --- a/crates/skilllite-commands/src/evolution_desktop.rs +++ b/crates/skilllite-commands/src/evolution_desktop.rs @@ -214,6 +214,10 @@ pub fn authorize_capability_evolution( Ok(AuthorizeCapabilitySnapshot { proposal_id }) } +fn clip_manual_trigger_summary(summary: &str) -> String { + truncate_utf8(summary, 480) +} + pub fn log_manual_evolution_trigger( workspace: &str, proposal_id: Option<&str>, @@ -221,11 +225,7 @@ pub fn log_manual_evolution_trigger( ) -> Result<()> { let chat_root = skilllite_core::paths::chat_root(); let conn = skilllite_evolution::feedback::open_evolution_db(&chat_root)?; - let mut clipped = summary.to_string(); - if clipped.len() > 480 { - clipped.truncate(480); - clipped.push('…'); - } + let clipped = clip_manual_trigger_summary(summary); let _ = skilllite_evolution::log_evolution_event( &conn, &chat_root, @@ -236,3 +236,28 @@ pub fn log_manual_evolution_trigger( ); Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn manual_trigger_summary_clip_is_utf8_boundary_safe() { + let mut summary = "界".repeat(159); + summary.push('🙂'); + summary.push_str("tail"); + + let clipped = clip_manual_trigger_summary(&summary); + + assert!(clipped.ends_with('…')); + assert!(clipped.is_char_boundary(clipped.len())); + assert!(clipped.len() <= 480 + "…".len()); + } + + #[test] + fn manual_trigger_summary_clip_leaves_short_text_unchanged() { + let summary = "Evolution completed: 新技能已生成"; + + assert_eq!(clip_manual_trigger_summary(summary), summary); + } +} diff --git a/tasks/TASK-2026-066-utf8-evolution-log-truncate/CONTEXT.md b/tasks/TASK-2026-066-utf8-evolution-log-truncate/CONTEXT.md new file mode 100644 index 00000000..8c3fa05b --- /dev/null +++ b/tasks/TASK-2026-066-utf8-evolution-log-truncate/CONTEXT.md @@ -0,0 +1,27 @@ +# Technical Context + +## Current State + +- Relevant crates/files: `crates/skilllite-commands/src/evolution_desktop.rs`, `crates/skilllite-commands/src/evolution.rs`. +- Current behavior: `log_manual_evolution_trigger()` copies the summary into a `String`, calls `truncate(480)`, and appends `…` when the byte length is above 480. + +## Architecture Fit + +- Layer boundaries involved: CLI command layer only; no dependency direction changes. +- Interfaces to preserve: `skilllite evolution run --log-manual-trigger`, evolution log event fields, and desktop L2 JSON contract. + +## Dependency and Compatibility + +- New dependencies: none. +- Backward compatibility notes: the logged reason may be a few bytes shorter when the limit lands inside a multi-byte character, but remains valid UTF-8 and keeps the same ellipsis marker. + +## Design Decisions + +- Decision: reuse the existing `truncate_utf8()` helper for manual trigger summaries. + - Rationale: the helper already enforces character boundaries for pending skill previews in the same module. + - Alternatives considered: count characters instead of bytes. + - Why rejected: byte-budget behavior was already established and only needs boundary safety. + +## Open Questions + +- [x] Is a schema or documentation update required? No; behavior remains the same except crash prevention. diff --git a/tasks/TASK-2026-066-utf8-evolution-log-truncate/PRD.md b/tasks/TASK-2026-066-utf8-evolution-log-truncate/PRD.md new file mode 100644 index 00000000..4cf35c5d --- /dev/null +++ b/tasks/TASK-2026-066-utf8-evolution-log-truncate/PRD.md @@ -0,0 +1,36 @@ +# PRD + +## Background + +The desktop split-ready evolution bridge now records a manual trigger event after `skilllite evolution run --log-manual-trigger`. The event reason is derived from the user-visible evolution response and can contain CJK or emoji text. Rust `String::truncate(n)` requires `n` to be a valid UTF-8 character boundary; using a raw byte limit can panic. + +## Objective + +Manual evolution trigger logging must never crash because of non-ASCII summary text. The fix should preserve the existing audit event shape and only alter the clipping implementation. + +## Functional Requirements + +- FR-1: Clip manual evolution trigger summaries to the existing 480-byte budget without splitting UTF-8 characters. +- FR-2: Preserve the trailing ellipsis behavior when clipping occurs. + +## Non-Functional Requirements + +- Security: no security policy relaxation. +- Performance: clipping remains linear in the summary length and only runs once per manual trigger. +- Compatibility: log event type, target ID, workspace field, and JSON/CLI outputs remain unchanged. + +## Constraints + +- Technical: use existing local helper where possible; no new dependencies. +- Timeline: autonomous critical bug fix; no calendar estimate. + +## Success Metrics + +- Metric: regression test with repeated CJK text passes without panic. +- Baseline: `String::truncate(480)` panics when 480 is not a character boundary. +- Target: UTF-8-safe clipping returns valid text and appends `…`. + +## Rollout + +- Rollout plan: ship as a minimal Rust patch in `skilllite-commands`. +- Rollback plan: revert the single helper usage and test if unexpected behavior appears. diff --git a/tasks/TASK-2026-066-utf8-evolution-log-truncate/REVIEW.md b/tasks/TASK-2026-066-utf8-evolution-log-truncate/REVIEW.md new file mode 100644 index 00000000..e5fd29ef --- /dev/null +++ b/tasks/TASK-2026-066-utf8-evolution-log-truncate/REVIEW.md @@ -0,0 +1,29 @@ +# Review Report + +## Scope Reviewed + +- Files/modules: +- Commits/changes: + +## Findings + +- Critical: +- Major: +- Minor: + +## Quality Gates + +- Architecture boundary checks: `pass | fail` +- Security invariants: `pass | fail` +- Required tests executed: `pass | fail` +- Docs sync (EN/ZH): `pass | fail` + +## Test Evidence + +- Commands run: +- Key outputs: + +## Decision + +- Merge readiness: `ready | not ready` +- Follow-up actions: diff --git a/tasks/TASK-2026-066-utf8-evolution-log-truncate/STATUS.md b/tasks/TASK-2026-066-utf8-evolution-log-truncate/STATUS.md new file mode 100644 index 00000000..6d6e408d --- /dev/null +++ b/tasks/TASK-2026-066-utf8-evolution-log-truncate/STATUS.md @@ -0,0 +1,17 @@ +# Status Journal + +## Timeline + +- 2026-05-29: + - Progress: Identified a UTF-8 byte-boundary panic in `log_manual_evolution_trigger()` introduced in recent evolution L2 CLI work. Drafted task scope, PRD, and context before code changes. Replaced raw byte truncation with the module's UTF-8-safe clipping helper and added non-ASCII regression coverage. + - Blockers: None. + - Next step: Commit and push the implementation, then run validation. + +## Checkpoints + +- [x] PRD drafted before implementation (or `N/A` recorded) +- [x] Context drafted before implementation (or `N/A` recorded) +- [x] Implementation complete +- [ ] Tests passed +- [ ] Review complete +- [ ] Board updated diff --git a/tasks/TASK-2026-066-utf8-evolution-log-truncate/TASK.md b/tasks/TASK-2026-066-utf8-evolution-log-truncate/TASK.md new file mode 100644 index 00000000..336c90c3 --- /dev/null +++ b/tasks/TASK-2026-066-utf8-evolution-log-truncate/TASK.md @@ -0,0 +1,50 @@ +# TASK Card + +## Metadata + +- Task ID: `TASK-2026-066` +- Title: Fix UTF-8-safe evolution trigger logging +- Status: `in_progress` +- Priority: `P0` +- Owner: `agent` +- Contributors: Cursor automation +- Created: `2026-05-29` +- Target milestone: Critical bug fix + +## Problem + +Recent desktop evolution L2 work added manual trigger logging for `skilllite evolution run --log-manual-trigger`. The log summary path truncates a UTF-8 `String` by byte length with `String::truncate(480)`, which panics if byte 480 falls inside a multi-byte CJK or emoji character. A long non-ASCII evolution summary can therefore crash the command after the evolution run has completed. + +## Scope + +- In scope: make manual evolution trigger log clipping UTF-8 safe; add regression coverage with non-ASCII input. +- Out of scope: broader evolution workflow refactors, log schema changes, or LLM behavior changes. + +## Acceptance Criteria + +- [ ] Long Chinese or emoji summaries do not panic during manual trigger log clipping. +- [ ] Existing preview truncation behavior remains unchanged except for UTF-8 safety. +- [ ] Regression test covers the non-ASCII boundary case. + +## Risks + +- Risk: changing truncation semantics could alter logged summary length slightly. + - Impact: low; only audit/log preview text is affected. + - Mitigation: reuse the existing UTF-8-aware helper and keep the same byte limit. + +## Validation Plan + +- Required tests: targeted `skilllite-commands` regression test; repository task validation. +- Commands to run: `cargo fmt --check`; `cargo test -p skilllite-commands evolution_desktop`; `python3 scripts/validate_tasks.py`; broader cargo validation if time permits. +- Manual checks: re-read modified source and task artifacts. + +## Regression Scope + +- Areas likely affected: `skilllite evolution run --log-manual-trigger` audit/log summary text; desktop manual evolution trigger bridge. +- Explicit non-goals: changing evolution run scheduling, database schema, or pending skill handling. + +## Links + +- Source TODO section: N/A +- Related PRs/issues: Recent PR #79 / #81 evolution L2 CLI bridge changes. +- Related docs: `spec/verification-integrity.md`, `spec/rust-conventions.md`, `spec/testing-policy.md` diff --git a/tasks/board.md b/tasks/board.md index b8add94a..f7be907e 100644 --- a/tasks/board.md +++ b/tasks/board.md @@ -1,10 +1,10 @@ # Task Board -Last updated: 2026-04-29 (TASK-2026-064 env-keys single-source done) +Last updated: 2026-05-29 (TASK-2026-066 utf8 evolution log truncate in progress) ## In Progress -- None. +- `TASK-2026-066-utf8-evolution-log-truncate` - Status: `in_progress` - Owner: `agent` ## Ready From 26bddd2325d034e95dfc7006a6c164c7802810b7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 29 May 2026 11:13:05 +0000 Subject: [PATCH 2/2] docs(task): record utf8 logging validation Co-authored-by: EXboy --- .../REVIEW.md | 29 ++++++++++++------- .../STATUS.md | 10 +++---- .../TASK.md | 15 +++++++--- tasks/board.md | 5 ++-- 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/tasks/TASK-2026-066-utf8-evolution-log-truncate/REVIEW.md b/tasks/TASK-2026-066-utf8-evolution-log-truncate/REVIEW.md index e5fd29ef..b973feb8 100644 --- a/tasks/TASK-2026-066-utf8-evolution-log-truncate/REVIEW.md +++ b/tasks/TASK-2026-066-utf8-evolution-log-truncate/REVIEW.md @@ -2,28 +2,35 @@ ## Scope Reviewed -- Files/modules: -- Commits/changes: +- Files/modules: `crates/skilllite-commands/src/evolution_desktop.rs`; task artifacts for `TASK-2026-066`. +- Commits/changes: Recent evolution L2 CLI bridge code paths around `skilllite evolution run --log-manual-trigger`. ## Findings -- Critical: -- Major: -- Minor: +- Critical: Fixed UTF-8 byte-boundary panic in manual evolution trigger log clipping. +- Major: None remaining. +- Minor: None. ## Quality Gates -- Architecture boundary checks: `pass | fail` -- Security invariants: `pass | fail` -- Required tests executed: `pass | fail` -- Docs sync (EN/ZH): `pass | fail` +- Architecture boundary checks: `pass` +- Security invariants: `pass` +- Required tests executed: `pass` +- Docs sync (EN/ZH): `pass` (not required; no user-facing command semantics changed) ## Test Evidence - Commands run: + - `cargo fmt --check && cargo test -p skilllite-commands evolution_desktop && python3 scripts/validate_tasks.py` + - `cargo test -p skilllite-commands --features agent manual_trigger_summary_clip` + - `cargo clippy --all-targets -- -D warnings && cargo clippy -p skilllite-commands --features agent --all-targets -- -D warnings && cargo test` - Key outputs: + - Initial validation was blocked by Cargo 1.83 lacking edition 2024 support for `time 0.3.47`; local stable toolchain was updated to Rust/Cargo 1.96. + - Agent-feature regression run: `2 passed; 0 failed`. + - Task validation: `Task validation passed (66 task directories checked).` + - Full clippy/test command exited 0; final workspace test/doc-test output showed all executed tests passing. ## Decision -- Merge readiness: `ready | not ready` -- Follow-up actions: +- Merge readiness: `ready` +- Follow-up actions: None. diff --git a/tasks/TASK-2026-066-utf8-evolution-log-truncate/STATUS.md b/tasks/TASK-2026-066-utf8-evolution-log-truncate/STATUS.md index 6d6e408d..54326087 100644 --- a/tasks/TASK-2026-066-utf8-evolution-log-truncate/STATUS.md +++ b/tasks/TASK-2026-066-utf8-evolution-log-truncate/STATUS.md @@ -3,15 +3,15 @@ ## Timeline - 2026-05-29: - - Progress: Identified a UTF-8 byte-boundary panic in `log_manual_evolution_trigger()` introduced in recent evolution L2 CLI work. Drafted task scope, PRD, and context before code changes. Replaced raw byte truncation with the module's UTF-8-safe clipping helper and added non-ASCII regression coverage. + - Progress: Identified a UTF-8 byte-boundary panic in `log_manual_evolution_trigger()` introduced in recent evolution L2 CLI work. Drafted task scope, PRD, and context before code changes. Replaced raw byte truncation with the module's UTF-8-safe clipping helper and added non-ASCII regression coverage. Validation completed after updating the local Rust stable toolchain to 1.96. - Blockers: None. - - Next step: Commit and push the implementation, then run validation. + - Next step: Open PR and report the fix. ## Checkpoints - [x] PRD drafted before implementation (or `N/A` recorded) - [x] Context drafted before implementation (or `N/A` recorded) - [x] Implementation complete -- [ ] Tests passed -- [ ] Review complete -- [ ] Board updated +- [x] Tests passed +- [x] Review complete +- [x] Board updated diff --git a/tasks/TASK-2026-066-utf8-evolution-log-truncate/TASK.md b/tasks/TASK-2026-066-utf8-evolution-log-truncate/TASK.md index 336c90c3..70c74d44 100644 --- a/tasks/TASK-2026-066-utf8-evolution-log-truncate/TASK.md +++ b/tasks/TASK-2026-066-utf8-evolution-log-truncate/TASK.md @@ -4,7 +4,7 @@ - Task ID: `TASK-2026-066` - Title: Fix UTF-8-safe evolution trigger logging -- Status: `in_progress` +- Status: `done` - Priority: `P0` - Owner: `agent` - Contributors: Cursor automation @@ -22,9 +22,9 @@ Recent desktop evolution L2 work added manual trigger logging for `skilllite evo ## Acceptance Criteria -- [ ] Long Chinese or emoji summaries do not panic during manual trigger log clipping. -- [ ] Existing preview truncation behavior remains unchanged except for UTF-8 safety. -- [ ] Regression test covers the non-ASCII boundary case. +- [x] Long Chinese or emoji summaries do not panic during manual trigger log clipping. +- [x] Existing preview truncation behavior remains unchanged except for UTF-8 safety. +- [x] Regression test covers the non-ASCII boundary case. ## Risks @@ -38,6 +38,13 @@ Recent desktop evolution L2 work added manual trigger logging for `skilllite evo - Commands to run: `cargo fmt --check`; `cargo test -p skilllite-commands evolution_desktop`; `python3 scripts/validate_tasks.py`; broader cargo validation if time permits. - Manual checks: re-read modified source and task artifacts. +## Validation Evidence + +- `rustup update stable && rustup default stable`: updated local toolchain from Rust/Cargo 1.83 to 1.96 because dependency `time 0.3.47` requires edition 2024 support. +- `cargo fmt --check && cargo test -p skilllite-commands evolution_desktop && python3 scripts/validate_tasks.py`: exit 0 after toolchain update. The first targeted filter compiled successfully but selected 0 tests because `evolution_desktop` is gated behind `agent`; task validation passed with 66 task directories checked. +- `cargo test -p skilllite-commands --features agent manual_trigger_summary_clip`: exit 0; 2 tests passed, including the UTF-8 boundary regression. +- `cargo clippy --all-targets -- -D warnings && cargo clippy -p skilllite-commands --features agent --all-targets -- -D warnings && cargo test`: exit 0; full test output ended with all workspace tests/doc-tests passing. + ## Regression Scope - Areas likely affected: `skilllite evolution run --log-manual-trigger` audit/log summary text; desktop manual evolution trigger bridge. diff --git a/tasks/board.md b/tasks/board.md index f7be907e..43428f7b 100644 --- a/tasks/board.md +++ b/tasks/board.md @@ -1,10 +1,10 @@ # Task Board -Last updated: 2026-05-29 (TASK-2026-066 utf8 evolution log truncate in progress) +Last updated: 2026-05-29 (TASK-2026-066 utf8 evolution log truncate done) ## In Progress -- `TASK-2026-066-utf8-evolution-log-truncate` - Status: `in_progress` - Owner: `agent` +- None. ## Ready @@ -17,6 +17,7 @@ Last updated: 2026-05-29 (TASK-2026-066 utf8 evolution log truncate in progress) ## Done +- `TASK-2026-066-utf8-evolution-log-truncate` - Status: `done` - Owner: `agent` - `TASK-2026-064-env-keys-single-source` - Status: `done` - Owner: `agent` - `TASK-2026-063-extension-tool-metadata-dispatch` - Status: `done` - Owner: `agent` - `TASK-2026-062-wiki-lesson-optimization-template` - Status: `done` - Owner: `agent`