commit cabaf6196174545dccb225d212bd9f4255134410 Author: Zhongwei Li Date: Sun Nov 30 08:54:29 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..1b234c1 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,14 @@ +{ + "name": "jjagent", + "description": "Track Claude Code sessions as jujutsu changes", + "version": "0.4.2", + "author": { + "name": "schpet" + }, + "commands": [ + "./commands" + ], + "hooks": [ + "./hooks" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..954bf89 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# jjagent + +Track Claude Code sessions as jujutsu changes diff --git a/commands/describe.md b/commands/describe.md new file mode 100644 index 0000000..637f87e --- /dev/null +++ b/commands/describe.md @@ -0,0 +1,82 @@ +--- +description: Generate a commit description for the current session's jj change +model: claude-haiku-4-5 +allowed-tools: Bash(jjagent:*), Bash(jj:*) +--- + +# jj-describe + +Generate a commit description for a Claude session's jj change + +## instructions + +You must follow these steps to create a proper commit message: + +1. **Determine session ID:** + - Use the session ID from the system reminder (current session) + - Store the session ID for use in subsequent steps + +2. **Check if a change exists for the session:** + - Run: `jjagent change-id ` to check if a change exists + - **IMPORTANT:** If the command fails or returns an error, immediately stop and inform the user: + "No jj change exists for this session yet. There's nothing to describe." + - Do NOT proceed to the next steps if no change ID is found + +3. **Gather context:** + - Run: `jj diff -r "$(jjagent change-id )"` to see ONLY the diff + - Review the diff to understand what was actually changed + - Review the conversation/context to understand why a change was made + - **Do NOT read the existing commit message** - it will be replaced entirely + +4. **Generate a NEW commit message:** + - **First line (subject):** + - 50 characters or less + - Capitalize the first letter + - Use imperative mood (e.g., "Add feature" not "Added feature" or "Adds feature") + - No period at the end + - Concise summary of the change, not just "jjagent: session " + - **Second line:** Blank + - **Body (if needed):** + - Wrap at 72 characters + - Provide more detailed explanatory text with technical details, but stay concise + - Explain what and why, not how the code works + - Use bullet points for multiple items if appropriate + - Blank lines separate paragraphs + +5. **Update the description:** + - Run: `jjagent describe -m "your commit message here"` + - **Do NOT include any trailers** (Claude-session-id, etc.) - they are preserved automatically + - Only include the subject line and body + - Use a heredoc or proper quoting to preserve formatting + - Note: This is the Rust CLI tool command, not the slash command + +6. **Show the final change** + - Run: `jj show "$(jjagent change-id ) -s` and show the user direct output formatted as a code block + +## Example commit message (what you pass to `jjagent describe`): + +``` +Add SessionStart and UserPromptSubmit hooks + +Implement hook handlers that inject the session ID into Claude's +context at the start of a session and re-inject it when it's been +lost from the recent transcript. + +- Add HookSpecificOutput structure for passing context to Claude +- Implement handle_session_start_hook to inject session ID +- Implement handle_user_prompt_submit_hook to re-inject when needed +- Add comprehensive tests for both hook handlers +``` + +Note: Do NOT include the `Claude-session-id` trailer - it's preserved automatically. + +## Important notes: + +- Do NOT use the generic "jjagent: session " format for the subject line +- DO write a concise summary in the subject, with technical details in the body +- DO analyze the actual changes and write a meaningful, specific subject +- DO use imperative mood ("Add", "Fix", "Refactor", not "Added", "Fixed", "Refactored") +- DO keep the subject line to 50 characters or less +- DO wrap body lines at 72 characters +- Do NOT include trailers - they are preserved automatically by `jjagent describe` +- Do NOT look at the existing commit message - generate a fresh one from the diff diff --git a/commands/insert-after.md b/commands/insert-after.md new file mode 100644 index 0000000..034f26a --- /dev/null +++ b/commands/insert-after.md @@ -0,0 +1,78 @@ +--- +description: Insert a new session change after a specific revision +argument-hint: [message] +model: claude-haiku-4-5 +allowed-tools: Bash(jjagent:*), Bash(jj:*) +--- + +# jjagent:insert-after + +Insert a new session change after a specific revision using jj's --insert-after flag + +## instructions + +You must follow these steps: + +1. **Parse arguments:** + - `$1` (required): The jj reference to insert after + - `$2 $3 $4...` (optional): Custom commit message for the session (all remaining arguments) + - If `$1` is empty, inform the user that a reference is required + +2. **Get the current session ID:** + - Extract it from the system reminder at the start of this conversation + - The format is: "The current session ID is " + +3. **Check if a session change already exists:** + - Run: `jjagent change-id ` + - If this succeeds and returns a change ID, the session already has a change + - If it fails, the session doesn't have a change yet + +4. **Validate that ref is an ancestor of @ (working copy):** + - Run: `jj log -r "$1..@" --no-graph -T "change_id.short()"` + - If this command fails or returns empty output, `$1` is not an ancestor of `@` + - If not an ancestor, inform the user: "Error: '$1' is not an ancestor of the working copy. Please choose a revision that comes before @ in the commit history." + - Stop execution if validation fails + +5. **Handle existing session change:** + - If a change already exists for this session, **STOP and ask the user for confirmation**: + "This session already has a change at . Would you like to rebase it to insert after '$1'?" + - **IMPORTANT: You MUST wait for explicit user confirmation before proceeding** + - Do NOT rebase without the user's explicit approval + - If the user confirms YES, then run: `jj rebase -r --insert-after "$1"` + - If the user says NO or declines, stop and explain that no changes were made + - After rebasing (if confirmed), show the result with: `jj log -r --no-graph` + +6. **Create new session change (if no existing change):** + - Build the commit message using the session-message command: + - If custom message arguments were provided (if `$2` is not empty): + - Combine all arguments from `$2` onwards into the message + - Run: `jjagent session-message "$2 $3 $4..."` (all remaining args) + - If no custom message (if `$2` is empty): + - Run: `jjagent session-message ` + - Create the change: `jj new --insert-after "$1" --no-edit -m "$(jjagent session-message [message if provided])"` + - Show the result with: `jj log -r @ --no-graph` + +7. **Inform the user:** + - Confirm that the session change has been created/rebased + - Explain that future edits will be tracked in this change + +## Example usage + +```bash +# Insert session after the parent of working copy +/jjagent:insert-after @- + +# Insert with custom message +/jjagent:insert-after @-- "Implement user authentication" + +# Insert after a specific change ID +/jjagent:insert-after qwerty123 +``` + +## Important notes + +- The `$1` (ref) must be an ancestor of `@` (working copy) +- If a session change already exists, offer to rebase it +- Always validate the reference before creating/rebasing changes +- Use proper quoting when passing `$1` to shell commands +- When passing custom messages to `jjagent session-message`, combine all arguments from `$2` onwards diff --git a/commands/into.md b/commands/into.md new file mode 100644 index 0000000..2967245 --- /dev/null +++ b/commands/into.md @@ -0,0 +1,40 @@ +--- +description: Choose the change where this session will be squashed into +model: claude-haiku-4-5 +allowed-tools: Bash(jjagent into:*) +argument-hint: +--- + +# jja-into + +Choose the change where this session will be squashed into + +## instructions + +You must follow these steps: + +1. **Get the current session ID:** + - Extract it from the system reminder at the start of this conversation + - The format is: "The current session ID is " + +2. **Get the target revision:** + - The target revision is: $1 + - This should be a jj reference (change ID, revset, etc.) + +3. **Run the into command:** + - Execute: `jjagent into $1` + - This will move session tracking to the specified revision + +4. **Inform the user:** + - Tell them that session tracking has been moved to the specified revision + - Explain that future changes will now be tracked with this revision + +## Example + +If the session ID is `abcd1234-5678-90ab-cdef-1234567890ab` and the target ref is `@--`, run: + +```bash +jjagent into abcd1234-5678-90ab-cdef-1234567890ab @-- +``` + +Then inform the user that session tracking has been moved to the specified revision. diff --git a/commands/split.md b/commands/split.md new file mode 100644 index 0000000..ae10de8 --- /dev/null +++ b/commands/split.md @@ -0,0 +1,35 @@ +--- +description: Split the current session into a new jj change +model: claude-haiku-4-5 +allowed-tools: Bash(jjagent split:*) +--- + +# jja-split + +Split the current Claude session into a new jj change + +## instructions + +You must follow these steps: + +1. **Get the current session ID:** + - Extract it from the system reminder at the start of this conversation + - The format is: "The current session ID is " + +2. **Run the split command:** + - Execute: `jjagent split ` + - This will create a new change part for the session + +3. **Inform the user:** + - Tell them a new change part was created + - Explain that future changes will now go into this new part + +## Example + +If the session ID is `abcd1234-5678-90ab-cdef-1234567890ab`, run: + +```bash +jjagent split abcd1234-5678-90ab-cdef-1234567890ab +``` + +Then inform the user that a new change part has been created for the session. diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..e869a43 --- /dev/null +++ b/hooks/hooks.json @@ -0,0 +1,47 @@ +{ + "description": "jjagent hooks for tracking Claude Code sessions as jj changes", + "hooks": { + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "jjagent claude hooks UserPromptSubmit" + } + ] + } + ], + "PreToolUse": [ + { + "matcher": "Edit|MultiEdit|Write", + "hooks": [ + { + "type": "command", + "command": "jjagent claude hooks PreToolUse" + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "Edit|MultiEdit|Write", + "hooks": [ + { + "type": "command", + "command": "jjagent claude hooks PostToolUse" + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "jjagent claude hooks Stop" + } + ] + } + ] + } +} diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..78412df --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,61 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:schpet/jjagent:", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "eab9eab513e7a486bbb5e6678625740be0f47f4e", + "treeHash": "e94079199f40709b79ca7a44465ddc3b44cf5d5d32ad0dbc396ad2991179db48", + "generatedAt": "2025-11-28T10:28:09.697913Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "jjagent", + "description": "Track Claude Code sessions as jujutsu changes", + "version": "0.4.2" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "bab1a5ace93d2421a0c6a474239dcf935f3791b41fec57c151eac519b9a4429d" + }, + { + "path": "hooks/hooks.json", + "sha256": "24a01da41be821ef7b8b83c748017357081f3b9908210a5c47b29eac75b02611" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "8690748d936bb1624ff4f4e682f92e4e2c95d617c19fddbd6c0d0975f38fe856" + }, + { + "path": "commands/split.md", + "sha256": "708ec92f4bbc5b685adc081f78741a0b7c8eaad974667d83661f38cbd0c3adc2" + }, + { + "path": "commands/into.md", + "sha256": "02e415922c4578c8f6df46725df1fd1758a1b27dca188814e0d6cd2e16a18843" + }, + { + "path": "commands/describe.md", + "sha256": "709ae1a049723960d37b7724bfe3d2dfdd91a0a328baed2b67f8db78a13d988f" + }, + { + "path": "commands/insert-after.md", + "sha256": "11c2e193a3a6abe5c3dd4aaa3fde06d198a237b6c193eb668058c069e0b15897" + } + ], + "dirSha256": "e94079199f40709b79ca7a44465ddc3b44cf5d5d32ad0dbc396ad2991179db48" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file