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
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.5.0] - 2026-03-20

### Added
- **Stop-hook haiku summarization** — AI context summaries are now derived from
Claude's actual response (`last_assistant_message`) via the Stop hook, delivering
~100% of the time (up from 0.5% with the previous inline strategy). Summaries
reflect what Claude *did*, not just what was asked.
- **Two-phase context** — first-5-words appears immediately on prompt submission;
a refined haiku summary overwrites after the turn completes.
- **Git commit message capture** — commit subject line automatically written to
context file on `💾 Committed`, providing a human-written task summary at zero cost.
- **Context sanitization** — shell prompt prefixes (`(venv) user@host %`) are
stripped from first-5-words when users paste terminal output.

### Fixed
- Clear `CLAUDECODE` env var in haiku subprocess to prevent nested claude call
failures.

### Removed
- **Inline AI context strategy** — `--ai-context-strategy` flag,
`--append-system-prompt` injection, `CCP_TASK_SUMMARY` markers, and
`inline_captured` sentinel files are all removed. `--ai-context` is now a
simple boolean toggle.

## [1.2.0] - 2026-03-05

### Added
Expand Down Expand Up @@ -98,6 +122,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **SECURITY.md** — responsible disclosure policy.
- **Issue templates** (bug report + feature request) and PR template.

[1.5.0]: https://github.com/brianruggieri/claude-code-pulse/compare/v1.2.0...v1.5.0
[1.2.0]: https://github.com/brianruggieri/claude-code-pulse/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/brianruggieri/claude-code-pulse/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/brianruggieri/claude-code-pulse/releases/tag/v1.0.0
38 changes: 12 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ Each pane title is independent — project, branch, task, and live status update
- **Priority-based display**. Errors always show first. Completions (tests passed, committed) immediately override whatever else is showing.
- **Hook-based**. Uses Claude Code's own hook events for status. No output parsing, no regex, just structured JSON.
- **Session tracking**. Save and re-open sessions by title with `--goto`.
- **AI task summaries**. `--ai-context` refines your prompt into a clean 3-5 word label shown in the title. Two strategies: `haiku` (separate claude-haiku call, fully invisible) or `inline` (zero extra API calls, uses the already-outgoing session). Both are opt-in.
- **AI task summaries**. `--ai-context` refines each turn into a clean 3-5 word label shown in the title. Uses claude-haiku to summarize after each turn completes. Opt-in only.
- **Git commit context**. When Claude commits, the commit subject line automatically appears as the task context — no flag needed.
- **Context sanitization**. Shell prompt prefixes (`$`, `%`, `user@host`, etc.) are automatically stripped from pasted terminal content before building the title.
- **Tested on iTerm2, Terminal.app, and tmux**. Detection logic for WezTerm, Ghostty, and Kitty is included but unverified.
- **Full Claude passthrough**. Every Claude Code flag works exactly as expected.

Expand Down Expand Up @@ -75,14 +77,11 @@ ccp --permission-mode bypassPermissions
# AI-summarized task labels in the title (opt-in, uses your subscription)
ccp --ai-context "Fix the auth bug"

# Inline strategy — zero extra API calls (one echo visible per session)
ccp --ai-context --ai-context-strategy inline "Fix the auth bug"

# Debug mode — structured JSONL logging + live Pulse Monitor (iTerm2: auto-opens in split pane)
ccp --debug-ccp "Fix the auth bug"
```

> See [AI Task Summaries](#ai-task-summaries) for the `--ai-context` flag and strategy options.
> See [AI Task Summaries](#ai-task-summaries) for the `--ai-context` flag.

## Installation

Expand Down Expand Up @@ -232,30 +231,19 @@ Precedence: `--status-profile` flag > `CCP_STATUS_PROFILE` env var > `quiet` def

## AI Task Summaries

With `--ai-context`, ccp distills your prompt into a clean 3-5 word label shown in the title. Without it, the first words of your prompt show as-is.

Two strategies are available:
With `--ai-context`, ccp generates a clean 3-5 word label for each conversation turn. Without it, the first words of your prompt show as-is.

| | `haiku` (default) | `inline` |
|---|---|---|
| Extra API calls | 1 per prompt | 0 |
| Visible to user | No | One `echo` per session |
| Summary context | Prompt text only | Full codebase + history |
| Reliability | High | Model-dependent |
How it works: when Claude finishes responding, the Stop hook sends a truncated excerpt of the response to claude-haiku for a 3-5 word summary. The summary overwrites the first-5-words placeholder in the title.

```bash
# Haiku strategy (default) — invisible, uses your subscription
# Enable AI summaries (opt-in, uses your subscription)
ccp --ai-context "PR #89 - Fix auth"

# Inline strategy — zero extra API calls, one echo visible per session
ccp --ai-context --ai-context-strategy inline "PR #89 - Fix auth"

# Always on, via environment variables
# Always on, via environment variable
export CCP_ENABLE_AI_CONTEXT=true
export CCP_AI_CONTEXT_STRATEGY=inline # optional, default is haiku
```

See [docs/ai-context.md](docs/ai-context.md) for the full trade-off analysis.
See [docs/ai-context.md](docs/ai-context.md) for the full details.

## Claude Flag Passthrough

Expand Down Expand Up @@ -284,7 +272,7 @@ Any unrecognized `-*` flag is also forwarded, so new Claude flags just work.
### Full forwarded flag list

**No value:**
`-c` / `--continue`, `--dangerously-skip-permissions`, `--allow-dangerously-skip-permissions`, `--verbose`, `--ide`, `--no-ide`, `--fork-session`, `--chrome`, `--no-chrome`, `--no-session-persistence`, `--include-partial-messages`, `--replay-user-messages`, `--strict-mcp-config`, `--mcp-debug`
`-c` / `--continue`, `-p` / `--print`, `--dangerously-skip-permissions`, `--allow-dangerously-skip-permissions`, `--verbose`, `--ide`, `--no-ide`, `--fork-session`, `--chrome`, `--no-chrome`, `--no-session-persistence`, `--include-partial-messages`, `--replay-user-messages`, `--strict-mcp-config`, `--mcp-debug`

**Required value:**
`--model`, `--permission-mode`, `--effort`, `--system-prompt`, `--append-system-prompt`, `--debug-file`, `--session-id`, `--output-format`, `--input-format`, `--settings`, `--setting-sources`, `--fallback-model`, `--max-budget-usd`, `--json-schema`, `--agent`
Expand Down Expand Up @@ -430,8 +418,7 @@ my-app (bug/login-crash) | login bug | ⬆️ Pushing
| `--auto-title` | Auto-detect title from git branch (this is the default) |
| `--no-dynamic` | Static title only, no live updates |
| `--status-profile quiet\|verbose` | Which events to surface (default: `quiet`) |
| `--ai-context` | Summarize prompts into a 3-5 word title label (opt-in) |
| `--ai-context-strategy haiku\|inline` | Summary strategy: `haiku` (default, invisible) or `inline` (zero extra API calls) |
| `--ai-context` | Summarize each turn into a 3-5 word title label (opt-in, uses your subscription) |
| `--debug-ccp` | Structured JSONL debug logging to `~/.config/claude-code-pulse/logs/`; auto-opens Pulse Monitor in iTerm2 |
| `--goto TITLE` | Re-open a previous session by title |
| `--list`, `-l` | List active ccp sessions |
Expand All @@ -445,8 +432,7 @@ my-app (bug/login-crash) | login bug | ⬆️ Pushing
| Variable | Default | Description |
|----------|---------|-------------|
| `CCP_STATUS_PROFILE` | `quiet` | Default status profile |
| `CCP_ENABLE_AI_CONTEXT` | `false` | Always-on AI prompt summarization |
| `CCP_AI_CONTEXT_STRATEGY` | `haiku` | Summary strategy: `haiku` or `inline` |
| `CCP_ENABLE_AI_CONTEXT` | `false` | Always-on AI context summarization |
| `CCP_DEBUG` | `false` | Always-on structured JSONL debug logging |

## Documentation
Expand Down
70 changes: 7 additions & 63 deletions bin/ccp
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,11 @@ ${BLUE}ccp Options:${NC}
--auto-title Auto-detect title from git branch (default)
--no-dynamic Disable dynamic updates (static title)
--status-profile MODE Status surface: quiet (default) or verbose
--ai-context Summarize prompts in title via claude-haiku (opt-in,
uses your Claude subscription — see docs/ai-context.md)
--ai-context Summarize tasks in title via claude-haiku (opt-in,
uses your Claude subscription)
Privacy: prompt text is sent to Anthropic to generate
the summary. Do not enable if prompts contain secrets.
Also: export CCP_ENABLE_AI_CONTEXT=true for always-on
--ai-context-strategy MODE
How AI context summaries are generated:
haiku — separate claude-haiku call (default)
inline — system prompt injection, zero extra API calls
Also: export CCP_AI_CONTEXT_STRATEGY=inline for always-on
--debug-ccp Enable structured JSONL debug logging to
~/.config/claude-code-pulse/logs/
In iTerm2, auto-opens Pulse Monitor in a split pane.
Expand Down Expand Up @@ -166,7 +161,6 @@ main() {
local enable_dynamic=true
local status_profile_cli=""
local enable_ai_context="${CCP_ENABLE_AI_CONTEXT:-false}"
local ai_context_strategy="${CCP_AI_CONTEXT_STRATEGY:-haiku}"
local enable_debug_ccp="${CCP_DEBUG:-false}"
local claude_args=()
local restored_title=""
Expand Down Expand Up @@ -205,15 +199,6 @@ main() {
enable_ai_context=true
shift
;;
--ai-context-strategy)
shift
if [[ $# -lt 1 ]]; then
log_error "Usage: ${SCRIPT_NAME} --ai-context-strategy haiku|inline"
exit 1
fi
ai_context_strategy="$1"
shift
;;
--status-profile)
shift
if [[ $# -lt 1 ]]; then
Expand Down Expand Up @@ -397,18 +382,6 @@ main() {
esac
export CCP_STATUS_PROFILE="${status_profile}"

# Resolve AI context strategy with precedence:
# CLI flag > environment variable > default haiku.
case "${ai_context_strategy}" in
haiku|inline)
;;
*)
log_error "Invalid AI context strategy '${ai_context_strategy}'. Use: haiku or inline."
exit 1
;;
esac
export CCP_AI_CONTEXT_STRATEGY="${ai_context_strategy}"

# Setup debug logging if enabled
if [[ "${enable_debug_ccp}" = true ]]; then
local log_dir="${STATE_DIR}/logs"
Expand Down Expand Up @@ -439,8 +412,7 @@ main() {
--arg dir "${directory}" \
--arg profile "${status_profile}" \
--arg ai_ctx "${enable_ai_context}" \
--arg ai_strat "${ai_context_strategy}" \
'{ts:$ts, src:"session", pid:$pid, event:"session_start", title:$title, dir:$dir, profile:$profile, ai_context:$ai_ctx, ai_strategy:$ai_strat}' \
'{ts:$ts, src:"session", pid:$pid, event:"session_start", title:$title, dir:$dir, profile:$profile, ai_context:$ai_ctx}' \
>> "${CCP_DEBUG_LOG}" 2>/dev/null || true
fi

Expand All @@ -466,7 +438,7 @@ main() {
cleanup_monitor
teardown_ccp_hooks "${ccp_settings_file}"
rm -f "${CCP_STATUS_FILE:-}" "${CCP_CONTEXT_FILE:-}" "${CCP_BRANCH_FILE:-}" \
"${CCP_AGENTS_FILE:-}" "${STATE_DIR}/inline_captured.$$"
"${CCP_AGENTS_FILE:-}"
}
trap '_ccp_cleanup' EXIT

Expand All @@ -484,33 +456,14 @@ main() {
log_info "Dynamic titles: ${GREEN}enabled${NC}"
log_info "Status profile: ${CCP_STATUS_PROFILE}"
if [[ "${enable_ai_context}" = true ]]; then
if [[ "${ai_context_strategy}" == "inline" ]]; then
log_info "AI context: ${GREEN}enabled${NC} (inline — summary via system prompt, no extra API calls)"
else
log_info "AI context: ${GREEN}enabled${NC} (prompts summarized via claude-haiku — uses your subscription)"
fi
log_info "AI context: ${GREEN}enabled${NC} (task summaries via claude-haiku — uses your subscription)"
fi
if [[ "${enable_debug_ccp}" = true ]]; then
log_info "Debug log: ${YELLOW}${CCP_DEBUG_LOG}${NC}"
fi
fi
if [[ ${#claude_args[@]} -gt 0 ]]; then
local display_args=()
local skip_next=false
for arg in "${claude_args[@]}"; do
if [[ "${skip_next}" == true ]]; then
skip_next=false
continue
fi
if [[ "${arg}" == "--append-system-prompt" ]]; then
skip_next=true
continue
fi
display_args+=("${arg}")
done
if [[ ${#display_args[@]} -gt 0 ]]; then
log_info "Claude args: ${display_args[*]}"
fi
log_info "Claude args: ${claude_args[*]}"
fi
echo ""

Expand Down Expand Up @@ -554,21 +507,12 @@ main() {
| awk '{print $1}') || _welcome_name=""
[[ -z "${_welcome_name}" ]] && _welcome_name="${USER:-}"
if [[ -n "${_welcome_name}" ]]; then
printf '%s' "👋 Welcome back, ${_welcome_name}" > "${CCP_STATUS_FILE}"
printf '%s' "👋 Welcome back, ${_welcome_name}!" > "${CCP_STATUS_FILE}"
fi

# Inject hooks into the project's .claude/settings.local.json
ccp_settings_file=$(setup_ccp_hooks "${directory}" "${CCP_HOOK_RUNNER}")

# Inline AI context: inject a system prompt instruction asking Claude
# to echo a structured task summary as its first action. The summary
# is captured by the PostToolUse hook (see hook_runner.sh post-tool).
if [[ "${enable_ai_context}" = true && "${ai_context_strategy}" == "inline" ]]; then
local _ccp_inline_prompt
_ccp_inline_prompt="[CCP Terminal Title] As your very first action, run a single bash command: echo 'CCP_TASK_SUMMARY_$$:' followed by a 3-5 word title-case summary of the user's task. Example: echo 'CCP_TASK_SUMMARY_$$:Fix JWT Validation Bug'. Do this once, then proceed with your work normally."
claude_args+=("--append-system-prompt" "${_ccp_inline_prompt}")
fi

# Start background title updater, then run Claude directly.
title_updater "${title}"

Expand Down
25 changes: 3 additions & 22 deletions bin/ccp-watch
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,7 @@ fi
last_status=""
last_status_ts=""
last_event_ts=""
pending_summaries=0
delivered_summaries=0
inline_miss_warned=false
total_errors=0
total_events=0

Expand Down Expand Up @@ -284,26 +282,11 @@ tail -f "${log}" | while IFS= read -r line; do
fi
fi

# ── Inline summary tracking ────────────────────────────────────────
# ── AI summary tracking ────────────────────────────────────────────
case "${event}" in
ai_context_pending)
pending_summaries=$((pending_summaries + 1))
;;
ai_summary)
delivered_summaries=$((delivered_summaries + 1))
alert "OK" "Summary delivered: '${summary}' (${delivered_summaries}/${pending_summaries})"
;;
inline_marker_miss)
# Only warn once per session — inline strategy is best-effort,
# Claude may skip the summary echo entirely.
if [[ "${inline_miss_warned}" = false && ${pending_summaries} -gt ${delivered_summaries} ]]; then
miss_count=$(jq -n "[inputs | select(.event == \"inline_marker_miss\")] | length" "${log}" 2>/dev/null) || miss_count=0
if [[ ${miss_count} -ge 20 ]]; then
cmd=$(printf '%s' "${line}" | jq -r '.command // ""' 2>/dev/null | head -c 40) || cmd=""
alert "INFO" "Inline summary not delivered after ${miss_count} Bash calls (best-effort, using first-5-words fallback)"
inline_miss_warned=true
fi
fi
alert "OK" "Summary delivered: '${summary}' (${delivered_summaries} total)"
;;
esac

Expand Down Expand Up @@ -339,11 +322,9 @@ tail -f "${log}" | while IFS= read -r line; do
echo -e " ${MAGENTA}${BOLD}Session Summary${NC}"
echo -e " Events: ${total_events}"
echo -e " Errors: ${total_errors}"
echo -e " Summaries: ${delivered_summaries}/${pending_summaries} delivered"
echo -e " Summaries: ${delivered_summaries} delivered"
if [[ ${total_errors} -gt 0 ]]; then
echo -e " ${RED}${BOLD}Issues detected — review log with: ccp-watch --list${NC}"
elif [[ ${pending_summaries} -gt ${delivered_summaries} ]]; then
echo -e " ${YELLOW}Dropped summaries detected${NC}"
else
echo -e " ${GREEN}Clean session.${NC}"
fi
Expand Down
Loading
Loading