Skip to content

Commit 51a1081

Browse files
authored
feat: add icm wake-up command + icm_wake_up MCP tool (#84)
* feat: add `icm wake-up` command + `icm_wake_up` MCP tool Adds a compact critical-facts pack generator for LLM system-prompt injection, inspired by MemPalace's wake-up command but grounded in ICM's importance/decay model. The command selects critical/high memories (plus global preferences) optionally scoped by project, ranks by importance × recency × weight, categorizes into Identity / Decisions / Constraints / Errors / Milestones / Context sections, and truncates to fit a token budget. - `icm-core`: new `wake_up` module with pure, testable `build_wake_up()` and `build_wake_up_from_memories()` APIs. 14 unit tests cover filtering, project scoping, budget truncation, formatting, and categorization. - `icm-cli`: new `wake-up` subcommand with `--project`, `--max-tokens`, `--format markdown|plain`, `--no-preferences`. Auto-detects project from PWD/git remote; use `--project -` to disable. - `icm-mcp`: new `icm_wake_up` tool with the same parameters, clamped token budget [20, 4000]. 7 MCP integration tests cover empty store, filtering, project scoping, plain format, budget clamping, preference opt-out, and tool listing. Total: 21 new tests, all green. Clippy clean. * refactor(wake-up): address review feedback - project_matches is now segment-aware: the topic and the project filter are both split on `-./_:/` and every project segment must appear as a complete topic segment. Rejects false positives like "icm" matching "icmp-notes" while still allowing "icm" -> "decisions-icm-core" and "icm-core" -> "decisions-icm-core". - compute_score now uses max(created_at, last_accessed) so frequently recalled memories stay fresh under decay. - truncate_by_budget and approx_tokens now count chars (not bytes) so multibyte summaries are not over-penalized and the token count printed in the header is honest for non-ASCII. - sanitize_summary flattens newlines and collapses runs of spaces, so a user-stored summary containing "\n## Fake header" cannot break the rendered section structure. - render_body trailing-newline loop fixed (was "\n\n\n" check that never triggered — now "\n\n"). - cmd_wake_up prints "Project: <detected>" to stderr when auto-detecting, so users understand why specific topics appear or not. - tool_wake_up accepts "" and "-" as project-disable sentinels, matching the CLI. - build_wake_up_from_memories marked #[must_use]. - +6 new unit tests covering segment matching, include_preferences=false, multiline sanitization, multibyte budget, access-aware recency, and plain format header suppression (20 core tests total).
1 parent 34b6c88 commit 51a1081

File tree

4 files changed

+1026
-4
lines changed

4 files changed

+1026
-4
lines changed

crates/icm-cli/src/main.rs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ use clap::{Parser, Subcommand, ValueEnum};
1717
use serde_json::Value;
1818

1919
use icm_core::{
20-
keyword_matches, topic_matches, Concept, ConceptLink, Feedback, FeedbackStore, Importance,
21-
Label, Memoir, MemoirStore, Memory, MemoryStore, Relation, MSG_NO_MEMORIES,
20+
build_wake_up, keyword_matches, topic_matches, Concept, ConceptLink, Feedback, FeedbackStore,
21+
Importance, Label, Memoir, MemoirStore, Memory, MemoryStore, Relation, WakeUpFormat,
22+
WakeUpOptions, MSG_NO_MEMORIES,
2223
};
2324
use icm_store::SqliteStore;
2425

@@ -279,6 +280,29 @@ enum Commands {
279280
limit: usize,
280281
},
281282

283+
/// Print a compact critical-facts pack for LLM system-prompt injection
284+
///
285+
/// Selects critical/high memories (and preferences) optionally scoped by
286+
/// project, ranks them by importance × recency × weight, then truncates
287+
/// to fit the token budget. Inspired by MemPalace's `wake-up` command.
288+
WakeUp {
289+
/// Project filter (default: auto-detect from PWD/git remote; use "-" to disable)
290+
#[arg(short, long)]
291+
project: Option<String>,
292+
293+
/// Approximate token budget (1 token ≈ 4 characters)
294+
#[arg(short = 't', long, default_value = "200")]
295+
max_tokens: usize,
296+
297+
/// Output format
298+
#[arg(short, long, default_value = "markdown")]
299+
format: CliWakeUpFormat,
300+
301+
/// Exclude global preferences/identity memories
302+
#[arg(long)]
303+
no_preferences: bool,
304+
},
305+
282306
/// Auto-save context for the current project (detects from PWD / git remote)
283307
SaveProject {
284308
/// Summary of what was done in this session
@@ -633,6 +657,21 @@ impl From<CliImportance> for Importance {
633657
}
634658
}
635659

660+
#[derive(Clone, Copy, ValueEnum)]
661+
enum CliWakeUpFormat {
662+
Markdown,
663+
Plain,
664+
}
665+
666+
impl From<CliWakeUpFormat> for WakeUpFormat {
667+
fn from(val: CliWakeUpFormat) -> Self {
668+
match val {
669+
CliWakeUpFormat::Markdown => WakeUpFormat::Markdown,
670+
CliWakeUpFormat::Plain => WakeUpFormat::Plain,
671+
}
672+
}
673+
}
674+
636675
#[derive(Clone, ValueEnum)]
637676
enum CliImportFormat {
638677
Auto,
@@ -923,6 +962,12 @@ fn main() -> Result<()> {
923962
}
924963
Commands::RecallContext { query, limit } => cmd_recall_context(&store, &query, limit),
925964
Commands::RecallProject { limit } => cmd_recall_project(&store, limit),
965+
Commands::WakeUp {
966+
project,
967+
max_tokens,
968+
format,
969+
no_preferences,
970+
} => cmd_wake_up(&store, project, max_tokens, format, no_preferences),
926971
Commands::SaveProject {
927972
content,
928973
importance,
@@ -2578,6 +2623,48 @@ fn cmd_recall_project(store: &SqliteStore, limit: usize) -> Result<()> {
25782623
Ok(())
25792624
}
25802625

2626+
/// Build and print a wake-up pack for LLM system-prompt injection.
2627+
///
2628+
/// Selects critical/high memories (plus preferences) optionally scoped to a
2629+
/// project, ranks by importance × recency × weight, and truncates to fit the
2630+
/// token budget.
2631+
fn cmd_wake_up(
2632+
store: &SqliteStore,
2633+
project: Option<String>,
2634+
max_tokens: usize,
2635+
format: CliWakeUpFormat,
2636+
no_preferences: bool,
2637+
) -> Result<()> {
2638+
// Resolve project: explicit "-" disables, None auto-detects, Some(name) uses it.
2639+
let detected;
2640+
let project_ref: Option<&str> = match project.as_deref() {
2641+
Some("-") => None,
2642+
Some(p) => Some(p),
2643+
None => {
2644+
detected = detect_project();
2645+
if detected.is_empty() || detected == "unknown" {
2646+
None
2647+
} else {
2648+
// Make auto-detection visible so users understand why
2649+
// specific topics show (or don't).
2650+
eprintln!("Project: {detected} (auto-detected; use --project - to disable)");
2651+
Some(detected.as_str())
2652+
}
2653+
}
2654+
};
2655+
2656+
let opts = WakeUpOptions {
2657+
project: project_ref,
2658+
max_tokens,
2659+
format: format.into(),
2660+
include_preferences: !no_preferences,
2661+
};
2662+
2663+
let pack = build_wake_up(store, &opts)?;
2664+
print!("{pack}");
2665+
Ok(())
2666+
}
2667+
25812668
fn cmd_save_project(
25822669
store: &SqliteStore,
25832670
embedder: Option<&dyn icm_core::Embedder>,

crates/icm-core/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod memoir;
99
pub mod memoir_store;
1010
pub mod memory;
1111
pub mod store;
12+
pub mod wake_up;
1213

1314
/// Default embedding vector dimensions (used when no embedder is configured).
1415
pub const DEFAULT_EMBEDDING_DIMS: usize = 384;
@@ -25,6 +26,7 @@ pub use memory::{
2526
Importance, Memory, MemorySource, PatternCluster, Scope, StoreStats, TopicHealth,
2627
};
2728
pub use store::MemoryStore;
29+
pub use wake_up::{build_wake_up, build_wake_up_from_memories, WakeUpFormat, WakeUpOptions};
2830

2931
pub use learn::{learn_project, LearnResult};
3032

0 commit comments

Comments
 (0)