diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 36e4af8..39f4730 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,14 +6,14 @@ }, "metadata": { "description": "Task Journal — append-only reasoning chain memory for AI-coding tasks", - "version": "0.14.0" + "version": "0.14.1" }, "plugins": [ { "name": "task-journal", "source": "./plugin", "description": "Append-only journal of AI-coding task reasoning chains. Captures hypotheses, decisions, rejections, evidence — renders compact resume packs so an agent can pick up a 2-week-old task with full context.", - "version": "0.14.0", + "version": "0.14.1", "author": { "name": "Digital-Threads" }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b016f0..9df0c04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.14.1] - 2026-06-12 + +### Added (reliability — no model, no cost) +- **Hardened MCP server instructions.** `task-journal-mcp` delivers a sharper, + non-negotiable "you are the recorder" ritual to the agent every session + (open/resume a task → log decisions/rejections/findings at the moment → + self-check before finishing → close with an outcome). Strongest always-on + lever for "every session records" — it rides the MCP connection, so it works + regardless of hooks. +- **`task-journal nudge`** — a tiny read-only hook that prints a UserPromptSubmit + `additionalContext` reminder to keep recording via the MCP tools. **No model, + never spawns `claude -p`, zero cost.** `install-hooks` now wires it on + `UserPromptSubmit` by **default** (alongside the SessionStart resume) so the + reminder stays fresh deep into a session. +- `install-hooks --uninstall` now also removes the `nudge` hook. + +Default install stays model-free: SessionStart resume + UserPromptSubmit nudge, +both no-`claude -p`. The per-message classifier remains opt-in via +`--auto-capture`. + ## [0.14.0] - 2026-06-12 ### Changed (breaking default) diff --git a/Cargo.lock b/Cargo.lock index b6ebd4c..2128ee5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2166,7 +2166,7 @@ dependencies = [ [[package]] name = "task-journal-cli" -version = "0.14.0" +version = "0.14.1" dependencies = [ "anyhow", "assert_cmd", @@ -2189,7 +2189,7 @@ dependencies = [ [[package]] name = "task-journal-core" -version = "0.14.0" +version = "0.14.1" dependencies = [ "anyhow", "chrono", @@ -2213,7 +2213,7 @@ dependencies = [ [[package]] name = "task-journal-mcp" -version = "0.14.0" +version = "0.14.1" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 1addbc4..b9b0cd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = [ ] [workspace.package] -version = "0.14.0" +version = "0.14.1" edition = "2021" rust-version = "1.88" license = "MIT" diff --git a/crates/tj-cli/Cargo.toml b/crates/tj-cli/Cargo.toml index 4bdd764..23fecd8 100644 --- a/crates/tj-cli/Cargo.toml +++ b/crates/tj-cli/Cargo.toml @@ -16,7 +16,7 @@ name = "task-journal" path = "src/main.rs" [dependencies] -tj-core = { package = "task-journal-core", version = "0.14.0", path = "../tj-core" } +tj-core = { package = "task-journal-core", version = "0.14.1", path = "../tj-core" } anyhow = { workspace = true } clap = { workspace = true } tracing = { workspace = true } diff --git a/crates/tj-cli/src/main.rs b/crates/tj-cli/src/main.rs index 611250f..9f87668 100644 --- a/crates/tj-cli/src/main.rs +++ b/crates/tj-cli/src/main.rs @@ -895,6 +895,12 @@ enum Commands { /// Hidden from --help; not a human command. #[command(hide = true)] Statusline, + /// Read-only reminder hook (no model, never spawns `claude -p`). Emits a + /// UserPromptSubmit additionalContext line nudging the agent to record + /// reasoning via the MCP tools as it goes. Wired by `install-hooks` by + /// default. Hidden from --help; not a human command. + #[command(hide = true)] + Nudge, /// Cross-task search for `rejection` events matching a topic. Helpful /// when the agent is about to repeat a path that was already turned /// down — query the topic, see the prior rejection. @@ -1454,7 +1460,10 @@ fn main() -> Result<()> { inner.retain(|h| { h.get("command") .and_then(|c| c.as_str()) - .map(|c| !c.contains("task-journal ingest-hook")) + .map(|c| { + !(c.contains("task-journal ingest-hook") + || c.contains("task-journal nudge")) + }) .unwrap_or(true) }); } @@ -1515,19 +1524,30 @@ fn main() -> Result<()> { format!("task-journal ingest-hook --backend={backend} || true") }; let cmd = cmd_string.as_str(); - // v0.14.0 — self-tagging-first. By DEFAULT only the cheap, - // read-only SessionStart hook is wired: ingest-hook short-circuits - // on that kind, injects the resume pack + a nudge, and spawns no - // model. The per-message hooks (UserPromptSubmit/PostToolUse/Stop) - // and PreCompact are what spawn the classifier (`claude -p`); they - // are opt-in via `--auto-capture`. Primary capture is the agent - // self-tagging through the MCP tools. + let nudge_cmd = "task-journal nudge || true"; + // v0.14.x — self-tagging-first. The DEFAULT wires only no-model + // hooks: SessionStart → ingest-hook short-circuits to inject the + // read-only resume pack (no classifier); UserPromptSubmit → `nudge` + // prints a reminder to keep recording (no model, no spawn). The + // per-message classifier (`claude -p`) is opt-in via + // `--auto-capture`, which appends `ingest-hook` to the message + // events. Primary capture is the agent self-tagging via the MCP + // tools. let mut entries = serde_json::json!({ - "SessionStart": [{ "matcher": "", "hooks": [{ "type": "command", "command": cmd }] }], + "SessionStart": [{ "matcher": "", "hooks": [{ "type": "command", "command": cmd }] }], + "UserPromptSubmit": [{ "matcher": "", "hooks": [{ "type": "command", "command": nudge_cmd }] }], }); if auto_capture { let obj = entries.as_object_mut().expect("entries is an object"); - for ev in ["UserPromptSubmit", "PostToolUse", "Stop", "PreCompact"] { + // UserPromptSubmit keeps the nudge AND gains the classifier. + obj.insert( + "UserPromptSubmit".into(), + serde_json::json!([{ "matcher": "", "hooks": [ + { "type": "command", "command": nudge_cmd }, + { "type": "command", "command": cmd }, + ]}]), + ); + for ev in ["PostToolUse", "Stop", "PreCompact"] { obj.insert( ev.to_string(), serde_json::json!([{ "matcher": "", "hooks": [{ "type": "command", "command": cmd }] }]), @@ -2903,6 +2923,18 @@ fn main() -> Result<()> { // empty than to look broken. print!("{}", run_statusline().unwrap_or_default()); } + Commands::Nudge => { + // No model, no spawn, never touches the classifier — just prints a + // UserPromptSubmit additionalContext reminder so the agent keeps + // recording via the MCP tools deep into a session. + let env = serde_json::json!({ + "hookSpecificOutput": { + "hookEventName": "UserPromptSubmit", + "additionalContext": "📓 task-journal — record as you go: the moment you commit to a decision, rule an approach out, or verify a fact, call event_add (open or resume a task first). Don't batch it to the end. This memory only works if you log it now." + } + }); + print!("{env}"); + } Commands::Rejected { topic, all_projects, diff --git a/crates/tj-cli/tests/cli.rs b/crates/tj-cli/tests/cli.rs index aa64c9b..7670546 100644 --- a/crates/tj-cli/tests/cli.rs +++ b/crates/tj-cli/tests/cli.rs @@ -836,19 +836,26 @@ fn install_hooks_writes_to_settings_json() { let settings_path = dir.path().join(".claude").join("settings.json"); assert!(settings_path.exists()); let content = std::fs::read_to_string(&settings_path).unwrap(); - assert!(content.contains("task-journal ingest-hook")); + assert!(content.contains("task-journal ingest-hook")); // SessionStart resume assert!( content.contains("SessionStart"), "install-hooks must wire SessionStart so resume-pack injection works" ); - // v0.14.0 — self-tagging-first: the default install does NOT wire the - // per-message classifier hooks (those spawn `claude -p`); they are opt-in - // via `--auto-capture`. + // v0.14.x — self-tagging-first: the default wires the no-model UserPromptSubmit + // nudge, but NOT the per-message classifier hooks (those spawn `claude -p`); + // the classifier is opt-in via `--auto-capture`. assert!( - !content.contains("UserPromptSubmit"), - "default install must not wire the per-message classifier hooks" + content.contains("task-journal nudge"), + "default must wire the no-model UserPromptSubmit nudge" + ); + assert!( + !content.contains("PostToolUse"), + "default must not wire the per-message classifier hooks" + ); + assert!( + !content.contains("\"Stop\""), + "default must not wire the classifier Stop hook" ); - assert!(!content.contains("PostToolUse")); } #[test] diff --git a/crates/tj-mcp/Cargo.toml b/crates/tj-mcp/Cargo.toml index b2cb97e..c789aea 100644 --- a/crates/tj-mcp/Cargo.toml +++ b/crates/tj-mcp/Cargo.toml @@ -16,7 +16,7 @@ name = "task-journal-mcp" path = "src/main.rs" [dependencies] -tj-core = { package = "task-journal-core", version = "0.14.0", path = "../tj-core" } +tj-core = { package = "task-journal-core", version = "0.14.1", path = "../tj-core" } anyhow = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/crates/tj-mcp/src/main.rs b/crates/tj-mcp/src/main.rs index 55223dc..9504b82 100644 --- a/crates/tj-mcp/src/main.rs +++ b/crates/tj-mcp/src/main.rs @@ -124,33 +124,36 @@ fn cached_open(state_path: &Path) -> anyhow::Result>> { /// MCP instructions delivered to every Claude Code session where this plugin is installed. /// This is the primary mechanism for self-contained plugin behavior — no manual CLAUDE.md edits needed. -const MCP_INSTRUCTIONS: &str = r#"Task Journal — reasoning chain memory for AI coding sessions. - -MANDATORY WORKFLOW — follow for EVERY coding session: - -1. SESSION START → task_search for recent open tasks → task_pack to resume, OR task_create for new work -2. EVERY significant discovery → event_add(event_type="finding") -3. EVERY decision made → event_add(event_type="decision") -4. EVERY rejected approach → event_add(event_type="rejection") -5. EVERY hypothesis formed → event_add(event_type="hypothesis") -6. TEST RESULTS → event_add(event_type="evidence") -7. WRONG hypothesis corrected → event_add(event_type="correction", corrects=) -8. TASK DONE → task_close with reason and outcome - -EVENT TYPE GUIDE — choose correctly: -• hypothesis = "I think" / "maybe" / "could be" → UNVERIFIED theory -• finding = "I see" / "the code shows" / "confirmed" → VERIFIED by reading code/logs -• evidence = ran a test/experiment that PROVES something -• decision = committed choice ("We'll use X because Y") -• rejection = explicitly rejected approach ("Tried X but won't work because Y") -• constraint = external limitation discovered ("API rate limit is 100/min") -• correction = corrects earlier event (set corrects field) - -KEY RULES: -• One task = one logical objective. Don't create a new task every turn. -• Always close tasks when done. Don't leave them open. -• Log rejections — wrong paths prevent repeated mistakes. -• Append-only — never edit events, write corrections instead. +const MCP_INSTRUCTIONS: &str = r#"Task Journal — reasoning-chain memory. NON-NEGOTIABLE: you are the recorder. +The code shows WHAT changed; only you can record WHY. If you don't log it the +moment it happens, it is lost. This memory only works if you actually call the +tools — so call them, every session, without being asked. + +THE RITUAL — do this on EVERY coding session, not optional: + +1. START. task_search(status="open") for related work → task_pack to resume it; + if nothing fits, task_create(title, goal=). Hold the returned task_id for the whole session. +2. AT THE MOMENT you commit to an approach → event_add(event_type="decision", + ...) and pass `alternatives` (the options you weighed). Right then — not at + the end, or you will forget. +3. AT THE MOMENT you rule an approach out → event_add(event_type="rejection"). +4. When you verify a fact from code/logs → event_add(event_type="finding"). + When a test/benchmark proves something → event_add(event_type="evidence"). +5. SELF-CHECK before you finish (or before the context compacts): "did I log + every decision, rejection, and key finding from this session?" If not, log + them NOW. +6. DONE → task_close(reason, outcome, outcome_tag). + +Record in the user's language, terse and specific (file:line, ids, names). One +task = one objective — don't spawn a new task per turn; events accumulate under +the held task_id. Append-only: never edit — correct a mistake with a +`correction` event (set `corrects`). + +event_type: hypothesis (unverified "maybe") | finding (verified from code/logs) +| evidence (a test proved it) | decision (committed choice) | rejection (ruled +out) | constraint (external limit) | correction (fixes an earlier event). +`alternatives` is decision-only. "#; #[derive(Clone, Default)] diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index bb2d5e3..53d629c 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "task-journal", - "version": "0.14.0", + "version": "0.14.1", "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"