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
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 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.14.0"
version = "0.14.1"
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 @@ -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 }
Expand Down
52 changes: 42 additions & 10 deletions crates/tj-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
});
}
Expand Down Expand Up @@ -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 }] }]),
Expand Down Expand Up @@ -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,
Expand Down
21 changes: 14 additions & 7 deletions crates/tj-cli/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion crates/tj-mcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
57 changes: 30 additions & 27 deletions crates/tj-mcp/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,33 +124,36 @@ fn cached_open(state_path: &Path) -> anyhow::Result<Arc<Mutex<Connection>>> {

/// 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=<event_id>)
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=<one sentence: what the user is
trying to accomplish>). 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)]
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.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"
Expand Down
Loading