Enhance tool documentation and add new features

- Added module-level documentation for memory tools (`remember`, `recall`, `forget`) to clarify their purpose.
- Improved documentation in `recall.rs` and `remember.rs` to describe the functionality and flow of memory entry operations.
- Updated `mod.rs` to include descriptions for the tool trait and execution context.
- Enhanced `plan.rs` with detailed comments on plan-mode signaling tools.
- Documented text search tools in `search.rs` to explain their functionality.
- Improved sequential-thinking tool documentation in `seqthink.rs`.
- Added safety filter documentation in `shell_filter` for credential and git operations.
- Enhanced utility tools documentation, including `cd`, `dir_cache_update`, and `todowrite`.
- Improved rendering documentation in view modules (`chat`, `markdown`, `status`, `workflow`) to clarify rendering flows and purposes.
This commit is contained in:
asepharyana
2026-07-12 11:28:39 +07:00
parent 7158d362fd
commit 2efd40ca88
124 changed files with 2379 additions and 19 deletions
+118
View File
@@ -1,3 +1,5 @@
//! Adaptive quality-review triggering, build/test probing, staleness
//! sweeps for stored lessons, and the pending-lesson approval workflow.
use std::process::Command;
use crate::app::state::rest::AppStateRest;
use crate::app::state::runtime::TurnEvent;
@@ -7,6 +9,7 @@ use crate::app::subagent::engine::run_subagent;
use crate::app::subagent::spawn::AgentDefinition;
use serde::{Deserialize, Serialize};
/// How much trust a lesson's origin/verification warrants.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Confidence {
Human,
@@ -15,6 +18,7 @@ pub enum Confidence {
Auto,
}
/// Where a lesson sits in its life cycle, from freshly written to superseded.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum LessonLifecycle {
New,
@@ -24,12 +28,14 @@ pub enum LessonLifecycle {
Superseded,
}
/// Whether a lesson applies to the current project only or globally.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum LessonScope {
Project,
Global,
}
/// Records who/what produced a lesson and in which session/turn.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Provenance {
pub session_turn: String,
@@ -37,6 +43,8 @@ pub struct Provenance {
pub reviewer: Origin,
}
/// A single learned fact/pattern surfaced by a review, prior to being
/// written to persistent memory.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Lesson {
pub name: String,
@@ -49,6 +57,18 @@ pub struct Lesson {
pub provenance: Provenance,
}
/// Decide whether an adaptive quality review should fire for this turn.
///
/// Flow: only `Origin::Main` turns are eligible → require review enabled
/// in settings → fire every 5th edit unconditionally → otherwise, once
/// `consecutive_empty_reviews` reaches `adaptive_review_max_skip` (min 2),
/// fire on an exponentially growing skip interval (2^n, capped at 2^10)
/// to avoid reviewing every single edit once reviews keep coming back empty.
///
/// Why: balances review usefulness against wasted subagent calls when
/// reviews consistently find nothing.
///
/// Return: `true` if a review should be triggered this turn.
pub fn should_trigger_review(state: &AppStateRest, origin: Origin) -> bool {
if origin != Origin::Main {
@@ -75,6 +95,7 @@ pub fn should_trigger_review(state: &AppStateRest, origin: Origin) -> bool {
}
false
}
/// Outcome of running a build/test probe command against a workspace.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProbeResult {
pub command: String,
@@ -82,6 +103,21 @@ pub struct ProbeResult {
pub output: String,
pub timed_out: bool,
}
/// Run a build/test verification command in the first workspace root and
/// capture its outcome, to back a review with a real pass/fail signal.
///
/// Flow: pick the first workspace → resolve the verify command (explicit
/// override or auto-detected via `resolve_verify_command`) → spawn it →
/// poll `try_wait` in a loop, killing the child if `timeout_ms` elapses →
/// capture combined stdout+stderr (truncated) on completion.
///
/// Why: polling instead of a blocking wait lets the timeout be enforced
/// without spawning a watcher thread.
///
/// Return: `None` if no workspace exists, no command could be resolved,
/// or the process failed to spawn/poll; otherwise `Some(ProbeResult)`
/// describing pass/fail/timeout and truncated output.
pub fn probe_build_test(workspaces: &[std::path::PathBuf], verify_command: Option<&str>, timeout_ms: u64) -> Option<ProbeResult> {
let probe_dir = workspaces.first()?;
let cmd = resolve_verify_command(probe_dir, verify_command)?;
@@ -131,6 +167,19 @@ pub fn probe_build_test(workspaces: &[std::path::PathBuf], verify_command: Optio
}
}
/// Determine the shell command to build/test a workspace, auto-detecting
/// the project type from marker files when no override is given.
///
/// Flow: use `override_cmd` verbatim if non-empty → otherwise probe for
/// language/tool marker files (Cargo.toml, go.mod, package.json, etc.)
/// in priority order and return that ecosystem's conventional test/build
/// command.
///
/// Why: covers a broad set of ecosystems so review probing works without
/// per-project configuration in the common case.
///
/// Return: `Some(command)` if a command could be determined, `None` if
/// no marker files matched (e.g. plain Python project with no test dir).
fn resolve_verify_command(probe_dir: &std::path::Path, override_cmd: Option<&str>) -> Option<String> {
if let Some(cmd) = override_cmd {
if !cmd.trim().is_empty() {
@@ -227,6 +276,10 @@ fn resolve_verify_command(probe_dir: &std::path::Path, override_cmd: Option<&str
None
}
/// Truncate a string to at most `max` characters, appending a marker if cut.
///
/// Return: the original string if short enough, otherwise the first `max`
/// characters plus `"... (truncated)"`.
fn truncate_output(s: &str, max: usize) -> String {
if s.len() <= max {
s.to_string()
@@ -237,6 +290,22 @@ fn truncate_output(s: &str, max: usize) -> String {
}
}
/// Spawn a background quality-review subagent for the current session.
///
/// Flow: build a "quality-reviewer" subagent context → probe build/test
/// status via `probe_build_test` to give the reviewer a real pass/fail
/// signal → compose a system prompt embedding the probe result and lesson
/// tagging instructions → spawn a thread running `run_subagent` → on
/// completion, push a `TurnEvent::SystemNote` with the verdict's first
/// line (or error) → push an "in progress" toast immediately.
///
/// Why: runs on a plain OS thread (not tokio) so it doesn't block the
/// async event loop; communicates its result back via `turn_events`
/// rather than a channel receiver (the `_rx` half is intentionally unused).
///
/// Return: `Ok(())` once the review has been kicked off; errors only
/// propagate from constructing the subagent context, not from the review
/// itself (that failure is reported via a `SystemNote` instead).
pub fn trigger_review(state: &mut AppStateRest) -> anyhow::Result<()> {
let def = AgentDefinition::new(
"quality-reviewer".to_string(),
@@ -310,6 +379,14 @@ pub fn trigger_review(state: &mut AppStateRest) -> anyhow::Result<()> {
const STALE_AFTER_DAYS: i64 = 60;
/// Flag memory entries as stale if they haven't been updated recently.
///
/// Flow: list all memory files → for each, read it → if `updated_at` is
/// older than `STALE_AFTER_DAYS` and it isn't already flagged, set
/// `lifecycle = "stale"` and write it back → collect flagged names.
///
/// Return: names of newly-flagged memories, or an I/O error from
/// `mem.write`.
pub fn run_staleness_sweep(memory_dir: &std::path::Path) -> std::io::Result<Vec<String>> {
let mut flagged = Vec::new();
let names = crate::model::memory::Memory::list(memory_dir);
@@ -327,6 +404,14 @@ pub fn run_staleness_sweep(memory_dir: &std::path::Path) -> std::io::Result<Vec<
Ok(flagged)
}
/// Run the staleness sweep at most once every 10 minutes, notifying via toast.
///
/// Flow: skip if less than 600,000ms since `last_staleness_sweep_ms` →
/// otherwise update the timestamp and run `run_staleness_sweep`, pushing
/// an info toast listing flagged lessons if any were found.
///
/// Why: rate-limited so the sweep (a file read/write per memory) doesn't
/// run on every event-loop tick.
pub fn maybe_run_staleness_sweep(state: &mut AppStateRest) {
let now = chrono::Utc::now().timestamp_millis();
if now.saturating_sub(state.misc.last_staleness_sweep_ms) < 600_000 {
@@ -343,6 +428,8 @@ pub fn maybe_run_staleness_sweep(state: &mut AppStateRest) {
}
}
/// A lesson awaiting confirmation before being committed to memory,
/// optionally auto-resolving after a grace period.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingLesson {
pub lesson: Lesson,
@@ -350,6 +437,10 @@ pub struct PendingLesson {
pub auto_resolve: bool,
}
/// Load the session's pending-lessons queue from disk.
///
/// Return: the parsed list, or an empty `Vec` if the file is missing or
/// fails to parse.
pub fn load_pending_lessons(session_dir: &std::path::Path) -> Vec<PendingLesson> {
let path = session_dir.join("pending_lessons.json");
std::fs::read_to_string(&path)
@@ -358,12 +449,28 @@ pub fn load_pending_lessons(session_dir: &std::path::Path) -> Vec<PendingLesson>
.unwrap_or_default()
}
/// Write the session's pending-lessons queue to disk as pretty JSON.
///
/// Return: `Ok(())`, or an I/O error from writing the file.
pub fn save_pending_lessons(session_dir: &std::path::Path, pending: &[PendingLesson]) -> std::io::Result<()> {
let path = session_dir.join("pending_lessons.json");
let data = serde_json::to_string_pretty(pending)?;
std::fs::write(&path, data)
}
/// Commit any auto-resolvable pending lessons whose grace period has
/// elapsed, and persist the remaining queue.
///
/// Flow: load pending lessons → partition into those eligible to commit
/// (`auto_resolve` and older than the 5s grace window) vs. still pending
/// → write eligible lessons as new `Memory` entries with `lifecycle:
/// "active"` → save the remaining (unresolved) queue back to disk.
///
/// Why: the grace window gives the user a brief window to reject an
/// auto-resolving lesson via `resolve_pending_lesson` before it commits.
///
/// Return: the still-pending lessons (post-commit), or an I/O error from
/// writing memory files or the queue.
pub fn process_pending_lessons(session_dir: &std::path::Path, memory_dir: &std::path::Path) -> std::io::Result<Vec<PendingLesson>> {
let pending = load_pending_lessons(session_dir);
let now = chrono::Utc::now().timestamp_millis();
@@ -399,6 +506,17 @@ pub fn process_pending_lessons(session_dir: &std::path::Path, memory_dir: &std::
save_pending_lessons(session_dir, &remaining)?;
Ok(remaining)
}
/// Manually resolve a single pending lesson by name: commit it to memory
/// or discard it.
///
/// Flow: load the queue → find the lesson matching `lesson_name` →
/// if `keep` is true, write it as an active `Memory` entry; either way
/// remove it from the queue → save the remaining queue.
///
/// Why: lets the user (or UI action) override a pending lesson's fate
/// before/without waiting for the auto-resolve grace window.
///
/// Return: `Ok(())`, or an I/O error from writing the memory file or queue.
pub fn resolve_pending_lesson(
session_dir: &std::path::Path,
memory_dir: &std::path::Path,