Skip to content

feat: auto-skill creation + cross-session FTS search (inspired by Hermes)#48

Merged
kienbui1995 merged 2 commits intomainfrom
feat/auto-skill-fts
Apr 13, 2026
Merged

feat: auto-skill creation + cross-session FTS search (inspired by Hermes)#48
kienbui1995 merged 2 commits intomainfrom
feat/auto-skill-fts

Conversation

@kienbui1995
Copy link
Copy Markdown
Owner

@kienbui1995 kienbui1995 commented Apr 13, 2026

Two features adapted from Hermes Agent for coding context:

1. Auto-skill creation

After complex successful turns (≥6 tool calls), agent auto-generates a reusable skill file:

~/.config/magic-code/skills/auto/auto-3t-8.md

Next time agent encounters similar task, it can load the skill. Won't overwrite existing skills.

2. Cross-session FTS search

/search-all fix auth timeout — searches ALL saved sessions:

🔍 3 matches for "fix auth timeout":
  📁 session-2026-04-10 (2026-04-10T14:30:00)
     ...the auth timeout was caused by missing refresh token...

Scoped to coding agent needs — no messaging gateway, no user modeling.

201 tests, 0 warnings.

Summary by CodeRabbit

  • New Features

    • /search-all command: cross-session full-text search with snippets, per-result timestamps, and total match counts (recency-sorted, capped).
    • Automatic skill generation: creates and saves Markdown "auto skills" from qualifying conversation turns into a user skills directory.
    • Forked sessions now preserve original creation timestamps; sessions include a created_at field.
  • Tests

    • Added unit tests for search and auto-skill behaviors.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 13, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Added cross-session full-text search and an auto-skill generator. New public modules mc_core::fts and mc_core::auto_skill implement search and automatic skill file creation; TUI/CLI plumbing to invoke search was added, and the runtime now may generate and persist auto-skill Markdown after a turn.

Changes

Cohort / File(s) Summary
Full-text search (core)
mc/crates/mc-core/src/fts.rs, mc/crates/mc-core/src/lib.rs
New public fts module with FtsResult and search_all_sessions(...) that scans recent session JSON files for case-insensitive matches and returns snippets; lib.rs exports pub mod fts.
Auto-skill generation (core & runtime)
mc/crates/mc-core/src/auto_skill.rs, mc/crates/mc-core/src/runtime.rs, mc/crates/mc-core/src/lib.rs
New public auto_skill module providing should_create_skill, generate_skill_content, and save_auto_skill. Runtime calls this after turns, may write Markdown to $HOME/.config/magic-code/skills/auto and emits a ProviderEvent::TextDelta on success; lib.rs exports pub mod auto_skill.
Session schema & branch fork
mc/crates/mc-core/src/session.rs, mc/crates/mc-core/src/branch.rs
Session gains created_at: String (serde default). BranchManager::fork now copies created_at into the forked session.
TUI / Commands
mc/crates/mc-tui/src/app.rs, mc/crates/mc-tui/src/commands.rs
Added PendingCommand::SearchAll(String) and /search-all slash command that queues a cross-session search request.
CLI integration
mc/crates/mc-cli/src/main.rs
run_tui now handles PendingCommand::SearchAll(query) by deriving the sessions directory and calling mc_core::fts::search_all_sessions, formatting results into the TUI output lines.

Sequence Diagram(s)

sequenceDiagram
    participant TUI as TUI (mc-tui)
    participant CLI as CLI (mc-cli)
    participant FTS as FTS (mc_core::fts)
    participant FS as Filesystem

    TUI->>CLI: enqueue SearchAll(query)
    CLI->>FTS: search_all_sessions(sessions_dir, query)
    FTS->>FS: read sessions_dir/*.json (sorted by mtime)
    FS-->>FTS: file contents
    FTS->>FTS: deserialize session, extract blocks, match query, build snippets
    FTS-->>CLI: Vec<FtsResult>
    CLI->>TUI: render results (header / lines / snippets)
Loading
sequenceDiagram
    participant Runtime as ConversationRuntime
    participant Auto as AutoSkill (mc_core::auto_skill)
    participant FS as Filesystem
    participant Provider as ProviderEvent

    Runtime->>Runtime: finalize turn, collect tool_calls & final_text
    Runtime->>Auto: should_create_skill(count, had_errors)
    alt should_create == true and HOME set
        Runtime->>Auto: generate_skill_content(summary, tools_used)
        Auto->>FS: create skills_dir/auto, create_new safe filename, write .md
        FS-->>Auto: write result (path or error)
        Auto-->>Runtime: saved path (Some) / None
        Runtime->>Provider: emit ProviderEvent::TextDelta("Saved auto skill: <path>")
    end
    Runtime-->>Caller: return TurnResult (unchanged)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I'm a rabbit with a pen and a grin,
I scanned the burrows deep within.
I stitched a skill from tools and time,
and hunted snippets in the thyme.
🐇✨ Saved a note — hop, let’s begin!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes both main features added in the changeset: auto-skill creation and cross-session FTS search.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/auto-skill-fts

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (1)
mc/crates/mc-core/src/auto_skill.rs (1)

35-37: Consider returning Result instead of collapsing all I/O errors to None.

Current behavior loses failure reason, which makes runtime diagnostics and user-facing feedback harder.

Refactor sketch
-pub fn save_auto_skill(skills_dir: &Path, name: &str, content: &str) -> Option<String> {
+pub fn save_auto_skill(skills_dir: &Path, name: &str, content: &str) -> std::io::Result<Option<String>> {
@@
-    if std::fs::create_dir_all(&dir).is_err() {
-        return None;
-    }
+    std::fs::create_dir_all(&dir)?;
@@
-        return None; // don't overwrite existing skills
+        return Ok(None); // don't overwrite existing skills
     }
-    std::fs::write(&path, content).ok()?;
-    Some(path.to_string_lossy().to_string())
+    std::fs::write(&path, content)?;
+    Ok(Some(path.to_string_lossy().to_string()))
 }

Also applies to: 52-53

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mc/crates/mc-core/src/auto_skill.rs` around lines 35 - 37, The code collapses
all I/O failures into None by calling std::fs::create_dir_all(&dir).is_err() and
returning None; change the enclosing function(s) in mc::auto_skill.rs to return
Result<T, std::io::Error> (or a crate error type) instead of Option, replace the
is_err() checks with match or ? to propagate the std::io::Error from
std::fs::create_dir_all(&dir), and update the two spots (the create_dir_all call
around lines 35–37 and the similar call at 52–53) to return Err(e) (or propagate
with ?) so callers can handle/inspect the real I/O error; remember to update
callers' signatures and call sites accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mc/crates/mc-core/src/auto_skill.rs`:
- Line 33: The public function save_auto_skill returns an Option<String> and its
result must not be dropped silently; add the #[must_use] attribute directly
above the pub fn save_auto_skill(...) declaration so callers get a compiler
warning if they ignore the return value (apply the same pattern to other public
functions returning values where needed).
- Around line 77-83: The test creates a temp directory name using only
std::process::id(), which can collide when tests run in parallel; change the
test to append a unique atomic counter to the temp path (e.g., use a static
AtomicUsize or AtomicU64 incremented with fetch_add) so the dir becomes
std::env::temp_dir().join(format!("mc-skill-{}-{}", std::process::id(),
UNIQUE_COUNTER.fetch_add(1, Ordering::SeqCst))); update the test that calls
save_auto_skill (the variables dir, path, path2) to use this new unique dir and
keep the same assertions and cleanup.
- Around line 49-53: Replace the TOCTOU pattern that checks path.exists() then
calls std::fs::write by atomically creating the file with
OpenOptions::new().write(true).create_new(true).open(...) and writing content
via write_all so creation fails if the file already exists (reference the
variables path and content and the current std::fs::write call). In tests that
currently use process::id() for temp directory names, switch to a global atomic
counter (e.g., an AtomicUsize in the test module) to generate unique suffixes
per test run to avoid collisions when tests run in parallel.

In `@mc/crates/mc-core/src/fts.rs`:
- Around line 57-71: FTS currently only searches crate::session::Block::Text and
skips other text-bearing variants; update the inner loop over session.messages
in mc/crates/mc-core/src/fts.rs to also extract and search text from
Block::Thinking { text }, Block::ToolUse { input }, and Block::ToolResult {
output } (use a match that binds a &str for each variant), then reuse the
existing search/slicing logic (q, start/end, snippet, file_name,
session.created_at, results.push) so command inputs, tool outputs and thinking
text are included in results.
- Around line 60-66: The code computes pos from text.to_lowercase().find(&q) but
then uses that byte offset to slice the original text, which can panic on
Unicode boundaries; update the logic in the FTS search where pos, start, end,
and snippet are computed (references: text, q, pos, start, end, snippet,
FtsResult) to derive safe byte indices from character indices—e.g., iterate
text.char_indices() to map the lowercase match position back to the original
string’s byte offsets (or perform the find on a normalized buffer and slice that
same buffer) so slicing uses valid UTF-8 byte boundaries and cannot panic.

In `@mc/crates/mc-core/src/runtime.rs`:
- Around line 683-686: The filename generation for auto skills uses
format!("auto-{iterations}t-{}", tool_calls.len()) inside the call to
crate::auto_skill::save_auto_skill which causes collisions; change the naming to
include a unique suffix (e.g., current timestamp, a monotonic counter, or a
short hash of content) so each save_auto_skill call produces a distinct name
(for example add chrono::Utc::now().timestamp_millis(), a global atomic counter,
or a content hash into the formatted string) and pass that new unique filename
to crate::auto_skill::save_auto_skill to preserve non-overwrite behavior while
still keeping the iterations/tool count in the name.
- Around line 676-677: The current had_errors value is computed from tool_calls
(a Vec of tool names) so t.contains("error") checks names, not execution
results; update the code that pushes into tool_calls (the two push sites) to
also record the failure flag (either change tool_calls to store a struct/tuple
like (name, is_error) or maintain a parallel Vec<bool> tool_errors), compute
had_errors from those recorded is_error values, and then call
crate::auto_skill::should_create_skill(tool_calls.len(), had_errors) using the
real failure-derived had_errors; ensure any code reading tool_calls is adjusted
to the new shape or continues to read only names.

In `@mc/crates/mc-core/src/session.rs`:
- Around line 191-193: The Session struct's created_at field remains empty
because Session::default() (used by Session::default in runtime and propagated
by branch code) leaves created_at as "" due to #[serde(default)]; set created_at
to the current timestamp when a new Session is constructed or immediately before
the first save: update the Session::default() (or the factory that creates
sessions) to assign created_at = Utc::now().to_rfc3339() (or equivalent
timestamp provider) so newly created sessions serialize a real timestamp and
downstream code (e.g., search-all) can rely on it.

In `@mc/crates/mc-tui/src/commands.rs`:
- Line 49: The /search-all branch is wired but not discoverable; add the
"/search-all" entry to the application's slash-command registry
(App::SLASH_COMMANDS) and include it in the help/discoverability list so it
appears in /help and Tab completion; ensure the help text/usage string matches
the one used in commands.rs (the Usage: /search-all <keyword> …) and that the
command maps to PendingCommand::SearchAll to keep behavior consistent.

---

Nitpick comments:
In `@mc/crates/mc-core/src/auto_skill.rs`:
- Around line 35-37: The code collapses all I/O failures into None by calling
std::fs::create_dir_all(&dir).is_err() and returning None; change the enclosing
function(s) in mc::auto_skill.rs to return Result<T, std::io::Error> (or a crate
error type) instead of Option, replace the is_err() checks with match or ? to
propagate the std::io::Error from std::fs::create_dir_all(&dir), and update the
two spots (the create_dir_all call around lines 35–37 and the similar call at
52–53) to return Err(e) (or propagate with ?) so callers can handle/inspect the
real I/O error; remember to update callers' signatures and call sites
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8fd65e52-05ba-462b-bb9e-2f016d0a0b02

📥 Commits

Reviewing files that changed from the base of the PR and between a9c2f74 and 61644e5.

📒 Files selected for processing (9)
  • mc/crates/mc-cli/src/main.rs
  • mc/crates/mc-core/src/auto_skill.rs
  • mc/crates/mc-core/src/branch.rs
  • mc/crates/mc-core/src/fts.rs
  • mc/crates/mc-core/src/lib.rs
  • mc/crates/mc-core/src/runtime.rs
  • mc/crates/mc-core/src/session.rs
  • mc/crates/mc-tui/src/app.rs
  • mc/crates/mc-tui/src/commands.rs

Comment on lines +77 to +83
let dir = std::env::temp_dir().join(format!("mc-skill-{}", std::process::id()));
let path = save_auto_skill(&dir, "test-skill", "# Test");
assert!(path.is_some());
// Don't overwrite
let path2 = save_auto_skill(&dir, "test-skill", "# Test 2");
assert!(path2.is_none());
std::fs::remove_dir_all(&dir).ok();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use an atomic counter in test temp paths to avoid parallel collisions.

Line 77 uses only process ID, which can collide across tests in the same process.

Proposed fix
 #[cfg(test)]
 mod tests {
     use super::*;
+    use std::sync::atomic::{AtomicUsize, Ordering};
+
+    static TEMP_COUNTER: AtomicUsize = AtomicUsize::new(0);
@@
     fn save_skill() {
-        let dir = std::env::temp_dir().join(format!("mc-skill-{}", std::process::id()));
+        let unique = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
+        let dir = std::env::temp_dir()
+            .join(format!("mc-skill-{}-{unique}", std::process::id()));
         let path = save_auto_skill(&dir, "test-skill", "# Test");
         assert!(path.is_some());

As per coding guidelines: "Use atomic counter for unique temp file paths in tests to avoid parallel test collisions".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let dir = std::env::temp_dir().join(format!("mc-skill-{}", std::process::id()));
let path = save_auto_skill(&dir, "test-skill", "# Test");
assert!(path.is_some());
// Don't overwrite
let path2 = save_auto_skill(&dir, "test-skill", "# Test 2");
assert!(path2.is_none());
std::fs::remove_dir_all(&dir).ok();
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
static TEMP_COUNTER: AtomicUsize = AtomicUsize::new(0);
#[test]
fn save_skill() {
let unique = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
let dir = std::env::temp_dir()
.join(format!("mc-skill-{}-{unique}", std::process::id()));
let path = save_auto_skill(&dir, "test-skill", "# Test");
assert!(path.is_some());
// Don't overwrite
let path2 = save_auto_skill(&dir, "test-skill", "# Test 2");
assert!(path2.is_none());
std::fs::remove_dir_all(&dir).ok();
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mc/crates/mc-core/src/auto_skill.rs` around lines 77 - 83, The test creates a
temp directory name using only std::process::id(), which can collide when tests
run in parallel; change the test to append a unique atomic counter to the temp
path (e.g., use a static AtomicUsize or AtomicU64 incremented with fetch_add) so
the dir becomes std::env::temp_dir().join(format!("mc-skill-{}-{}",
std::process::id(), UNIQUE_COUNTER.fetch_add(1, Ordering::SeqCst))); update the
test that calls save_auto_skill (the variables dir, path, path2) to use this new
unique dir and keep the same assertions and cleanup.

Comment on lines +60 to +66
if let Some(pos) = text.to_lowercase().find(&q) {
let start = pos.saturating_sub(60);
let end = (pos + q.len() + 60).min(text.len());
results.push(FtsResult {
session_file: file_name.clone(),
timestamp: session.created_at.clone(),
snippet: format!("...{}...", &text[start..end]),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "fts.rs" | grep mc-core

Repository: kienbui1995/mc-code

Length of output: 93


🏁 Script executed:

cat -n mc/crates/mc-core/src/fts.rs | head -100

Repository: kienbui1995/mc-code

Length of output: 3550


The snippet slice can panic on valid Unicode content.

pos is computed from text.to_lowercase().find(&q), then reused to slice the original text. When lowercasing changes byte lengths of characters (e.g., 'ẞ' → 'ß'), the byte offset becomes misaligned between the lowercase and original versions. Using misaligned offsets to slice the original text can panic if they land on invalid UTF-8 boundaries. Compute snippet bounds from text.char_indices() or keep search and slicing on the same normalized buffer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mc/crates/mc-core/src/fts.rs` around lines 60 - 66, The code computes pos
from text.to_lowercase().find(&q) but then uses that byte offset to slice the
original text, which can panic on Unicode boundaries; update the logic in the
FTS search where pos, start, end, and snippet are computed (references: text, q,
pos, start, end, snippet, FtsResult) to derive safe byte indices from character
indices—e.g., iterate text.char_indices() to map the lowercase match position
back to the original string’s byte offsets (or perform the find on a normalized
buffer and slice that same buffer) so slicing uses valid UTF-8 byte boundaries
and cannot panic.

Comment on lines +676 to +677
let had_errors = tool_calls.iter().any(|t| t.contains("error"));
if crate::auto_skill::should_create_skill(tool_calls.len(), had_errors) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

had_errors is derived from tool names, not tool failures.

tool_calls only contains the names pushed at mc/crates/mc-core/src/runtime.rs:479 and mc/crates/mc-core/src/runtime.rs:495, so t.contains("error") is almost always false. Failed tool executions will still pass the “no errors” gate and produce auto-skills unless you track the actual is_error values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mc/crates/mc-core/src/runtime.rs` around lines 676 - 677, The current
had_errors value is computed from tool_calls (a Vec of tool names) so
t.contains("error") checks names, not execution results; update the code that
pushes into tool_calls (the two push sites) to also record the failure flag
(either change tool_calls to store a struct/tuple like (name, is_error) or
maintain a parallel Vec<bool> tool_errors), compute had_errors from those
recorded is_error values, and then call
crate::auto_skill::should_create_skill(tool_calls.len(), had_errors) using the
real failure-derived had_errors; ensure any code reading tool_calls is adjusted
to the new shape or continues to read only names.

Comment on lines 191 to +193
#[serde(default)]
pub created_at: String,
#[serde(default)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

created_at is never populated for new sessions.

#[serde(default)] keeps old JSON loadable, but new sessions still start as Session::default() in mc/crates/mc-core/src/runtime.rs:103, and mc/crates/mc-core/src/branch.rs:41-48 only propagates that value. As written, newly saved sessions serialize created_at: "", so /search-all cannot return the timestamp promised by this feature unless you stamp it when the session is created or before first save.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mc/crates/mc-core/src/session.rs` around lines 191 - 193, The Session
struct's created_at field remains empty because Session::default() (used by
Session::default in runtime and propagated by branch code) leaves created_at as
"" due to #[serde(default)]; set created_at to the current timestamp when a new
Session is constructed or immediately before the first save: update the
Session::default() (or the factory that creates sessions) to assign created_at =
Utc::now().to_rfc3339() (or equivalent timestamp provider) so newly created
sessions serialize a real timestamp and downstream code (e.g., search-all) can
rely on it.

"/init" => app.pending_command = Some(PendingCommand::Init),
"/summary" => app.pending_command = Some(PendingCommand::Summary),
"/search" => if arg.is_empty() { app.push("Usage: /search <keyword>"); } else { app.pending_command = Some(PendingCommand::Search(arg.into())); },
"/search-all" => if arg.is_empty() { app.push("Usage: /search-all <keyword> — search across all saved sessions"); } else { app.pending_command = Some(PendingCommand::SearchAll(arg.into())); },
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep /search-all in the discoverability lists.

This branch wires the command correctly, but "/search-all" is still missing from the /help output here and from App::SLASH_COMMANDS in mc/crates/mc-tui/src/app.rs:349-427, so users cannot discover it via help or Tab completion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mc/crates/mc-tui/src/commands.rs` at line 49, The /search-all branch is wired
but not discoverable; add the "/search-all" entry to the application's
slash-command registry (App::SLASH_COMMANDS) and include it in the
help/discoverability list so it appears in /help and Tab completion; ensure the
help text/usage string matches the one used in commands.rs (the Usage:
/search-all <keyword> …) and that the command maps to PendingCommand::SearchAll
to keep behavior consistent.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
mc/crates/mc-core/src/runtime.rs (1)

676-677: ⚠️ Potential issue | 🟠 Major

had_errors still checks tool names, not execution results.

The tool_calls vector only contains tool names (populated at lines 479 and 495 with .name.clone()). The is_error flag from tool execution is available at both sites but never captured:

  • Line 479: r.is_error is present in batch results
  • Line 495: is_error is returned from execute_tool

To fix, either change tool_calls to Vec<(String, bool)> storing (name, is_error), or maintain a separate Vec<bool> for error flags.

,

🐛 Proposed fix to track actual tool errors
-        let mut tool_calls = Vec::new();
+        let mut tool_calls: Vec<(String, bool)> = Vec::new();

At line 479:

-                tool_calls.push(r.name.clone());
+                tool_calls.push((r.name.clone(), r.is_error));

At line 495:

-                tool_calls.push(name.clone());
+                tool_calls.push((name.clone(), is_error));

Then at line 676:

-        let had_errors = tool_calls.iter().any(|t| t.contains("error"));
-        if crate::auto_skill::should_create_skill(tool_calls.len(), had_errors) {
+        let had_errors = tool_calls.iter().any(|(_, is_error)| *is_error);
+        if crate::auto_skill::should_create_skill(tool_calls.len(), had_errors) {

Note: This also requires updating TurnResult.tool_calls and extracting just names where needed (e.g., line 682 for generate_skill_content).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mc/crates/mc-core/src/runtime.rs` around lines 676 - 677, The code currently
records only tool names in tool_calls so had_errors = tool_calls.iter().any(|t|
t.contains("error")) inspects names instead of execution results; update the
flow to propagate execution error flags from r.is_error (batch results) and the
is_error returned by execute_tool into the recorded data (either change
tool_calls to Vec<(String, bool)> or keep a parallel Vec<bool>), update
TurnResult.tool_calls accordingly, capture and store the boolean error when
pushing at the sites that now push .name.clone() (the batch handling around
r.is_error and the execute_tool call), and then change the had_errors check used
by crate::auto_skill::should_create_skill and any subsequent usages (e.g.,
generate_skill_content) to inspect the stored booleans or extract names-only
where needed.
🧹 Nitpick comments (1)
mc/crates/mc-core/src/runtime.rs (1)

678-679: HOME environment variable is Unix-specific.

std::env::var_os("HOME") returns None on Windows, so auto-skill creation will silently be skipped on that platform. If cross-platform support is intended, consider using the dirs crate (dirs::config_dir()) or std::env::var_os("USERPROFILE") as a Windows fallback.

♻️ Cross-platform alternative
-            let skills_dir = std::env::var_os("HOME")
-                .map(|h| std::path::PathBuf::from(h).join(".config/magic-code/skills"));
+            let skills_dir = dirs::config_dir()
+                .map(|c| c.join("magic-code/skills"));

This would require adding dirs to dependencies.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mc/crates/mc-core/src/runtime.rs` around lines 678 - 679, The code computing
skills_dir via std::env::var_os("HOME") is Unix-specific and will be None on
Windows; update the logic that sets skills_dir in runtime.rs (the variable named
skills_dir) to use a cross-platform config directory (e.g., use
dirs::config_dir() and join ".magic-code/skills") or at minimum fall back to
std::env::var_os("USERPROFILE") when HOME is missing; if you choose
dirs::config_dir(), add the dirs crate to Cargo.toml and ensure you handle
Option results and join the path similarly to the original code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@mc/crates/mc-core/src/runtime.rs`:
- Around line 676-677: The code currently records only tool names in tool_calls
so had_errors = tool_calls.iter().any(|t| t.contains("error")) inspects names
instead of execution results; update the flow to propagate execution error flags
from r.is_error (batch results) and the is_error returned by execute_tool into
the recorded data (either change tool_calls to Vec<(String, bool)> or keep a
parallel Vec<bool>), update TurnResult.tool_calls accordingly, capture and store
the boolean error when pushing at the sites that now push .name.clone() (the
batch handling around r.is_error and the execute_tool call), and then change the
had_errors check used by crate::auto_skill::should_create_skill and any
subsequent usages (e.g., generate_skill_content) to inspect the stored booleans
or extract names-only where needed.

---

Nitpick comments:
In `@mc/crates/mc-core/src/runtime.rs`:
- Around line 678-679: The code computing skills_dir via
std::env::var_os("HOME") is Unix-specific and will be None on Windows; update
the logic that sets skills_dir in runtime.rs (the variable named skills_dir) to
use a cross-platform config directory (e.g., use dirs::config_dir() and join
".magic-code/skills") or at minimum fall back to std::env::var_os("USERPROFILE")
when HOME is missing; if you choose dirs::config_dir(), add the dirs crate to
Cargo.toml and ensure you handle Option results and join the path similarly to
the original code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dd66eb44-d981-4720-b33d-7936eafb0f46

📥 Commits

Reviewing files that changed from the base of the PR and between 61644e5 and 29507f4.

📒 Files selected for processing (3)
  • mc/crates/mc-core/src/auto_skill.rs
  • mc/crates/mc-core/src/fts.rs
  • mc/crates/mc-core/src/runtime.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • mc/crates/mc-core/src/fts.rs
  • mc/crates/mc-core/src/auto_skill.rs

1. Auto-skill creation (mc-core/src/auto_skill.rs):
   - After complex turns (≥6 tool calls, no errors), auto-generates skill file
   - Saves to ~/.config/magic-code/skills/auto/
   - Won't overwrite existing skills
   - Notifies user via TextDelta event

2. Cross-session FTS search (mc-core/src/fts.rs):
   - /search-all <query> searches across all saved session files
   - Returns session name, timestamp, and context snippet
   - Scans up to 100 most recent sessions, max 20 results
   - Case-insensitive matching

3. Session.created_at field added for timestamp tracking

201 tests (4 new), 0 warnings.
1. auto_skill: atomic create_new(true) instead of exists() check (TOCTOU fix)
2. auto_skill: add #[must_use] to save_auto_skill
3. auto_skill: add timestamp to filename (collision fix)
4. fts: safe byte slicing via char_indices (unicode panic fix)
5. fts: search Thinking, ToolUse, ToolResult blocks too

201 tests, 0 warnings.
@kienbui1995 kienbui1995 force-pushed the feat/auto-skill-fts branch from 29507f4 to 604b2b9 Compare April 13, 2026 02:50
@kienbui1995 kienbui1995 enabled auto-merge (squash) April 13, 2026 02:51
@kienbui1995 kienbui1995 merged commit e8671ca into main Apr 13, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant