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.13.1"
"version": "0.14.0"
},
"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.13.1",
"version": "0.14.0",
"author": {
"name": "Digital-Threads"
},
Expand Down
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.14.0] - 2026-06-12

### Changed (breaking default)
- **Self-tagging-first: realtime auto-capture is now OFF by default.** A fresh
`install-hooks` wires only the cheap, read-only SessionStart resume hook — no
per-message classifier, no `claude -p` spawned, nothing charged. The agent
records reasoning directly via the MCP tools (the bundled skill drives this);
that is the capture mechanism. This removes the whole class of failures the
per-chunk `claude -p` design caused: each classification booted a full Claude
Code instance (CLAUDE.md + every plugin + hooks + MCP + plugin-marketplace
`git pull`) just to label one line — a recursion fork bomb (fixed 0.13.1), a
`git pull origin HEAD` storm, a runaway pending backlog, and burned Agent SDK
credit.
- **Per-message auto-capture is opt-in** via `install-hooks --auto-capture`,
which wires the `UserPromptSubmit` / `PostToolUse` / `Stop` / `PreCompact`
ingest hooks (honoring `--backend`). The classifier backends
(`heuristic` / `agent-sdk` / `api` / `hybrid`) and the recursion guard remain
in the codebase, unchanged, for users who explicitly want them.
- `dream` offline backfill is unchanged — a manual, batched LLM pass you run on
demand.

## [0.13.1] - 2026-06-12

### Fixed
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.13.1"
version = "0.14.0"
edition = "2021"
rust-version = "1.88"
license = "MIT"
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ That's it. Restart Claude Code, start working, and the journal fills itself.
## How it works

- **Self-tagging is the primary path (recommended).** You — the agent in the live session — record reasoning directly via the five MCP tools: open a task with a `goal`, append a typed `decision` / `finding` / `rejection` / `evidence` event at the moment of commitment, and `task_close` with a written `outcome`. This is free (it rides the interactive session), language-agnostic, and higher-fidelity than any after-the-fact classifier. The bundled `task-journal` skill drives this automatically. See [MCP tools](#mcp-tools).
- **Auto-capture is a best-effort backstop.** Claude Code hooks also run every prompt, tool call, and reply through a two-stage classifier that lands typed events on its own. Stage 1 is a fast in-process heuristic — pattern-matches obvious EN+RU phrasing for zero cost. Stage 2 falls back to an LLM only when the heuristic is uncertain. Without an LLM backend it **degrades to heuristic-only**: it reliably catches keyword-obvious lines but misses real reasoning — especially non-English prose. Treat it as a safety net under your explicit self-tagging, not the main capture mechanism. Hook returns in <100 msboth stages run in a detached background worker, never blocking your session.
- **Auto-capture is an opt-in backstop — OFF by default (v0.14.0).** A fresh `install-hooks` wires only a cheap, read-only SessionStart resume hook: no per-message classifier runs, no `claude -p` is ever spawned, nothing is charged. Self-tagging is the capture mechanism. Opt in with `install-hooks --auto-capture` and Claude Code hooks also run every prompt, tool call, and reply through a two-stage classifier that lands typed events on its own: Stage 1 is a fast in-process heuristic (obvious EN+RU phrasing, zero cost); Stage 2 falls back to an LLM only when the heuristic is uncertain (and only if you pick `--backend agent-sdk` / `api`). Even opted in it is a safety net under your explicit self-tagging, not the main mechanism, and it misses real reasoningespecially non-English prose.
- **Artifact extraction.** Each event scans its text for commit hashes, PR URLs, file paths, issue IDs, and branch names. Aggregated artifacts are how Task Journal links related tasks: when you start a new task touching the same issue or file, the prior task is surfaced automatically.
- **Resume packs.** `task_pack` (MCP tool or CLI) renders a task into a compact Markdown briefing — Goal, Outcome, decisions, rejections, evidence, artifacts — that fits in a fresh agent's context window without dumping the raw event log.
- **Auto-capture boundaries.** Beyond per-event capture, two extra hooks mark *reasoning boundaries* automatically. On `PreCompact`, Task Journal reads the transcript JSONL tail (entries newer than the active task's last event) and enqueues anything the synchronous hooks missed before the compact — then drops a marker decision so the post-compact agent sees a clear cut. A `/rewind`-prefixed prompt appends a single correction event so pack readers see where the user rolled back. No mass-rejection of prior events — the boundary is a sentinel, not a rewrite.
Expand Down Expand Up @@ -166,7 +166,7 @@ task-journal pack tj-x9rz1f --mode full
| `doctor` | Self-check the install |
| `rebuild-state` | Rebuild SQLite from JSONL |
| `migrate-project` | Re-key data when a project moves on disk |
| `install-hooks [--scope user\|project] [--backend hybrid\|agent-sdk\|api\|heuristic]` | Wire Claude Code auto-capture hooks |
| `install-hooks [--scope user\|project] [--auto-capture] [--backend hybrid\|agent-sdk\|api\|heuristic]` | Wire Claude Code hooks. Default: only the read-only SessionStart resume hook. `--auto-capture` adds the per-message classifier (spawns `claude -p` unless `--backend heuristic`). |

## MCP tools

Expand Down Expand Up @@ -253,10 +253,15 @@ Wire the MCP server into Claude Code (`~/.claude/settings.json`):
}
```

Wire auto-capture hooks (one-shot):
Wire hooks (one-shot). Default installs only the read-only SessionStart resume
hook — no classifier, no `claude -p`:

```bash
task-journal install-hooks --scope user

# Opt in to per-message auto-capture (spawns `claude -p` per uncertain chunk
# unless you pin --backend heuristic):
task-journal install-hooks --scope user --auto-capture --backend heuristic
```

### Updating
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.13.1", path = "../tj-core" }
tj-core = { package = "task-journal-core", version = "0.14.0", path = "../tj-core" }
anyhow = { workspace = true }
clap = { workspace = true }
tracing = { workspace = true }
Expand Down
40 changes: 27 additions & 13 deletions crates/tj-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,14 @@ enum Commands {
/// ANTHROPIC_API_KEY (see `ingest-hook --help` for the credit note).
#[arg(long, default_value = "hybrid")]
backend: String,
/// v0.14.0: opt in to realtime auto-capture. Without it, install-hooks
/// wires ONLY the cheap, read-only SessionStart resume hook — no
/// per-message classifier, no `claude -p`, no cost. Primary capture is
/// the agent self-tagging via the MCP tools. With `--auto-capture` the
/// per-message + PreCompact ingest hooks are installed too (they spawn
/// the classifier, honoring `--backend`).
#[arg(long)]
auto_capture: bool,
},
/// Show local classifier and journal statistics.
Stats,
Expand Down Expand Up @@ -1392,6 +1400,7 @@ fn main() -> Result<()> {
uninstall,
backfill,
backend,
auto_capture,
} => {
let settings_path = match scope.as_str() {
"user" => {
Expand Down Expand Up @@ -1506,20 +1515,25 @@ fn main() -> Result<()> {
format!("task-journal ingest-hook --backend={backend} || true")
};
let cmd = cmd_string.as_str();
let entries = serde_json::json!({
"UserPromptSubmit": [{ "matcher": "", "hooks": [{ "type": "command", "command": cmd }] }],
"PostToolUse": [{ "matcher": "", "hooks": [{ "type": "command", "command": cmd }] }],
"Stop": [{ "matcher": "", "hooks": [{ "type": "command", "command": cmd }] }],
// SessionStart drives the auto resume-pack injection:
// ingest-hook short-circuits on this kind, queries open
// tasks for the current project, and emits the
// additionalContext envelope Claude Code expects.
"SessionStart": [{ "matcher": "", "hooks": [{ "type": "command", "command": cmd }] }],
// PreCompact: drop a marker decision event on the most-recent
// open task so the post-compact agent sees a clear boundary
// in the journal between pre- and post-compaction reasoning.
"PreCompact": [{ "matcher": "", "hooks": [{ "type": "command", "command": cmd }] }],
// 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 mut entries = serde_json::json!({
"SessionStart": [{ "matcher": "", "hooks": [{ "type": "command", "command": cmd }] }],
});
if auto_capture {
let obj = entries.as_object_mut().expect("entries is an object");
for ev in ["UserPromptSubmit", "PostToolUse", "Stop", "PreCompact"] {
obj.insert(
ev.to_string(),
serde_json::json!([{ "matcher": "", "hooks": [{ "type": "command", "command": cmd }] }]),
);
}
}
hooks_obj.insert("hooks".into(), entries);
}
std::fs::write(&settings_path, serde_json::to_string_pretty(&current)?)?;
Expand Down
39 changes: 34 additions & 5 deletions crates/tj-cli/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -836,13 +836,42 @@ 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("UserPromptSubmit"));
assert!(content.contains("PostToolUse"));
assert!(content.contains("task-journal ingest-hook"));
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`.
assert!(
!content.contains("UserPromptSubmit"),
"default install must not wire the per-message classifier hooks"
);
assert!(!content.contains("PostToolUse"));
}

#[test]
fn install_hooks_auto_capture_wires_all_events() {
let dir = assert_fs::TempDir::new().unwrap();
Command::cargo_bin("task-journal")
.unwrap()
.env("HOME", dir.path())
.args(["install-hooks", "--scope", "user", "--auto-capture"])
.assert()
.success();

let content =
std::fs::read_to_string(dir.path().join(".claude").join("settings.json")).unwrap();
for ev in [
"SessionStart",
"UserPromptSubmit",
"PostToolUse",
"Stop",
"PreCompact",
] {
assert!(content.contains(ev), "--auto-capture must wire {ev}");
}
}

#[test]
Expand Down Expand Up @@ -875,7 +904,7 @@ fn install_hooks_is_idempotent_and_uninstall_works() {
|| after_install.contains("\"theme\": \"dark\""),
"must preserve unrelated keys"
);
assert!(after_install.contains("UserPromptSubmit"));
assert!(after_install.contains("SessionStart"));

Command::cargo_bin("task-journal")
.unwrap()
Expand Down Expand Up @@ -2891,13 +2920,13 @@ fn install_hooks_wires_precompact_event() {
Command::cargo_bin("task-journal")
.unwrap()
.env("HOME", dir.path())
.args(["install-hooks", "--scope", "user"])
.args(["install-hooks", "--scope", "user", "--auto-capture"])
.assert()
.success();
let s = std::fs::read_to_string(dir.path().join(".claude/settings.json")).unwrap();
assert!(
s.contains("PreCompact"),
"settings.json must wire PreCompact: {s}"
"settings.json must wire PreCompact under --auto-capture: {s}"
);
}

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.13.1", path = "../tj-core" }
tj-core = { package = "task-journal-core", version = "0.14.0", path = "../tj-core" }
anyhow = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
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.13.1",
"version": "0.14.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