diff --git a/crates/skilllite-agent/src/chat_session.rs b/crates/skilllite-agent/src/chat_session.rs index a3e248e..f5cbdc2 100644 --- a/crates/skilllite-agent/src/chat_session.rs +++ b/crates/skilllite-agent/src/chat_session.rs @@ -1161,6 +1161,20 @@ fn apply_message_window_to_cache(cache: &mut TranscriptCache, paths: &[PathBuf], // ─── A9: evolution triggers (periodic + decision-count) ───────────────────── +fn resolve_evolution_skills_root(workspace: &str) -> Option { + if workspace.is_empty() { + return None; + } + let workspace_root = skilllite_core::paths::resolve_workspace_filesystem_root(workspace); + Some( + skilllite_core::skill::discovery::resolve_skills_dir_with_legacy_fallback( + &workspace_root, + "skills", + ) + .effective_path, + ) +} + async fn run_evolution_and_emit_summary( data_root: &Path, workspace: &str, @@ -1168,20 +1182,7 @@ async fn run_evolution_and_emit_summary( api_key: &str, model: &str, ) { - let skills_root = if workspace.is_empty() { - None - } else { - let ws = std::path::Path::new(workspace); - let sr = if ws.is_absolute() { - ws.join(".skills") - } else { - std::env::current_dir() - .unwrap_or_else(|_| std::path::PathBuf::from(".")) - .join(workspace) - .join(".skills") - }; - Some(sr) - }; + let skills_root = resolve_evolution_skills_root(workspace); let llm = match LlmClient::new(api_base, api_key) { Ok(c) => c, Err(e) => { @@ -1372,6 +1373,35 @@ fn transcript_entry_to_message(entry: &transcript::TranscriptEntry) -> Option bool { // ─── Subprocess helpers ───────────────────────────────────────────────────── +fn evolution_growth_args(workspace: &str) -> Vec { + vec![ + "evolution".to_string(), + "run".to_string(), + "--workspace".to_string(), + workspace.to_string(), + ] +} + fn spawn_growth( skilllite_path: &std::path::Path, + workspace: &str, env_pairs: &[(String, String)], running: Arc, app: tauri::AppHandle, ) { let path = skilllite_path.to_path_buf(); + let workspace = workspace.to_string(); let env: Vec<(String, String)> = env_pairs.to_vec(); std::thread::spawn(move || { emit(&app, "growth-started", None); + let args = evolution_growth_args(&workspace); + let root = skilllite_bridge::find_project_root(&workspace); let mut growth_cmd = Command::new(&path); crate::windows_spawn::hide_child_console(&mut growth_cmd); let result = growth_cmd - .args(["evolution", "run"]) + .args(&args) .envs(env.iter().map(|(k, v)| (k.as_str(), v.as_str()))) + .current_dir(root) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .status(); @@ -281,6 +295,7 @@ pub fn start(state: LifePulseState, skilllite_path: PathBuf, app: tauri::AppHand s.growth_running.store(true, Ordering::SeqCst); spawn_growth( &skilllite_path, + &workspace, &child_env, s.growth_running.clone(), app.clone(), @@ -326,3 +341,23 @@ pub fn start(state: LifePulseState, skilllite_path: PathBuf, app: tauri::AppHand pub fn stop(state: &LifePulseState) { state.alive.store(false, Ordering::SeqCst); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn growth_args_include_active_workspace() { + let args = evolution_growth_args("/tmp/skilllite workspace"); + + assert_eq!( + args, + vec![ + "evolution", + "run", + "--workspace", + "/tmp/skilllite workspace", + ] + ); + } +} diff --git a/crates/skilllite-assistant/src-tauri/src/skilllite_bridge/chat.rs b/crates/skilllite-assistant/src-tauri/src/skilllite_bridge/chat.rs index 955f59f..b4a59d6 100644 --- a/crates/skilllite-assistant/src-tauri/src/skilllite_bridge/chat.rs +++ b/crates/skilllite-assistant/src-tauri/src/skilllite_bridge/chat.rs @@ -157,6 +157,18 @@ pub fn merge_dotenv_with_chat_overrides( pairs } +fn child_env_with_workspace( + mut env_pairs: Vec<(String, String)>, + workspace: &str, +) -> Vec<(String, String)> { + env_pairs.retain(|(key, _)| key != super::local::env_keys::paths::SKILLLITE_WORKSPACE); + env_pairs.push(( + super::local::env_keys::paths::SKILLLITE_WORKSPACE.to_string(), + workspace.to_string(), + )); + env_pairs +} + fn apply_chat_overrides_env( m: &mut std::collections::HashMap, cfg: &ChatConfigOverrides, @@ -369,6 +381,7 @@ pub fn chat_stream( load_dotenv_for_child(&raw_workspace), config_overrides.as_ref(), ); + let env_pairs = child_env_with_workspace(env_pairs, &workspace_str); let provenance = summarize_env_provenance(&env_sources); if !provenance.is_empty() { eprintln!( @@ -774,4 +787,22 @@ mod tests { Some("https://ui.base") ); } + + #[test] + fn child_env_with_workspace_overrides_dotenv_workspace() { + let env_pairs = vec![ + ("SKILLLITE_WORKSPACE".to_string(), "/tmp/wrong".to_string()), + ("OPENAI_MODEL".to_string(), "dotenv-model".to_string()), + ]; + + let merged = child_env_with_workspace(env_pairs, "/tmp/project"); + let map: std::collections::HashMap<_, _> = merged.into_iter().collect(); + + assert_eq!( + map.get(super::super::local::env_keys::paths::SKILLLITE_WORKSPACE) + .map(String::as_str), + Some("/tmp/project") + ); + assert_eq!(map.get("OPENAI_MODEL").map(String::as_str), Some("dotenv-model")); + } } diff --git a/crates/skilllite-assistant/src-tauri/src/skilllite_bridge/integrations/evolution_ui/authorize.rs b/crates/skilllite-assistant/src-tauri/src/skilllite_bridge/integrations/evolution_ui/authorize.rs index dd76838..7af0817 100644 --- a/crates/skilllite-assistant/src-tauri/src/skilllite_bridge/integrations/evolution_ui/authorize.rs +++ b/crates/skilllite-assistant/src-tauri/src/skilllite_bridge/integrations/evolution_ui/authorize.rs @@ -5,6 +5,16 @@ use crate::skilllite_bridge::local::engine_types::AuthorizeCapabilityResponse; use crate::skilllite_bridge::local::env_keys::evolution as evo_keys; use crate::skilllite_bridge::paths::{find_project_root, load_dotenv_for_child}; +fn authorized_evolution_run_args(workspace: &str) -> Vec { + vec![ + "evolution".to_string(), + "run".to_string(), + "--json".to_string(), + "--workspace".to_string(), + workspace.to_string(), + ] +} + pub fn authorize_capability_evolution( workspace: &str, tool_name: &str, @@ -36,11 +46,10 @@ pub fn authorize_capability_evolution( let skilllite_path_owned = skilllite_path.to_path_buf(); std::thread::spawn(move || { let root = find_project_root(&workspace_owned); + let args = authorized_evolution_run_args(&workspace_owned); let mut cmd = std::process::Command::new(&skilllite_path_owned); crate::windows_spawn::hide_child_console(&mut cmd); - cmd.arg("evolution") - .arg("run") - .arg("--json") + cmd.args(&args) .current_dir(&root) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()); @@ -52,3 +61,24 @@ pub fn authorize_capability_evolution( }); Ok(proposal_id) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn authorized_run_args_include_target_workspace() { + let args = authorized_evolution_run_args("/tmp/skilllite workspace"); + + assert_eq!( + args, + vec![ + "evolution", + "run", + "--json", + "--workspace", + "/tmp/skilllite workspace", + ] + ); + } +} diff --git a/crates/skilllite-assistant/src-tauri/src/skilllite_bridge/mod.rs b/crates/skilllite-assistant/src-tauri/src/skilllite_bridge/mod.rs index e6ab1d9..2948e3b 100644 --- a/crates/skilllite-assistant/src-tauri/src/skilllite_bridge/mod.rs +++ b/crates/skilllite-assistant/src-tauri/src/skilllite_bridge/mod.rs @@ -25,7 +25,7 @@ pub use chat::{ pub use followup_suggestions::followup_chat_suggestions; pub use integrations::*; pub use llm_routing_error::{classify_llm_routing_error_message, LlmInvokeResult}; -pub(crate) use paths::load_dotenv_for_child; +pub(crate) use paths::{find_project_root, load_dotenv_for_child}; pub use paths::{ default_writable_workspace_dir, ensure_skilllite_version, resolve_skilllite_path_app, MIN_SKILLLITE_VERSION, diff --git a/crates/skilllite-commands/src/evolution.rs b/crates/skilllite-commands/src/evolution.rs index ee12525..b7ce40c 100644 --- a/crates/skilllite-commands/src/evolution.rs +++ b/crates/skilllite-commands/src/evolution.rs @@ -28,9 +28,10 @@ use crate::Result; use skilllite_core::config::env_keys::paths as env_paths; use skilllite_core::paths; use skilllite_core::protocol::{NewSkill, NodeResult}; +use skilllite_core::skill::discovery::resolve_skills_dir_with_legacy_fallback; use skilllite_core::skill::manifest; -/// Resolve workspace for project-level skill evolution. +/// Resolve workspace for legacy project-level skill commands. /// Uses SKILLLITE_WORKSPACE env or current_dir. Returns workspace/.skills. fn resolve_skills_root(workspace: Option<&str>) -> Option { let ws: PathBuf = workspace @@ -50,6 +51,11 @@ fn resolve_skills_root(workspace: Option<&str>) -> Option { Some(ws.join(".skills")) } +fn resolve_run_skills_root(workspace: &str) -> PathBuf { + let ws = crate::evolution_status::resolve_workspace_root(workspace); + resolve_skills_dir_with_legacy_fallback(&ws, "skills").effective_path +} + #[derive(Debug)] struct BacklogRow { proposal_id: String, @@ -569,7 +575,7 @@ pub fn cmd_run( ); let root = paths::chat_root(); - let skills_root = resolve_skills_root(Some(workspace)); + let skills_root = Some(resolve_run_skills_root(workspace)); skilllite_core::config::ensure_default_output_dir(); let force_key = skilllite_core::config::env_keys::evolution::SKILLLITE_EVO_FORCE_PROPOSAL_ID; @@ -1060,4 +1066,28 @@ mod tests { let _ = std::fs::remove_dir_all(&root); } + + #[test] + fn run_skills_root_prefers_workspace_skills_dir() { + let workspace = tempfile::tempdir().expect("workspace"); + let skills_dir = workspace.path().join("skills"); + let legacy_dir = workspace.path().join(".skills"); + std::fs::create_dir_all(&skills_dir).expect("create skills"); + std::fs::create_dir_all(&legacy_dir).expect("create legacy skills"); + + let resolved = resolve_run_skills_root(workspace.path().to_string_lossy().as_ref()); + + assert_eq!(resolved, skills_dir); + } + + #[test] + fn run_skills_root_keeps_legacy_fallback_when_default_missing() { + let workspace = tempfile::tempdir().expect("workspace"); + let legacy_dir = workspace.path().join(".skills"); + std::fs::create_dir_all(&legacy_dir).expect("create legacy skills"); + + let resolved = resolve_run_skills_root(workspace.path().to_string_lossy().as_ref()); + + assert_eq!(resolved, legacy_dir); + } } diff --git a/crates/skilllite-evolution/src/run.rs b/crates/skilllite-evolution/src/run.rs index d1331b4..a3e39d5 100644 --- a/crates/skilllite-evolution/src/run.rs +++ b/crates/skilllite-evolution/src/run.rs @@ -37,7 +37,7 @@ fn try_log_evolution_run_outcome(chat_root: &Path, reason: &str) { /// /// Returns [EvolutionRunResult]: SkippedBusy if another run in progress, NoScope if nothing to evolve, Completed(txn_id) otherwise. /// When force=true (manual trigger), bypass decision thresholds. -/// skills_root: project-level dir (workspace/.skills). When None, skips skill evolution. +/// skills_root: project-level skills dir. When None, skips skill evolution. pub async fn run_evolution( chat_root: &Path, skills_root: Option<&Path>, diff --git a/tasks/TASK-2026-069-evolution-workspace-run-scope/CONTEXT.md b/tasks/TASK-2026-069-evolution-workspace-run-scope/CONTEXT.md new file mode 100644 index 0000000..d642625 --- /dev/null +++ b/tasks/TASK-2026-069-evolution-workspace-run-scope/CONTEXT.md @@ -0,0 +1,56 @@ +# Technical Context + +## Current State + +- Relevant crates/files: + - `crates/skilllite-commands/src/evolution.rs` + - `crates/skilllite-commands/src/evolution_desktop.rs` + - `crates/skilllite-agent/src/chat_session.rs` + - `crates/skilllite-assistant/src-tauri/src/skilllite_bridge/chat.rs` + - `crates/skilllite-assistant/src-tauri/src/skilllite_bridge/integrations/evolution_ui/authorize.rs` + - `crates/skilllite-assistant/src-tauri/src/life_pulse.rs` +- Current behavior: + - `evolution_desktop` pending/confirm/status use `resolve_skills_dir_with_legacy_fallback`, preferring `skills/` with `.skills` fallback. + - `evolution::cmd_run` resolves generated skill output as `workspace/.skills`. + - Desktop `agent-rpc` receives workspace in JSON config, but `chat_root()` is environment based and can still resolve `~/.skilllite/chat`. + - Agent in-process A9 evolution resolves generated skill output as `workspace/.skills`. + - Desktop manual trigger includes `--workspace`; authorize follow-up and Life Pulse growth currently start `evolution run` without `--workspace`. + +## Architecture Fit + +- Layer boundaries involved: + - Desktop assistant bridge starts CLI subprocesses. + - CLI entry dispatch calls `skilllite-commands`. + - `skilllite-commands` calls `skilllite-evolution` with explicit chat and skills roots. +- Interfaces to preserve: + - Existing `skilllite evolution run --workspace ` CLI. + - Existing desktop pending/confirm/reject JSON contract. + - Existing legacy `.skills` fallback behavior. + +## Dependency and Compatibility + +- New dependencies: + - None. +- Backward compatibility notes: + - Workspaces containing only `.skills` should continue to use `.skills`. + - Workspaces containing `skills` should use `skills` consistently for generated pending skills and UI review. + +## Design Decisions + +- Decision: Reuse the existing core skill discovery fallback helper for `cmd_run`. + - Rationale: It is already the source of truth for desktop pending/confirm/status paths. + - Alternatives considered: Keep `.skills` for run and teach pending to search both roots. + - Why rejected: It preserves the split write/read model and risks duplicate or ambiguous pending skills. +- Decision: Set `SKILLLITE_WORKSPACE` explicitly for desktop `agent-rpc` children after resolving the UI workspace. + - Rationale: `skilllite_core::paths::chat_root()` uses that env var, while the L2 evolution UI reads `/chat`. + - Alternatives considered: Change L2 UI to read the global chat root. + - Why rejected: It would undo the workspace scoping fixed in `TASK-2026-068` and reintroduce cross-workspace leakage. +- Decision: Test subprocess run arguments through pure helper functions. + - Rationale: It avoids spawning an LLM-backed evolution run while still locking the high-risk contract. + - Alternatives considered: Full desktop integration tests. + - Why rejected: Current Tauri/LLM environment makes full integration tests heavy and brittle for this narrow fix. + +## Open Questions + +- [x] Should this change add a new CLI flag? No; the existing `--workspace` flag is sufficient. +- [x] Should force/manual policy be changed now? No; that is a broader governance behavior change outside the minimal critical fix. diff --git a/tasks/TASK-2026-069-evolution-workspace-run-scope/PRD.md b/tasks/TASK-2026-069-evolution-workspace-run-scope/PRD.md new file mode 100644 index 0000000..d17e4b8 --- /dev/null +++ b/tasks/TASK-2026-069-evolution-workspace-run-scope/PRD.md @@ -0,0 +1,46 @@ +# PRD + +## Background + +`TASK-2026-068` fixed several L2 evolution database readers/writers to honor the CLI `--workspace` argument. A follow-up inspection found remaining chat/run paths that still rely on cwd/env defaults or a hardcoded `.skills` output root. These paths affect desktop chat decisions, user-authorized evolution, and automatic desktop/agent evolution, where a silent workspace split makes generated pending skills appear lost. + +## Objective + +Evolution execution and desktop chat decision recording must use the same workspace and skill directory contract as the desktop status/backlog/pending UI. A chat turn, user-triggered run, or automatic run for workspace `W` must not write proposals, decisions, or generated skills under a different workspace root. + +## Functional Requirements + +- FR-1: `skilllite evolution run --workspace W` resolves skill output via the shared `skills` with `.skills` legacy fallback helper. +- FR-2: Desktop `agent-rpc` child processes receive `SKILLLITE_WORKSPACE=W` after UI workspace resolution. +- FR-3: Agent in-process A9 evolution resolves skill output via the shared fallback helper. +- FR-4: Desktop background follow-up runs after authorization include `--workspace W`. +- FR-5: Life Pulse growth runs include `--workspace W`. +- FR-6: Tests must prove argument/root/env selection without requiring a live LLM provider. + +## Non-Functional Requirements + +- Security: Preserve existing path validation and gatekeeper boundaries; do not widen allowed write locations beyond the effective workspace skill root. +- Performance: Keep changes to constant-time path/argument construction. +- Compatibility: Continue supporting legacy workspaces that only contain `.skills`. + +## Constraints + +- Technical: Avoid broad refactors in Tauri command wiring and avoid requiring an LLM key in regression tests. +- Timeline: N/A for autonomous task execution; scope is constrained to known run paths and focused tests. + +## Success Metrics + +- Metric: Workspace run paths that explicitly carry `--workspace`. +- Baseline: Manual trigger carries `--workspace`; authorize follow-up and Life Pulse growth do not. +- Target: All desktop `evolution run` subprocess paths carry `--workspace`. +- Metric: Desktop chat/evolution DB agreement. +- Baseline: `agent-rpc` can write chat data under `~/.skilllite/chat` while L2 reads `/chat`. +- Target: Desktop `agent-rpc` receives `SKILLLITE_WORKSPACE=`. +- Metric: Skill output root agreement. +- Baseline: `cmd_run` writes `.skills`; desktop pending reads effective `skills`/`.skills`. +- Target: `cmd_run` and desktop pending use the same effective root. + +## Rollout + +- Rollout plan: Ship as a narrow bug-fix PR with focused tests and no schema migration. +- Rollback plan: Revert the commit if regressions appear; no persisted data migration is introduced. diff --git a/tasks/TASK-2026-069-evolution-workspace-run-scope/REVIEW.md b/tasks/TASK-2026-069-evolution-workspace-run-scope/REVIEW.md new file mode 100644 index 0000000..5b557f4 --- /dev/null +++ b/tasks/TASK-2026-069-evolution-workspace-run-scope/REVIEW.md @@ -0,0 +1,65 @@ +# Review Report + +## Scope Reviewed + +- Files/modules: + - `crates/skilllite-assistant/src-tauri/src/skilllite_bridge/chat.rs` + - `crates/skilllite-assistant/src-tauri/src/skilllite_bridge/integrations/evolution_ui/authorize.rs` + - `crates/skilllite-assistant/src-tauri/src/life_pulse.rs` + - `crates/skilllite-assistant/src-tauri/src/skilllite_bridge/mod.rs` + - `crates/skilllite-agent/src/chat_session.rs` + - `crates/skilllite-commands/src/evolution.rs` + - `crates/skilllite-evolution/src/run.rs` + - `tasks/TASK-2026-069-evolution-workspace-run-scope/*` + - `tasks/board.md` +- Commits/changes: + - Fix desktop chat child env so `SKILLLITE_WORKSPACE` matches the resolved UI workspace. + - Fix `evolution run` and agent A9 skill output root to use shared `skills`/`.skills` fallback. + - Fix desktop authorize follow-up and Life Pulse growth subprocess args to include `--workspace`. + - Add focused unit tests for env override, subprocess args, and skill-root fallback. + +## Findings + +- Critical: Fixed workspace DB/root split that could make desktop chat decisions, authorized proposals, automatic growth, or generated pending skills land outside the workspace that the UI reads. +- Major: Assistant standalone clippy with `-D warnings` is still blocked by existing unrelated lint baseline; tests for changed assistant code pass. +- Minor: No docs changes required because this preserves intended shipped workspace behavior and does not add user-facing flags. + +## Quality Gates + +- Architecture boundary checks: `pass` +- Security invariants: `pass` +- Required tests executed: `pass` +- Docs sync (EN/ZH): `pass` (`N/A`; bug fix restores documented/intended behavior with no new command/env surface) + +## Test Evidence + +- Commands run: + - `rustup update stable && rustup default stable && rustc --version && cargo --version` + - `cargo fmt --check` + - `cargo test -p skilllite-commands --features agent` + - `cargo test -p skilllite-agent` + - `sudo apt-get update && sudo apt-get install -y libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev` + - `npm ci && npm run build` in `crates/skilllite-assistant` + - `cargo test --manifest-path crates/skilllite-assistant/src-tauri/Cargo.toml` + - `cargo test -p skilllite` + - `cargo clippy --all-targets -- -D warnings` + - `cargo clippy --manifest-path crates/skilllite-assistant/src-tauri/Cargo.toml --all-targets -- -D warnings` + - `cargo test` + - `python3 scripts/validate_tasks.py` +- Key outputs: + - Rust toolchain updated to `rustc 1.96.0` / `cargo 1.96.0`; initial Cargo 1.83 run failed on edition 2024 dependency metadata. + - `cargo fmt --check`: passed. + - `cargo test -p skilllite-commands --features agent`: `test result: ok. 41 passed; 0 failed`. + - `cargo test -p skilllite-agent`: `test result: ok. 247 passed; 0 failed`; doc-test result ok with 1 ignored. + - `cargo test --manifest-path crates/skilllite-assistant/src-tauri/Cargo.toml`: `test result: ok. 53 passed; 0 failed`. + - `cargo test -p skilllite`: passed all package unit/integration tests, including `cli_evolution_workspace`. + - `cargo clippy --all-targets -- -D warnings`: passed for main workspace. + - `cargo clippy --manifest-path crates/skilllite-assistant/src-tauri/Cargo.toml --all-targets -- -D warnings`: failed on existing unrelated assistant lint baseline (`parse_dotenv_from_dir`, `SkillInstance`, `MIN_SKILLLITE_VERSION`, `Command`/`Stdio`, several dead-code helpers, and `workspace.rs` sort_by lints). + - `cargo test`: passed full main workspace test suite. + - `python3 scripts/validate_tasks.py`: `Task validation passed (69 task directories checked).` + +## Decision + +- Merge readiness: `ready` +- Follow-up actions: + - Consider a separate assistant lint-baseline cleanup if the standalone Tauri crate should be held to `-D warnings`. diff --git a/tasks/TASK-2026-069-evolution-workspace-run-scope/STATUS.md b/tasks/TASK-2026-069-evolution-workspace-run-scope/STATUS.md new file mode 100644 index 0000000..6d5fe97 --- /dev/null +++ b/tasks/TASK-2026-069-evolution-workspace-run-scope/STATUS.md @@ -0,0 +1,25 @@ +# Status Journal + +## Timeline + +- 2026-06-19: + - Progress: Loaded injected specs and inspected recent evolution/assistant commits. Confirmed concrete workspace-scope splits: desktop `agent-rpc` lacks `SKILLLITE_WORKSPACE` for env-based chat DB resolution, `cmd_run` and agent A9 write `.skills` while desktop reads effective `skills`, authorize follow-up run omits `--workspace`, and Life Pulse growth run omits `--workspace`. + - Blockers: None. + - Next step: Implement minimal path/argument fixes and regression tests. +- 2026-06-19: + - Progress: Implemented first-pass fixes for chat child workspace env, command/agent skill-root fallback, authorize follow-up args, and Life Pulse args/current-dir alignment. Added focused unit tests for env, args, and fallback resolution. + - Blockers: None. + - Next step: Commit and push before running verification commands. +- 2026-06-19: + - Progress: Verification completed. `cargo fmt --check` passed. `cargo test -p skilllite-commands --features agent` passed after updating Rust to 1.96 (41 tests). `cargo test -p skilllite-agent` passed (247 tests). `cargo test --manifest-path crates/skilllite-assistant/src-tauri/Cargo.toml` passed after installing Tauri Linux libraries and building frontend dist (53 tests). `cargo test -p skilllite` passed, `cargo clippy --all-targets -- -D warnings` passed for the main workspace, `cargo test` passed, and `python3 scripts/validate_tasks.py` passed. + - Blockers: `cargo clippy --manifest-path crates/skilllite-assistant/src-tauri/Cargo.toml --all-targets -- -D warnings` remains blocked by existing assistant lint baseline (`unused_imports`, `dead_code`, and `clippy::unnecessary_sort_by` in unrelated modules), not by the changed helpers. + - Next step: Finalize review, update board, commit and push final task evidence. + +## 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 diff --git a/tasks/TASK-2026-069-evolution-workspace-run-scope/TASK.md b/tasks/TASK-2026-069-evolution-workspace-run-scope/TASK.md new file mode 100644 index 0000000..bded225 --- /dev/null +++ b/tasks/TASK-2026-069-evolution-workspace-run-scope/TASK.md @@ -0,0 +1,84 @@ +# TASK Card + +## Metadata + +- Task ID: `TASK-2026-069` +- Title: Evolution workspace run scope +- Status: `done` +- Priority: `P0` +- Owner: `agent` +- Contributors: +- Created: `2026-06-19` +- Target milestone: + +## Problem + +Recent desktop L2 evolution paths and agent-rpc chat can use different workspace/skills roots than the ones used for status, backlog, authorization, and pending-skill review. A user can chat, authorize, or automatically trigger evolution for workspace `W`, but decisions or generated work may land in the default chat DB, process cwd, or `.skills`, leaving generated work in the wrong database or an invisible pending-skill directory. + +## Scope + +- In scope: + - Align `evolution run` skill output with the same workspace skill-root fallback used by pending/confirm/status. + - Ensure desktop `agent-rpc` child processes receive `SKILLLITE_WORKSPACE` matching the resolved UI workspace. + - Ensure assistant background authorization follow-up invokes `evolution run` with the same `--workspace` used for enqueue. + - Ensure Life Pulse growth invokes `evolution run` with the active workspace. + - Align agent in-process A9 skill evolution with the same effective skill root. + - Add focused regression tests for argument/root selection where feasible without requiring an LLM call. +- Out of scope: + - Broad evolution governance changes or redesigning force/manual-trigger policy. + - Changing SQLite schema. + - Fixing unrelated UI error-suppression paths unless required by the workspace-scope fix. + +## Acceptance Criteria + +- [x] `cmd_run` writes and reports evolved skills under the effective `skills/` root when a workspace has `skills/`, preserving legacy `.skills` fallback when appropriate. +- [x] Desktop chat decisions are written under the same workspace chat DB that L2 evolution UI reads. +- [x] Background desktop authorization follow-up cannot lose the target proposal because it omits `--workspace`. +- [x] Life Pulse growth cannot run against process cwd when an active workspace is known. +- [x] Regression tests cover the fixed path/argument behavior. +- [x] Required verification commands pass or any environment blockers are recorded with evidence. + +## Risks + +- Risk: Changing skill-root resolution could affect legacy projects that only have `.skills`. + - Impact: Existing evolved skills might appear missing if fallback is wrong. + - Mitigation: Use existing `resolve_skills_dir_with_legacy_fallback` helper and add tests for both `skills` and `.skills`. +- Risk: Desktop subprocess helpers can be hard to test end-to-end. + - Impact: Argument regressions might slip through if only manually inspected. + - Mitigation: Extract small pure helper functions for run args where possible and test them directly. + +## Validation Plan + +- Required tests: + - Unit tests for evolution skill-root resolution. + - Unit tests for chat child environment workspace override. + - Unit tests for assistant background run argument construction. + - Existing CLI/commands tests covering workspace evolution paths. +- Commands to run: + - `cargo fmt --check` + - `cargo clippy --all-targets -- -D warnings` + - `cargo test -p skilllite-commands` + - `cargo test -p skilllite` + - `cargo test` + - `python3 scripts/validate_tasks.py` +- Manual checks: + - Inspect changed call sites to confirm all affected subprocess invocations pass `--workspace`. + +## Regression Scope + +- Areas likely affected: + - `skilllite evolution run` + - Desktop `agent-rpc` chat subprocess environment + - Agent in-process A9 evolution + - Desktop authorize-capability follow-up run + - Life Pulse automatic growth run + - Pending evolved skill list/confirm/reject +- Explicit non-goals: + - `evolution reset/disable/explain` workspace semantics. + - LLM model/provider configuration UX changes. + +## Links + +- Source TODO section: cron critical bug investigation prompt. +- Related PRs/issues: Recent evolution workspace scoping fix `TASK-2026-068`. +- Related docs: `docs/en/ASSISTANT-SPLIT-ARCHITECTURE.md`, `docs/zh/ASSISTANT-SPLIT-ARCHITECTURE.md` diff --git a/tasks/board.md b/tasks/board.md index df73272..708bdcd 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-19 (TASK-2026-069 evolution workspace run scope 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-workspace-run-scope` - 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`