Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.18.0] - 2026-06-12

### Added
- **MCP `memory_note` tool** — the agent can now record a durable user
preference or standing fact itself (not just the user via the CLI), so it
learns how you work over time. De-duplicated; injected into every future
session like CLI-added preferences.
- **`stats` now reports memory metrics** — the global cross-project recall
index size and the number of stored preferences, so the memory platform's
state is visible at a glance.

### Notes
- Consolidation (clustering episodic events into durable semantic/procedural
facts) is intentionally deferred: a good version needs the offline LLM
backend for quality, and a pure heuristic would manufacture noise. Tracked
separately. claude-mem/mem0 *import* likewise awaits their on-disk format +
sample data rather than being guessed at.

## [0.17.0] - 2026-06-12

### Added
Expand Down
7 changes: 4 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ members = [
]

[workspace.package]
version = "0.17.0"
version = "0.18.0"
edition = "2021"
rust-version = "1.88"
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion crates/tj-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ default = ["embed"]
embed = ["tj-core/embed"]

[dependencies]
tj-core = { package = "task-journal-core", version = "0.17.0", path = "../tj-core", default-features = false }
tj-core = { package = "task-journal-core", version = "0.18.0", path = "../tj-core", default-features = false }
anyhow = { workspace = true }
clap = { workspace = true }
tracing = { workspace = true }
Expand Down
13 changes: 13 additions & 0 deletions crates/tj-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1849,6 +1849,19 @@ fn main() -> Result<()> {
let ratio = confirmed as f64 / total as f64 * 100.0;
println!(" confirmed ratio: {ratio:.1}%");
}
// Memory platform (Pillars A/B/C): the global cross-project index.
let mem_path = tj_core::paths::memory_db()?;
if mem_path.exists() {
if let Ok(g) = tj_core::memory::open(&mem_path) {
let entries = tj_core::memory::count(&g).unwrap_or(0);
let prefs = tj_core::memory::list_preferences(&g)
.map(|p| p.len())
.unwrap_or(0);
println!("memory (global cross-project recall index):");
println!(" recall entries: {entries}");
println!(" preferences: {prefs}");
}
}
}
Commands::Doctor { json } => {
let report = run_doctor()?;
Expand Down
19 changes: 19 additions & 0 deletions crates/tj-cli/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5190,3 +5190,22 @@ fn remembered_preference_lists_and_injects_at_session_start() {
);
assert!(body.contains("additionalContext"));
}

#[test]
fn stats_reports_memory_preferences_count() {
// stats surfaces the global memory state (Pillar A/B/C metrics).
let dir = assert_fs::TempDir::new().unwrap();
Command::cargo_bin("task-journal")
.unwrap()
.env("XDG_DATA_HOME", dir.path())
.args(["remember", "respond in Russian"])
.assert()
.success();
Command::cargo_bin("task-journal")
.unwrap()
.env("XDG_DATA_HOME", dir.path())
.args(["stats"])
.assert()
.success()
.stdout(contains("preferences: 1"));
}
3 changes: 2 additions & 1 deletion crates/tj-mcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ path = "src/main.rs"

[dependencies]
# Lean: the MCP server doesn't embed yet, so it skips the model2vec backend.
tj-core = { package = "task-journal-core", version = "0.17.0", path = "../tj-core", default-features = false }
tj-core = { package = "task-journal-core", version = "0.18.0", path = "../tj-core", default-features = false }
anyhow = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
Expand All @@ -27,6 +27,7 @@ serde = { workspace = true }
serde_json = { workspace = true }
schemars = { workspace = true }
ulid = { workspace = true }
chrono = { workspace = true }
rusqlite = { workspace = true }
clap = { workspace = true }

Expand Down
37 changes: 37 additions & 0 deletions crates/tj-mcp/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,20 @@ pub struct TaskCloseResult {
pub completeness_gaps: Vec<String>,
}

#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct MemoryNoteParams {
/// The durable user preference or standing fact to remember across all
/// projects and sessions — e.g. "respond in Russian, terse", "this team
/// always squash-merges". Keep it one short sentence.
pub text: String,
}

#[derive(Debug, Serialize, schemars::JsonSchema)]
pub struct MemoryNoteResult {
pub remembered: bool,
pub text: String,
}

fn parse_event_type(s: &str) -> anyhow::Result<tj_core::event::EventType> {
use tj_core::event::EventType::*;
Ok(match s {
Expand Down Expand Up @@ -664,6 +678,29 @@ impl TaskJournalServer {
})
.await
}

#[tool(
name = "memory_note",
description = "Remember a durable user preference or standing fact across ALL projects and sessions — how the user wants to be worked with (\"respond in Russian, terse\"), or a stable team/project rule. Injected into every future session's context. Use it when you learn something about the user or their workflow that should outlive this task. De-duplicated."
)]
async fn memory_note(
&self,
Parameters(p): Parameters<MemoryNoteParams>,
) -> Result<Json<MemoryNoteResult>, McpError> {
traced_tool("memory_note", async move {
run_blocking(move || {
let global = tj_core::memory::open(tj_core::paths::memory_db()?)?;
let now = chrono::Utc::now().to_rfc3339();
let remembered = tj_core::memory::add_preference(&global, &p.text, &now)?;
Ok(Json(MemoryNoteResult {
remembered,
text: p.text.trim().to_string(),
}))
})
.await
})
.await
}
}

#[tool_handler(router = Self::tool_router())]
Expand Down
2 changes: 1 addition & 1 deletion plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "task-journal",
"version": "0.17.0",
"version": "0.18.0",
"description": "Append-only journal of AI-coding task reasoning chains: hypotheses, decisions, rejections, evidence. Renders compact resume packs so an agent can pick up a 2-week-old task with full context.",
"author": {
"name": "Mher Shahinyan"
Expand Down
Loading