Initial commit
This commit is contained in:
18
.claude-plugin/plugin.json
Normal file
18
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "git",
|
||||
"description": "Git intelligence for Claude Code - session context, commit history, and smart commits with Conventional Commits",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Nathan Vale",
|
||||
"email": "hi@nathanvale.com"
|
||||
},
|
||||
"skills": [
|
||||
"./skills"
|
||||
],
|
||||
"commands": [
|
||||
"./commands"
|
||||
],
|
||||
"hooks": [
|
||||
"./hooks"
|
||||
]
|
||||
}
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# git
|
||||
|
||||
Git intelligence for Claude Code - session context, commit history, and smart commits with Conventional Commits
|
||||
54
commands/checkpoint.md
Normal file
54
commands/checkpoint.md
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
description: Create a quick WIP checkpoint commit to save your current work
|
||||
model: claude-haiku-4-5-20251001
|
||||
allowed-tools: Bash(git add:*), Bash(git commit:*), mcp__plugin_git_git-intelligence__get_diff_summary
|
||||
argument-hint: [description]
|
||||
---
|
||||
|
||||
# Quick Checkpoint Commit
|
||||
|
||||
Create a quick WIP checkpoint commit to save your current work.
|
||||
|
||||
## Instructions
|
||||
|
||||
Create a quick checkpoint commit to save work-in-progress. This is useful for:
|
||||
- Saving state before risky operations
|
||||
- Creating restore points during development
|
||||
- Quick saves when switching context
|
||||
|
||||
### Workflow
|
||||
|
||||
1. **Check current status** - Use `get_status` MCP tool
|
||||
- Quickly see staged, modified, and untracked files
|
||||
- Check for any files that shouldn't be committed (secrets, etc.)
|
||||
|
||||
2. **Stage all changes** (unless there are files with secrets):
|
||||
```bash
|
||||
git add -A
|
||||
```
|
||||
|
||||
3. **Create checkpoint commit**:
|
||||
```bash
|
||||
git commit -m "$(cat <<'EOF'
|
||||
chore(wip): checkpoint - <brief description>
|
||||
|
||||
Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
- Checkpoints can be squashed later with `git rebase -i`
|
||||
- The description should be brief (e.g., "before refactor", "auth working", "halfway done")
|
||||
- Skip files that shouldn't be committed (secrets, large binaries)
|
||||
|
||||
### Arguments
|
||||
|
||||
If the user provides a description after the command, use it:
|
||||
- `/git:checkpoint before api changes` -> `chore(wip): checkpoint - before api changes`
|
||||
- `/git:checkpoint` -> Ask for a brief description or use current context
|
||||
|
||||
Now create a checkpoint commit.
|
||||
122
commands/commit.md
Normal file
122
commands/commit.md
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
description: Create well-formatted commits using Conventional Commits specification
|
||||
model: claude-sonnet-4-5-20250929
|
||||
allowed-tools: Bash(git add:*), Bash(git commit:*), mcp__plugin_git_git-intelligence__get_recent_commits, mcp__plugin_git_git-intelligence__get_diff_summary
|
||||
---
|
||||
|
||||
# Smart Commit
|
||||
|
||||
Create well-formatted commits using Conventional Commits specification.
|
||||
|
||||
## Instructions
|
||||
|
||||
You are a git commit specialist. Create atomic, well-documented commits following these rules:
|
||||
|
||||
### Commit Format
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
```
|
||||
|
||||
### Types (REQUIRED - use exactly these)
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| feat | A new feature |
|
||||
| fix | A bug fix |
|
||||
| docs | Documentation changes |
|
||||
| style | Code style changes (formatting, etc) |
|
||||
| refactor | Code refactoring |
|
||||
| perf | Performance improvements |
|
||||
| test | Adding or updating tests |
|
||||
| build | Build system changes |
|
||||
| ci | CI/CD configuration changes |
|
||||
| chore | Maintenance tasks |
|
||||
| revert | Revert changes |
|
||||
|
||||
### Rules
|
||||
- Subject line max 100 characters
|
||||
- Use lowercase for type and scope
|
||||
- No period at end of subject
|
||||
- Scope should describe the area of change (e.g., auth, api, config)
|
||||
- Subject should be imperative ("add" not "added", "fix" not "fixed")
|
||||
|
||||
### Workflow
|
||||
|
||||
1. **Check status and diff** (use MCP tools for efficiency):
|
||||
- Use `get_status` tool to see all changes (staged, modified, untracked)
|
||||
- Use `get_diff_summary` tool to review what will be committed
|
||||
- Use `get_recent_commits` tool with `limit: 5` to see recent commit style
|
||||
- For detailed diff content, use Bash: `git diff` or `git diff --cached`
|
||||
|
||||
2. **Analyze the changes**:
|
||||
- Determine if changes should be one commit or split into multiple
|
||||
- Identify the primary type of change (feat, fix, refactor, etc.)
|
||||
- Identify the scope (which area of the codebase)
|
||||
|
||||
3. **Stage files** (if not already staged):
|
||||
- Use `git add <files>` for specific files
|
||||
- NEVER use `git add .` without reviewing what will be added
|
||||
- Skip files that contain secrets (.env, credentials, etc.)
|
||||
|
||||
4. **Create the commit**:
|
||||
```bash
|
||||
git commit -m "$(cat <<'EOF'
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
[optional body explaining what and why]
|
||||
|
||||
Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
5. **Handle pre-commit hook failures**:
|
||||
- If hooks modify files, stage the changes and amend: `git add . && git commit --amend --no-edit`
|
||||
- Only amend if: (1) you're the author, (2) commit not pushed
|
||||
- Check authorship: `git log -1 --format='%an %ae'`
|
||||
|
||||
### Examples
|
||||
|
||||
**Feature commit:**
|
||||
```
|
||||
feat(auth): add OAuth2 login support
|
||||
|
||||
Implement OAuth2 flow with Google and GitHub providers.
|
||||
Includes token refresh and session management.
|
||||
|
||||
Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
**Bug fix commit:**
|
||||
```
|
||||
fix(api): handle null response in user endpoint
|
||||
|
||||
The /api/users endpoint was crashing when the database
|
||||
returned null for deleted users.
|
||||
|
||||
Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
**Chore commit:**
|
||||
```
|
||||
chore(deps): update dependencies to latest versions
|
||||
|
||||
Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
### Important Notes
|
||||
|
||||
- NEVER skip hooks with --no-verify unless explicitly asked
|
||||
- NEVER force push to main/master
|
||||
- NEVER commit files containing secrets
|
||||
- If changes are too large, suggest splitting into multiple commits
|
||||
- Ask user before committing if anything is unclear
|
||||
|
||||
Now analyze the current changes and create an appropriate commit.
|
||||
152
commands/create-pr.md
Normal file
152
commands/create-pr.md
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
description: Create pull requests using GitHub CLI with Conventional Commits format
|
||||
model: claude-sonnet-4-5-20250929
|
||||
allowed-tools: Bash(git push:*), Bash(gh pr:*), mcp__plugin_git_git-intelligence__get_recent_commits, mcp__plugin_git_git-intelligence__get_diff_summary
|
||||
---
|
||||
|
||||
# Create Pull Request
|
||||
|
||||
Create well-formatted pull requests using GitHub CLI with Conventional Commits specification.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Check if `gh` is installed and authenticated:
|
||||
```bash
|
||||
gh auth status
|
||||
```
|
||||
|
||||
If not installed:
|
||||
```bash
|
||||
brew install gh
|
||||
gh auth login
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Gather Context
|
||||
|
||||
Use MCP tools for efficient data gathering:
|
||||
|
||||
- **Status** - Use `get_status` MCP tool
|
||||
- **Recent commits** - Use `get_recent_commits` MCP tool
|
||||
- **Diff summary** - Use `get_diff_summary` MCP tool
|
||||
|
||||
For branch info and comparison, use Bash:
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# Check if we need to push
|
||||
git log --oneline @{u}..HEAD 2>/dev/null || echo "No upstream"
|
||||
|
||||
# All commits on this branch vs main
|
||||
git log --oneline main..HEAD
|
||||
```
|
||||
|
||||
### 2. Analyze Changes
|
||||
|
||||
Review all commits that will be in the PR (not just the latest):
|
||||
```bash
|
||||
git diff main...HEAD --stat
|
||||
```
|
||||
|
||||
### 3. Determine PR Title
|
||||
|
||||
Use Conventional Commits format matching the primary change type:
|
||||
|
||||
| Type | Example |
|
||||
|------|---------|
|
||||
| feat | `feat(auth): add OAuth2 login support` |
|
||||
| fix | `fix(api): handle null response` |
|
||||
| docs | `docs(readme): update installation guide` |
|
||||
| style | `style(ui): improve button styling` |
|
||||
| refactor | `refactor(core): simplify data flow` |
|
||||
| perf | `perf(queries): optimize database calls` |
|
||||
| test | `test(auth): add login integration tests` |
|
||||
| build | `build(deps): upgrade to Node 20` |
|
||||
| ci | `ci(actions): add deployment workflow` |
|
||||
| chore | `chore(deps): update dependencies` |
|
||||
|
||||
### 4. Create the PR
|
||||
|
||||
```bash
|
||||
# Push branch if needed
|
||||
git push -u origin HEAD
|
||||
|
||||
# Create PR with HEREDOC for body
|
||||
gh pr create --title "<type>(<scope>): <subject>" --body "$(cat <<'EOF'
|
||||
## Summary
|
||||
|
||||
<1-3 bullet points describing what this PR does>
|
||||
|
||||
## Changes
|
||||
|
||||
<Brief description of the changes made>
|
||||
|
||||
## Test Plan
|
||||
|
||||
- [ ] <Testing checklist item>
|
||||
- [ ] <Another testing item>
|
||||
|
||||
---
|
||||
|
||||
Generated with [Claude Code](https://claude.ai/code)
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
### 5. Draft vs Ready
|
||||
|
||||
- Use `--draft` flag if work is in progress
|
||||
- Convert to ready: `gh pr ready`
|
||||
|
||||
## PR Body Template
|
||||
|
||||
```markdown
|
||||
## Summary
|
||||
|
||||
- <Primary change/feature>
|
||||
- <Secondary change if applicable>
|
||||
|
||||
## Changes
|
||||
|
||||
<Describe what changed and why>
|
||||
|
||||
## Test Plan
|
||||
|
||||
- [ ] Unit tests pass
|
||||
- [ ] Manual testing completed
|
||||
- [ ] No regressions
|
||||
|
||||
---
|
||||
|
||||
Generated with [Claude Code](https://claude.ai/code)
|
||||
```
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```bash
|
||||
# List your open PRs
|
||||
gh pr list --author "@me"
|
||||
|
||||
# Check PR status
|
||||
gh pr status
|
||||
|
||||
# View specific PR
|
||||
gh pr view <PR-NUMBER>
|
||||
|
||||
# Add reviewers
|
||||
gh pr edit <PR-NUMBER> --add-reviewer username
|
||||
|
||||
# Merge PR (squash)
|
||||
gh pr merge <PR-NUMBER> --squash
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
- NEVER force push to main/master
|
||||
- Always analyze ALL commits in the branch, not just the latest
|
||||
- If there's a PR template at `.github/pull_request_template.md`, use it
|
||||
- Return the PR URL when done so user can access it
|
||||
|
||||
Now analyze the current branch and create an appropriate PR.
|
||||
62
commands/history.md
Normal file
62
commands/history.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
description: Explore git commit history using git-intelligence MCP tools
|
||||
model: claude-haiku-4-5-20251001
|
||||
allowed-tools: mcp__plugin_git_git-intelligence__get_recent_commits, mcp__plugin_git_git-intelligence__search_commits, mcp__plugin_git_git-intelligence__get_diff_summary
|
||||
argument-hint: [search-query]
|
||||
---
|
||||
|
||||
# Git History Explorer
|
||||
|
||||
Interactively explore git commit history using the git-intelligence MCP tools.
|
||||
|
||||
## Instructions
|
||||
|
||||
Help the user explore git history to understand past changes. Use the MCP tools for efficient queries.
|
||||
|
||||
### Available MCP Tools
|
||||
|
||||
Based on the user's request, use the appropriate tool:
|
||||
|
||||
1. **Recent commits** → Use `get_recent_commits` tool
|
||||
- Default: 10 commits, adjust `limit` as needed
|
||||
|
||||
2. **Search by message** → Use `search_commits` tool
|
||||
- Set `query` to the search term
|
||||
- Set `search_code: false` (default)
|
||||
|
||||
3. **Search by code change** → Use `search_commits` tool
|
||||
- Set `query` to the code snippet
|
||||
- Set `search_code: true` (like git log -S)
|
||||
|
||||
4. **Current status** → Use `get_status` tool
|
||||
- Shows branch, staged/modified/untracked files
|
||||
|
||||
### Fallback to Bash
|
||||
|
||||
For queries not covered by MCP tools, use Bash:
|
||||
|
||||
- **File history**: `git log --oneline -10 -- <filepath>`
|
||||
- **Branch info**: `git branch -a`
|
||||
- **Show specific commit**: `git show <commit-hash> --stat`
|
||||
- **Compare branches**: `git log --oneline main..HEAD`
|
||||
- **Who changed what**: `git blame <filepath>`
|
||||
- **Time-based queries**: `git log --oneline --since="last week"`
|
||||
|
||||
### Arguments
|
||||
|
||||
Parse the user's query to determine intent:
|
||||
- `/git:history` → Use `get_recent_commits`
|
||||
- `/git:history auth` → Use `search_commits` with query "auth"
|
||||
- `/git:history src/api.ts` → Use Bash: `git log --oneline -10 -- src/api.ts`
|
||||
- `/git:history -S function` → Use `search_commits` with search_code: true
|
||||
- `/git:history last week` → Use Bash: `git log --oneline --since="last week"`
|
||||
|
||||
### Output
|
||||
|
||||
Present results clearly with:
|
||||
- Commit hash (short)
|
||||
- Subject line
|
||||
- Author and relative time
|
||||
- Optionally show diff for specific commits
|
||||
|
||||
Now explore the history based on the query: $ARGUMENTS
|
||||
56
commands/session-log.md
Normal file
56
commands/session-log.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
description: Show git activity during this Claude session
|
||||
model: claude-haiku-4-5-20251001
|
||||
allowed-tools: mcp__plugin_git_git-intelligence__get_recent_commits, mcp__plugin_git_git-intelligence__get_diff_summary
|
||||
---
|
||||
|
||||
# Session Activity Log
|
||||
|
||||
Show what git activity has happened during this session using the git-intelligence MCP tools.
|
||||
|
||||
## Instructions
|
||||
|
||||
Display a summary of git activity during the current Claude session, including:
|
||||
- Commits made
|
||||
- Files changed
|
||||
- Current uncommitted work
|
||||
|
||||
### Workflow
|
||||
|
||||
1. **Get recent commits** → Use `get_recent_commits` tool with `limit: 10`
|
||||
- Shows commits with hash, message, author, and relative time
|
||||
|
||||
2. **Get current status** → Use `get_status` tool
|
||||
- Returns branch, staged/modified/untracked counts, and file lists
|
||||
|
||||
3. **Get diff summary** → Use `get_diff_summary` tool
|
||||
- Returns files changed with lines added/deleted
|
||||
|
||||
4. **Check for session summary file** (Bash fallback):
|
||||
```bash
|
||||
cat .claude-session-summary 2>/dev/null || echo "No session summary found"
|
||||
```
|
||||
|
||||
### Output Format
|
||||
|
||||
Present the information in a clear summary:
|
||||
|
||||
```
|
||||
## Session Activity
|
||||
|
||||
### Commits Made (recent)
|
||||
- abc1234 feat(auth): add login endpoint (5 minutes ago)
|
||||
- def5678 fix(api): handle null case (20 minutes ago)
|
||||
|
||||
### Current Changes
|
||||
Staged: 2 files (+45, -12)
|
||||
Modified: 3 files
|
||||
Untracked: 1 file
|
||||
|
||||
### Uncommitted Files
|
||||
M src/auth.ts
|
||||
M src/api.ts
|
||||
?? src/new-file.ts
|
||||
```
|
||||
|
||||
Now show the session activity.
|
||||
39
hooks/bun.lock
Normal file
39
hooks/bun.lock
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "git-hooks",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.1.50", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^3.24.1" } }, "sha512-vHOLohUeiVadWl4eTAbw12ACIG1wZ/NN4ScLe8P/yrsldT1QkYwn6ndkoilaFBB2gIHECEx7wRAtSfCLefge4Q=="],
|
||||
|
||||
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
|
||||
|
||||
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="],
|
||||
|
||||
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="],
|
||||
|
||||
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="],
|
||||
|
||||
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="],
|
||||
|
||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
|
||||
|
||||
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
}
|
||||
}
|
||||
46
hooks/git-context-loader.test.ts
Normal file
46
hooks/git-context-loader.test.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { parseGitStatus } from "./git-context-loader";
|
||||
|
||||
describe("git-context-loader", () => {
|
||||
test("parseGitStatus parses clean status", () => {
|
||||
const output = "## main...origin/main";
|
||||
const result = parseGitStatus(output);
|
||||
expect(result.branch).toBe("main");
|
||||
expect(result.status).toEqual({ staged: 0, modified: 0, untracked: 0 });
|
||||
});
|
||||
|
||||
test("parseGitStatus parses dirty status", () => {
|
||||
const output = `## feature/test...origin/feature/test [ahead 1]
|
||||
M modified-file.ts
|
||||
A staged-file.ts
|
||||
?? untracked-file.ts
|
||||
D deleted-file.ts`;
|
||||
|
||||
const result = parseGitStatus(output);
|
||||
expect(result.branch).toBe("feature/test");
|
||||
// M (modified), A (staged), ?? (untracked), D (unstaged delete -> modified)
|
||||
// M -> index: M, worktree: space -> staged
|
||||
// A -> index: A, worktree: space -> staged
|
||||
// ?? -> untracked
|
||||
// D -> index: space, worktree: D -> modified
|
||||
|
||||
// Let's check the logic in parseGitStatus:
|
||||
// if (code.startsWith("?") || code === "??") untracked++
|
||||
// else:
|
||||
// if (code[0] !== " " && code[0] !== "?") staged++
|
||||
// if (code[1] !== " " && code[1] !== "?") modified++
|
||||
|
||||
// M -> code="M " -> staged++
|
||||
// A -> code="A " -> staged++
|
||||
// ?? -> untracked++
|
||||
// D -> code=" D" -> modified++
|
||||
|
||||
expect(result.status).toEqual({ staged: 2, modified: 1, untracked: 1 });
|
||||
});
|
||||
|
||||
test("parseGitStatus handles detached head", () => {
|
||||
const output = "## HEAD (no branch)";
|
||||
const result = parseGitStatus(output);
|
||||
expect(result.branch).toBe("HEAD (no branch)");
|
||||
});
|
||||
});
|
||||
182
hooks/git-context-loader.ts
Normal file
182
hooks/git-context-loader.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
/**
|
||||
* Git Context Loader Hook
|
||||
*
|
||||
* SessionStart hook that loads git context at the beginning of a session.
|
||||
* Outputs recent commits, status, and open issues for Claude's awareness.
|
||||
*/
|
||||
|
||||
import type { SessionStartHookInput } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
interface GitContext {
|
||||
branch: string;
|
||||
status: {
|
||||
staged: number;
|
||||
modified: number;
|
||||
untracked: number;
|
||||
};
|
||||
recentCommits: string[];
|
||||
openIssues?: string[];
|
||||
}
|
||||
|
||||
async function exec(
|
||||
command: string,
|
||||
cwd: string,
|
||||
): Promise<{ stdout: string; exitCode: number }> {
|
||||
const proc = Bun.spawn(["sh", "-c", command], {
|
||||
cwd,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const stdout = await new Response(proc.stdout).text();
|
||||
const exitCode = await proc.exited;
|
||||
|
||||
return { stdout: stdout.trim(), exitCode };
|
||||
}
|
||||
|
||||
async function isGitRepo(cwd: string): Promise<boolean> {
|
||||
const { exitCode } = await exec("git rev-parse --git-dir", cwd);
|
||||
return exitCode === 0;
|
||||
}
|
||||
|
||||
export function parseGitStatus(statusOut: string) {
|
||||
const lines = statusOut.split("\n");
|
||||
const branchLine = lines.find((l) => l.startsWith("##"));
|
||||
let branch = "(detached)";
|
||||
if (branchLine) {
|
||||
const parsed = branchLine.substring(3).split("...")[0];
|
||||
if (parsed) branch = parsed.trim();
|
||||
}
|
||||
|
||||
let staged = 0;
|
||||
let modified = 0;
|
||||
let untracked = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("##") || !line.trim()) continue;
|
||||
|
||||
const code = line.substring(0, 2);
|
||||
if (code.startsWith("?") || code === "??") {
|
||||
untracked++;
|
||||
} else {
|
||||
if (code[0] !== " " && code[0] !== "?") staged++;
|
||||
if (code[1] !== " " && code[1] !== "?") modified++;
|
||||
}
|
||||
}
|
||||
|
||||
return { branch, status: { staged, modified, untracked } };
|
||||
}
|
||||
|
||||
async function getGitContext(cwd: string): Promise<GitContext | null> {
|
||||
if (!(await isGitRepo(cwd))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get status and branch in one go
|
||||
const { stdout: statusOut } = await exec(
|
||||
"git status --porcelain -b 2>/dev/null",
|
||||
cwd,
|
||||
);
|
||||
|
||||
const { branch, status } = parseGitStatus(statusOut);
|
||||
|
||||
// Get recent commits
|
||||
const { stdout: commitsOut } = await exec(
|
||||
'git log --oneline -5 --format="%h %s (%ar)" 2>/dev/null',
|
||||
cwd,
|
||||
);
|
||||
const recentCommits = commitsOut
|
||||
.split("\n")
|
||||
.filter((line) => line.trim() !== "");
|
||||
|
||||
const context: GitContext = {
|
||||
branch: branch || "(detached)",
|
||||
status,
|
||||
recentCommits,
|
||||
};
|
||||
|
||||
// Check for open issues if gh is available and authenticated
|
||||
const { exitCode: ghAuthCheck } = await exec("gh auth status", cwd);
|
||||
if (ghAuthCheck === 0) {
|
||||
const { stdout: issuesOut, exitCode: issuesCode } = await exec(
|
||||
"gh issue list --limit 3 --state open 2>/dev/null",
|
||||
cwd,
|
||||
);
|
||||
if (issuesCode === 0 && issuesOut.trim()) {
|
||||
context.openIssues = issuesOut
|
||||
.split("\n")
|
||||
.filter((line) => line.trim() !== "");
|
||||
}
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
function formatContext(context: GitContext): string {
|
||||
const { branch, status, recentCommits, openIssues } = context;
|
||||
|
||||
let output = "Git Context:\n";
|
||||
output += ` Branch: ${branch}\n`;
|
||||
output += ` Status: ${status.staged} staged, ${status.modified} modified, ${status.untracked} untracked\n`;
|
||||
output += "\nRecent commits:\n";
|
||||
|
||||
if (recentCommits.length > 0) {
|
||||
recentCommits.forEach((commit) => {
|
||||
output += ` ${commit}\n`;
|
||||
});
|
||||
} else {
|
||||
output += " (no commits yet)\n";
|
||||
}
|
||||
|
||||
if (openIssues && openIssues.length > 0) {
|
||||
output += "\nOpen issues (top 3):\n";
|
||||
openIssues.forEach((issue) => {
|
||||
output += ` ${issue}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function formatSystemMessage(context: GitContext): string {
|
||||
const { branch, status, recentCommits } = context;
|
||||
const totalChanges = status.staged + status.modified + status.untracked;
|
||||
const changesStr = totalChanges > 0 ? `, ${totalChanges} changes` : "";
|
||||
const lastCommit =
|
||||
recentCommits[0]?.split(" ").slice(1).join(" ") || "no commits";
|
||||
return `Git: ${branch}${changesStr} | Last: ${lastCommit}`;
|
||||
}
|
||||
|
||||
interface HookOutput {
|
||||
systemMessage?: string;
|
||||
hookSpecificOutput: {
|
||||
hookEventName: string;
|
||||
additionalContext: string;
|
||||
};
|
||||
}
|
||||
|
||||
// Main execution
|
||||
if (import.meta.main) {
|
||||
const input = (await Bun.stdin.json()) as SessionStartHookInput;
|
||||
const { cwd, source } = input;
|
||||
|
||||
// Only run on startup, not on resume/clear/compact
|
||||
if (source === "startup") {
|
||||
const context = await getGitContext(cwd);
|
||||
|
||||
if (context) {
|
||||
const output: HookOutput = {
|
||||
systemMessage: formatSystemMessage(context),
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "SessionStart",
|
||||
additionalContext: formatContext(context),
|
||||
},
|
||||
};
|
||||
console.log(JSON.stringify(output));
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
32
hooks/hooks.json
Normal file
32
hooks/hooks.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"description": "Git intelligence hooks for session context and summaries",
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/install-deps.sh",
|
||||
"timeout": 30
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun run ${CLAUDE_PLUGIN_ROOT}/hooks/git-context-loader.ts",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreCompact": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun run ${CLAUDE_PLUGIN_ROOT}/hooks/session-summary.ts",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
200
hooks/hooks.test.ts
Normal file
200
hooks/hooks.test.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import type {
|
||||
PreCompactHookInput,
|
||||
SessionStartHookInput,
|
||||
} from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
// Test the hook input types and basic functionality
|
||||
|
||||
describe("SessionStartHookInput type", () => {
|
||||
test("has correct shape", () => {
|
||||
const input: SessionStartHookInput = {
|
||||
session_id: "test-session-123",
|
||||
transcript_path: "/tmp/transcript",
|
||||
cwd: "/Users/test/code",
|
||||
hook_event_name: "SessionStart",
|
||||
source: "startup",
|
||||
};
|
||||
|
||||
expect(input.session_id).toBe("test-session-123");
|
||||
expect(input.cwd).toBe("/Users/test/code");
|
||||
expect(input.hook_event_name).toBe("SessionStart");
|
||||
expect(input.source).toBe("startup");
|
||||
});
|
||||
|
||||
test("source can be startup, resume, clear, or compact", () => {
|
||||
const sources: SessionStartHookInput["source"][] = [
|
||||
"startup",
|
||||
"resume",
|
||||
"clear",
|
||||
"compact",
|
||||
];
|
||||
|
||||
sources.forEach((source) => {
|
||||
const input: SessionStartHookInput = {
|
||||
session_id: "test",
|
||||
transcript_path: "/tmp",
|
||||
cwd: "/tmp",
|
||||
hook_event_name: "SessionStart",
|
||||
source,
|
||||
};
|
||||
expect(input.source).toBe(source);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("PreCompactHookInput type", () => {
|
||||
test("has correct shape", () => {
|
||||
const input: PreCompactHookInput = {
|
||||
session_id: "test-session-123",
|
||||
transcript_path: "/tmp/transcript",
|
||||
cwd: "/Users/test/code",
|
||||
hook_event_name: "PreCompact",
|
||||
trigger: "auto",
|
||||
custom_instructions: null,
|
||||
};
|
||||
|
||||
expect(input.session_id).toBe("test-session-123");
|
||||
expect(input.hook_event_name).toBe("PreCompact");
|
||||
expect(input.trigger).toBe("auto");
|
||||
expect(input.custom_instructions).toBeNull();
|
||||
});
|
||||
|
||||
test("trigger can be manual or auto", () => {
|
||||
const triggers: PreCompactHookInput["trigger"][] = ["manual", "auto"];
|
||||
|
||||
triggers.forEach((trigger) => {
|
||||
const input: PreCompactHookInput = {
|
||||
session_id: "test",
|
||||
transcript_path: "/tmp",
|
||||
cwd: "/tmp",
|
||||
hook_event_name: "PreCompact",
|
||||
trigger,
|
||||
custom_instructions: null,
|
||||
};
|
||||
expect(input.trigger).toBe(trigger);
|
||||
});
|
||||
});
|
||||
|
||||
test("custom_instructions can be string or null", () => {
|
||||
const withInstructions: PreCompactHookInput = {
|
||||
session_id: "test",
|
||||
transcript_path: "/tmp",
|
||||
cwd: "/tmp",
|
||||
hook_event_name: "PreCompact",
|
||||
trigger: "manual",
|
||||
custom_instructions: "Focus on the auth changes",
|
||||
};
|
||||
|
||||
const withoutInstructions: PreCompactHookInput = {
|
||||
session_id: "test",
|
||||
transcript_path: "/tmp",
|
||||
cwd: "/tmp",
|
||||
hook_event_name: "PreCompact",
|
||||
trigger: "auto",
|
||||
custom_instructions: null,
|
||||
};
|
||||
|
||||
expect(withInstructions.custom_instructions).toBe(
|
||||
"Focus on the auth changes",
|
||||
);
|
||||
expect(withoutInstructions.custom_instructions).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("git-context-loader", () => {
|
||||
test("can be run with valid input", async () => {
|
||||
const input: SessionStartHookInput = {
|
||||
session_id: "test-123",
|
||||
transcript_path: "/tmp/transcript",
|
||||
cwd: import.meta.dir, // Use the hooks directory (which is in a git repo)
|
||||
hook_event_name: "SessionStart",
|
||||
source: "startup",
|
||||
};
|
||||
|
||||
// Run the actual hook script using echo to pipe input
|
||||
const proc = Bun.spawn(
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
`echo '${JSON.stringify(input)}' | bun run git-context-loader.ts`,
|
||||
],
|
||||
{
|
||||
cwd: import.meta.dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
},
|
||||
);
|
||||
|
||||
// Wait for process to complete
|
||||
const exitCode = await proc.exited;
|
||||
const stdout = await new Response(proc.stdout).text();
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
// Should output git context
|
||||
expect(stdout).toContain("Git Context:");
|
||||
expect(stdout).toContain("Branch:");
|
||||
});
|
||||
|
||||
test("skips non-startup sources", async () => {
|
||||
const input: SessionStartHookInput = {
|
||||
session_id: "test-123",
|
||||
transcript_path: "/tmp/transcript",
|
||||
cwd: import.meta.dir,
|
||||
hook_event_name: "SessionStart",
|
||||
source: "resume", // Not startup
|
||||
};
|
||||
|
||||
const proc = Bun.spawn(
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
`echo '${JSON.stringify(input)}' | bun run git-context-loader.ts`,
|
||||
],
|
||||
{
|
||||
cwd: import.meta.dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
},
|
||||
);
|
||||
|
||||
const exitCode = await proc.exited;
|
||||
const stdout = await new Response(proc.stdout).text();
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
// Should NOT output git context for resume
|
||||
expect(stdout).not.toContain("Git Context:");
|
||||
});
|
||||
});
|
||||
|
||||
describe("session-summary", () => {
|
||||
test("can be run with valid input", async () => {
|
||||
const input: PreCompactHookInput = {
|
||||
session_id: "test-123",
|
||||
transcript_path: "/tmp/transcript",
|
||||
cwd: import.meta.dir,
|
||||
hook_event_name: "PreCompact",
|
||||
trigger: "manual",
|
||||
custom_instructions: null,
|
||||
};
|
||||
|
||||
const proc = Bun.spawn(
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
`echo '${JSON.stringify(input)}' | bun run session-summary.ts`,
|
||||
],
|
||||
{
|
||||
cwd: import.meta.dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
},
|
||||
);
|
||||
|
||||
const exitCode = await proc.exited;
|
||||
const stdout = await new Response(proc.stdout).text();
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Session summary saved");
|
||||
});
|
||||
});
|
||||
23
hooks/install-deps.sh
Executable file
23
hooks/install-deps.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
# Install MCP server and hooks dependencies on session start
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PLUGIN_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
MCP_DIR="$PLUGIN_ROOT/mcp-servers/git-intelligence"
|
||||
HOOKS_DIR="$PLUGIN_ROOT/hooks"
|
||||
|
||||
# Install MCP server dependencies
|
||||
if [ ! -d "$MCP_DIR/node_modules" ]; then
|
||||
cd "$MCP_DIR"
|
||||
bun install --silent 2>/dev/null || bun install
|
||||
fi
|
||||
|
||||
# Install hooks dependencies (for TypeScript types)
|
||||
if [ ! -d "$HOOKS_DIR/node_modules" ]; then
|
||||
cd "$HOOKS_DIR"
|
||||
bun install --silent 2>/dev/null || bun install
|
||||
fi
|
||||
|
||||
exit 0
|
||||
15
hooks/package.json
Normal file
15
hooks/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "git-hooks",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "bun test",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "^1.3.3"
|
||||
}
|
||||
}
|
||||
158
hooks/session-summary.ts
Normal file
158
hooks/session-summary.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
/**
|
||||
* Session Summary Hook
|
||||
*
|
||||
* PreCompact hook that saves a session summary before context compaction.
|
||||
* Helps maintain continuity across context windows.
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { PreCompactHookInput } from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
interface SessionSummary {
|
||||
timestamp: string;
|
||||
branch: string;
|
||||
trigger: "manual" | "auto";
|
||||
sessionCommits: string[];
|
||||
uncommittedChanges: {
|
||||
staged: string;
|
||||
modified: string;
|
||||
};
|
||||
}
|
||||
|
||||
async function exec(
|
||||
command: string,
|
||||
cwd: string,
|
||||
): Promise<{ stdout: string; exitCode: number }> {
|
||||
const proc = Bun.spawn(["sh", "-c", command], {
|
||||
cwd,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const stdout = await new Response(proc.stdout).text();
|
||||
const exitCode = await proc.exited;
|
||||
|
||||
return { stdout: stdout.trim(), exitCode };
|
||||
}
|
||||
|
||||
async function isGitRepo(cwd: string): Promise<boolean> {
|
||||
const { exitCode } = await exec("git rev-parse --git-dir", cwd);
|
||||
return exitCode === 0;
|
||||
}
|
||||
|
||||
async function getGitRoot(cwd: string): Promise<string | null> {
|
||||
const { stdout, exitCode } = await exec("git rev-parse --show-toplevel", cwd);
|
||||
return exitCode === 0 ? stdout : null;
|
||||
}
|
||||
|
||||
async function getSessionSummary(
|
||||
cwd: string,
|
||||
trigger: "manual" | "auto",
|
||||
): Promise<SessionSummary | null> {
|
||||
if (!(await isGitRepo(cwd))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get branch
|
||||
const { stdout: branch } = await exec(
|
||||
"git branch --show-current 2>/dev/null || echo '(detached)'",
|
||||
cwd,
|
||||
);
|
||||
|
||||
// Get commits from the last hour (approximate session length)
|
||||
const { stdout: commitsOut } = await exec(
|
||||
'git log --oneline --since="1 hour ago" 2>/dev/null | head -10',
|
||||
cwd,
|
||||
);
|
||||
const sessionCommits = commitsOut
|
||||
.split("\n")
|
||||
.filter((line) => line.trim() !== "");
|
||||
|
||||
// Get staged changes summary
|
||||
const { stdout: stagedStat } = await exec(
|
||||
"git diff --cached --stat 2>/dev/null | tail -1",
|
||||
cwd,
|
||||
);
|
||||
|
||||
// Get modified changes summary
|
||||
const { stdout: modifiedStat } = await exec(
|
||||
"git diff --stat 2>/dev/null | tail -1",
|
||||
cwd,
|
||||
);
|
||||
|
||||
return {
|
||||
timestamp: new Date().toISOString(),
|
||||
branch: branch || "(detached)",
|
||||
trigger,
|
||||
sessionCommits,
|
||||
uncommittedChanges: {
|
||||
staged: stagedStat || "none",
|
||||
modified: modifiedStat || "none",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function formatSummary(summary: SessionSummary): string {
|
||||
let output = "# Claude Session Summary\n";
|
||||
output += `# Generated: ${summary.timestamp}\n`;
|
||||
output += `# Branch: ${summary.branch}\n`;
|
||||
output += `# Trigger: ${summary.trigger}\n\n`;
|
||||
|
||||
output += "## Session Activity\n\n";
|
||||
|
||||
if (summary.sessionCommits.length > 0) {
|
||||
output += "### Commits this session:\n";
|
||||
summary.sessionCommits.forEach((commit) => {
|
||||
output += `- ${commit}\n`;
|
||||
});
|
||||
output += "\n";
|
||||
}
|
||||
|
||||
if (
|
||||
summary.uncommittedChanges.staged !== "none" ||
|
||||
summary.uncommittedChanges.modified !== "none"
|
||||
) {
|
||||
output += "### Uncommitted changes:\n";
|
||||
if (summary.uncommittedChanges.staged !== "none") {
|
||||
output += `Staged: ${summary.uncommittedChanges.staged}\n`;
|
||||
}
|
||||
if (summary.uncommittedChanges.modified !== "none") {
|
||||
output += `Modified: ${summary.uncommittedChanges.modified}\n`;
|
||||
}
|
||||
output += "\n";
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Main execution
|
||||
const input = (await Bun.stdin.json()) as PreCompactHookInput;
|
||||
const { cwd, trigger } = input;
|
||||
|
||||
const gitRoot = await getGitRoot(cwd);
|
||||
|
||||
if (gitRoot) {
|
||||
const summary = await getSessionSummary(cwd, trigger);
|
||||
|
||||
if (summary) {
|
||||
// Write to ~/.claude/session-summaries/ to avoid polluting user repos
|
||||
const claudeDir = join(homedir(), ".claude", "session-summaries");
|
||||
if (!existsSync(claudeDir)) {
|
||||
mkdirSync(claudeDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Use repo name as filename to keep summaries separate per project
|
||||
const repoName = gitRoot.split("/").pop() || "unknown";
|
||||
const summaryPath = join(claudeDir, `${repoName}.md`);
|
||||
const content = formatSummary(summary);
|
||||
|
||||
await Bun.write(summaryPath, content);
|
||||
console.log(`Session summary saved to ${summaryPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
97
plugin.lock.json
Normal file
97
plugin.lock.json
Normal file
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||
"pluginId": "gh:nathanvale/side-quest-marketplace:plugins/git",
|
||||
"normalized": {
|
||||
"repo": null,
|
||||
"ref": "refs/tags/v20251128.0",
|
||||
"commit": "d928f3dcf67e95244c1c89af5bc6c128c9c1f431",
|
||||
"treeHash": "530f81ef6118809acaf022e840c4de5c9f6301021f6176971c33a37827567dbd",
|
||||
"generatedAt": "2025-11-28T10:27:14.576270Z",
|
||||
"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": "git",
|
||||
"description": "Git intelligence for Claude Code - session context, commit history, and smart commits with Conventional Commits",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"content": {
|
||||
"files": [
|
||||
{
|
||||
"path": "README.md",
|
||||
"sha256": "0268c0aff442dc0bcbca8102c649d1903859525b59d26d4a5a1fe58230928daa"
|
||||
},
|
||||
{
|
||||
"path": "hooks/git-context-loader.ts",
|
||||
"sha256": "f4b0e0eb11cb8398b6b6db856f8e14f8a3f0e4e5bc7dabe9ed4b459ea319b165"
|
||||
},
|
||||
{
|
||||
"path": "hooks/bun.lock",
|
||||
"sha256": "5ef31882354fa9dcc22799527b8e6ccb0292e33f6d85c202605dc02a85f7c51f"
|
||||
},
|
||||
{
|
||||
"path": "hooks/package.json",
|
||||
"sha256": "3c6f46756b0ccf6bda00514b17178d78c52b5c37dfa6548222155c3e177bbadc"
|
||||
},
|
||||
{
|
||||
"path": "hooks/hooks.test.ts",
|
||||
"sha256": "38d7b10a461910fd02b428705497b3396ebb7eea52c504250a9b665d0f3026ad"
|
||||
},
|
||||
{
|
||||
"path": "hooks/git-context-loader.test.ts",
|
||||
"sha256": "845e6e8bb21627fcc1ec91e2ce9a34ad8488d818173b74c8b1662e414db2fdbd"
|
||||
},
|
||||
{
|
||||
"path": "hooks/hooks.json",
|
||||
"sha256": "b23bc6fd5500fd83d47d215025c76a49251e7cf49dd17dccf61443cfe06b0084"
|
||||
},
|
||||
{
|
||||
"path": "hooks/session-summary.ts",
|
||||
"sha256": "7895cf7f075ebe6da65bc8dee42a7ba91f9202efc79e3eda632039c2b43eec75"
|
||||
},
|
||||
{
|
||||
"path": "hooks/install-deps.sh",
|
||||
"sha256": "e47946c4b285189db9ced87561ade597a60f6dcdde516cbeced9331984cfd0d3"
|
||||
},
|
||||
{
|
||||
"path": ".claude-plugin/plugin.json",
|
||||
"sha256": "d6aef1074c5d2b985dfe23b565b01aa00de6d4d63fd68aa43d58c8746ddf52fd"
|
||||
},
|
||||
{
|
||||
"path": "commands/history.md",
|
||||
"sha256": "dd8864684e6ffc10cc50809cce988139d24497e7c842e2d1f4bb49ad2d7aa0c3"
|
||||
},
|
||||
{
|
||||
"path": "commands/checkpoint.md",
|
||||
"sha256": "e6dba1faf3ec8cfce9c1f2178d2bfa07c5d28305d3395a76940c510f4c493173"
|
||||
},
|
||||
{
|
||||
"path": "commands/create-pr.md",
|
||||
"sha256": "4319905290ed92a99103e9a8797d3c2e99899cc25a54edcce9a708b0df10e492"
|
||||
},
|
||||
{
|
||||
"path": "commands/commit.md",
|
||||
"sha256": "84da3a89debb5af49dc417f02dd67e59b79d0904f8ac6df8370c81f3b0da3da8"
|
||||
},
|
||||
{
|
||||
"path": "commands/session-log.md",
|
||||
"sha256": "a7a3f91aa93d98335950f7896a88098b2605837e4f52c36567f1840a5cc7b35c"
|
||||
},
|
||||
{
|
||||
"path": "skills/git-expert/SKILL.md",
|
||||
"sha256": "1477e0db66d80387769adbc2bf1fddbc6faf4e5a0b3fb0f83c4bf6826a4e6436"
|
||||
}
|
||||
],
|
||||
"dirSha256": "530f81ef6118809acaf022e840c4de5c9f6301021f6176971c33a37827567dbd"
|
||||
},
|
||||
"security": {
|
||||
"scannedAt": null,
|
||||
"scannerVersion": null,
|
||||
"flags": []
|
||||
}
|
||||
}
|
||||
83
skills/git-expert/SKILL.md
Normal file
83
skills/git-expert/SKILL.md
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
name: git-expert
|
||||
description: Git operations, history exploration, and intelligent commit management. Use when users ask about git history ("what did we change", "when did we add"), want to understand past changes, need help with commits ("commit this", "save my work", "checkpoint"), ask about branches, or mention recent work.
|
||||
---
|
||||
|
||||
# Git Expert Skill
|
||||
|
||||
Git operations, history exploration, and intelligent commit management.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### History Analysis
|
||||
- Search commit messages and code changes
|
||||
- Trace file history to understand evolution
|
||||
- Find when specific code was introduced or removed
|
||||
- Identify who made changes and why
|
||||
|
||||
### Smart Commits
|
||||
- Create well-formatted Conventional Commits
|
||||
- Split large changes into atomic commits
|
||||
- Handle pre-commit hooks gracefully
|
||||
- Never commit secrets or sensitive data
|
||||
|
||||
### Session Awareness
|
||||
- Track what was done during the current session
|
||||
- Show uncommitted work
|
||||
- Create checkpoints before risky operations
|
||||
|
||||
## Tools Available
|
||||
|
||||
Use the git-intelligence MCP server tools:
|
||||
- `get_recent_commits` - Recent commit history
|
||||
- `search_commits` - Search by message or code
|
||||
- `get_status` - Current repository state
|
||||
- `get_diff_summary` - Summary of changes
|
||||
|
||||
For file history and branch info, use Bash:
|
||||
- `git log --oneline -10 -- <filepath>` - File-specific history
|
||||
- `git branch -a` - Branch information
|
||||
|
||||
## Commit Format
|
||||
|
||||
Always use Conventional Commits format:
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
```
|
||||
|
||||
Type mappings:
|
||||
| Type | Use for |
|
||||
|------|---------|
|
||||
| feat | New feature |
|
||||
| fix | Bug fix |
|
||||
| docs | Documentation |
|
||||
| style | Formatting |
|
||||
| refactor | Refactoring |
|
||||
| perf | Performance |
|
||||
| test | Tests |
|
||||
| build | Build system |
|
||||
| ci | CI/CD |
|
||||
| chore | Maintenance |
|
||||
| revert | Revert |
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Before editing**: Check recent commits to understand context
|
||||
2. **Before committing**: Review all changes, never blind commit
|
||||
3. **Large changes**: Suggest splitting into atomic commits
|
||||
4. **Unclear intent**: Ask user for commit scope/description
|
||||
5. **Secrets detected**: Warn and refuse to commit
|
||||
|
||||
## Example Interactions
|
||||
|
||||
**User**: "What changed in the auth module recently?"
|
||||
- Use `search_commits` with query "auth" or Bash `git log --oneline -10 -- src/auth/`
|
||||
|
||||
**User**: "Commit my changes"
|
||||
- Run `get_status`, review diffs, create appropriate conventional commit
|
||||
|
||||
**User**: "What did we do this session?"
|
||||
- Check recent commits and current diff, summarize activity
|
||||
|
||||
**User**: "Save my work before I try this"
|
||||
- Create a checkpoint commit with current context
|
||||
Reference in New Issue
Block a user