Initial commit
This commit is contained in:
17
.claude-plugin/plugin.json
Normal file
17
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "tools",
|
||||||
|
"description": "Skills and documentation for various CLI, development, and language-specific tools",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "boneskull"
|
||||||
|
},
|
||||||
|
"skills": [
|
||||||
|
"./skills"
|
||||||
|
],
|
||||||
|
"commands": [
|
||||||
|
"./commands"
|
||||||
|
],
|
||||||
|
"hooks": [
|
||||||
|
"./hooks"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# tools
|
||||||
|
|
||||||
|
Skills and documentation for various CLI, development, and language-specific tools
|
||||||
222
commands/apply-template.md
Normal file
222
commands/apply-template.md
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
---
|
||||||
|
description: Retroactively apply configuration and dev dependencies from boneskull-template to an existing project
|
||||||
|
argument-hint: [target-directory]
|
||||||
|
---
|
||||||
|
|
||||||
|
# /apply-template
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Retroactively apply configuration files and development dependencies from the [boneskull-template](https://github.com/boneskull/boneskull-template) repository to an existing project, intelligently merging package.json and copying missing configuration files.
|
||||||
|
|
||||||
|
## Contract
|
||||||
|
|
||||||
|
**Inputs:** `target-directory` (optional) — Target project directory (defaults to current working directory)
|
||||||
|
**Outputs:** Summary of changes made
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Preparation
|
||||||
|
|
||||||
|
- Validate target directory exists and contains a `package.json`
|
||||||
|
- Verify git working tree is clean in target (warn user if not)
|
||||||
|
- Fetch or clone latest boneskull-template:
|
||||||
|
- **Cache location:** `~/.cache/boneskull-template`
|
||||||
|
- If cache exists: `cd ~/.cache/boneskull-template && git pull origin main`
|
||||||
|
- If cache doesn't exist: `git clone https://github.com/boneskull/boneskull-template.git ~/.cache/boneskull-template`
|
||||||
|
- This avoids repeated clones and speeds up subsequent runs
|
||||||
|
|
||||||
|
### 2. Ignore List
|
||||||
|
|
||||||
|
**Never copy these from template:**
|
||||||
|
|
||||||
|
- `docs/plans/`
|
||||||
|
- `src/`
|
||||||
|
- `test/`
|
||||||
|
- `package-lock.json`
|
||||||
|
- Any file that already exists in target project (EXCEPT `package.json`)
|
||||||
|
|
||||||
|
### 3. Merge package.json
|
||||||
|
|
||||||
|
**Strategy:** Use the `merge-package.js` script to intelligently merge dependencies
|
||||||
|
|
||||||
|
1. **Create backup:** `cp package.json package.json.backup`
|
||||||
|
2. **Run merge script:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node plugins/tools/scripts/merge-package.js \
|
||||||
|
<target-directory>/package.json \
|
||||||
|
~/.cache/boneskull-template/package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Install dependencies:** Run `npm install` to install newly added dependencies
|
||||||
|
4. **Clean up:** Delete `package.json.backup` after successful merge and install
|
||||||
|
|
||||||
|
The merge script automatically:
|
||||||
|
|
||||||
|
- Compares versions between template and target
|
||||||
|
- Chooses the most recent semantic version
|
||||||
|
- Adds any dependencies that exist only in template
|
||||||
|
- Keeps any dependencies that exist only in target
|
||||||
|
- Adds scripts from template that don't exist in target (preserves user customizations)
|
||||||
|
- Adds missing fields like `engines`, `knip`, `lint-staged`, etc.
|
||||||
|
- Merges prettier config and adds plugins
|
||||||
|
|
||||||
|
**Version comparison logic (handled by merge-package.js):**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Use semver comparison - choose higher version
|
||||||
|
// Examples:
|
||||||
|
// "1.2.3" vs "1.2.4" → choose "1.2.4"
|
||||||
|
// "^1.0.0" vs "^2.0.0" → choose "^2.0.0"
|
||||||
|
// "~1.2.3" vs "1.2.4" → choose "1.2.4" (prefer exact)
|
||||||
|
// "latest" vs "1.0.0" → choose "latest"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Copy Configuration Files
|
||||||
|
|
||||||
|
**Strategy:** Copy files from template only if they don't exist in target
|
||||||
|
|
||||||
|
1. Get list of all files in template root (excluding ignored paths)
|
||||||
|
2. For each file:
|
||||||
|
- Skip if in ignore list
|
||||||
|
- Skip if already exists in target
|
||||||
|
- Copy to target if missing
|
||||||
|
3. Handle `.github/` directory specially:
|
||||||
|
- Copy `.github/` files that don't exist in target
|
||||||
|
- Don't overwrite existing workflow files
|
||||||
|
- Preserve target's existing .github structure
|
||||||
|
|
||||||
|
**Files to copy (if missing):**
|
||||||
|
|
||||||
|
- `.editorconfig`
|
||||||
|
- `.gitattributes`
|
||||||
|
- `.gitignore` (careful - may want to merge)
|
||||||
|
- `.eslintrc.*` / `eslint.config.js`
|
||||||
|
- `.prettierrc.*` / `prettier.config.js`
|
||||||
|
- `.prettierignore`
|
||||||
|
- `.commitlintrc.*`
|
||||||
|
- `.markdownlint*`
|
||||||
|
- `.npmrc`
|
||||||
|
- `tsconfig.json`
|
||||||
|
- `cspell.json`
|
||||||
|
- `.husky/` directory and contents
|
||||||
|
- `.github/` directory (non-overlapping files only)
|
||||||
|
- `LICENSE` (only if missing)
|
||||||
|
- Other dotfiles in root
|
||||||
|
|
||||||
|
### 5. Post-Application Steps
|
||||||
|
|
||||||
|
After all changes are complete, inform user they should:
|
||||||
|
|
||||||
|
1. **Review changes:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git diff package.json
|
||||||
|
git status # See new files
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Initialize tools if needed:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# If Husky was added:
|
||||||
|
npm run prepare # or: npx husky install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Review and customize:**
|
||||||
|
- Check new configuration files match project needs
|
||||||
|
- Adjust scripts in package.json
|
||||||
|
- Customize ESLint/Prettier rules
|
||||||
|
- Update README with new tooling info
|
||||||
|
|
||||||
|
**Note:** Dependencies are automatically installed during step 3 (after merging package.json), so no separate `npm install` is needed unless the user wants to run it again.
|
||||||
|
|
||||||
|
### 6. Output Format
|
||||||
|
|
||||||
|
Provide clear summary of actions taken:
|
||||||
|
|
||||||
|
```text
|
||||||
|
✅ Applied boneskull-template to project
|
||||||
|
|
||||||
|
Package.json changes:
|
||||||
|
📦 Added dependencies: prettier, eslint, typescript
|
||||||
|
📦 Updated dependencies: husky (8.0.0 → 9.0.0), lint-staged (15.0.0 → 16.0.0)
|
||||||
|
📝 Added scripts: lint, format, test
|
||||||
|
|
||||||
|
Configuration files added:
|
||||||
|
✨ .editorconfig
|
||||||
|
✨ .prettierrc.json
|
||||||
|
✨ eslint.config.js
|
||||||
|
✨ tsconfig.json
|
||||||
|
✨ .husky/pre-commit
|
||||||
|
✨ .github/workflows/ci.yml
|
||||||
|
|
||||||
|
Files skipped (already exist):
|
||||||
|
⏭️ .gitignore
|
||||||
|
⏭️ LICENSE
|
||||||
|
⏭️ .github/workflows/release.yml
|
||||||
|
|
||||||
|
Next steps:
|
||||||
|
1. Review changes: git diff package.json && git status
|
||||||
|
2. Initialize Husky: npm run prepare
|
||||||
|
3. Customize configs as needed
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Apply to current directory
|
||||||
|
/tools:apply-template
|
||||||
|
|
||||||
|
# Apply to specific project
|
||||||
|
/tools:apply-template ../my-project
|
||||||
|
|
||||||
|
# Apply to absolute path
|
||||||
|
/tools:apply-template /Users/me/projects/my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- **Never overwrite existing files** (except `package.json`)
|
||||||
|
- **Always choose newer version** when merging dependencies
|
||||||
|
- **Preserve user customizations** in scripts and configs
|
||||||
|
- **Git working tree must be clean** (warn and exit if not)
|
||||||
|
- **Validate package.json** after merge (must be valid JSON)
|
||||||
|
- **Create backup** of original package.json before modifying
|
||||||
|
- **Handle errors gracefully** (missing template, network issues, etc.)
|
||||||
|
|
||||||
|
## Edge Cases
|
||||||
|
|
||||||
|
1. **Git not clean:**
|
||||||
|
- Warn user: "Working tree has uncommitted changes. Commit or stash before applying template."
|
||||||
|
- Exit without making changes
|
||||||
|
|
||||||
|
2. **No package.json in target:**
|
||||||
|
- Error: "Target directory is not a Node.js project (no package.json found)"
|
||||||
|
- Exit
|
||||||
|
|
||||||
|
3. **Network error fetching template:**
|
||||||
|
- Try local cached copy if available
|
||||||
|
- Error if no cached copy: "Cannot fetch template. Check network connection."
|
||||||
|
|
||||||
|
4. **Version comparison ambiguity:**
|
||||||
|
- If versions are equivalent (e.g., "^1.0.0" vs "~1.0.2"), prefer exact version
|
||||||
|
- If can't parse version, keep target's version and warn user
|
||||||
|
|
||||||
|
5. **.gitignore conflicts:**
|
||||||
|
- If target has .gitignore, DON'T overwrite
|
||||||
|
- Consider offering to merge (show diff, ask user)
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
- **Template cache:** `~/.cache/boneskull-template`
|
||||||
|
- **Template URL:** `https://github.com/boneskull/boneskull-template.git`
|
||||||
|
- **Merge script:** `plugins/tools/scripts/merge-package.js` - handles all package.json merging logic
|
||||||
|
- **Workflow:**
|
||||||
|
1. Ensure template cache exists (clone if needed, pull if exists)
|
||||||
|
2. Create package.json.backup
|
||||||
|
3. Run merge-package.js script
|
||||||
|
4. Run npm install
|
||||||
|
5. Copy missing configuration files
|
||||||
|
6. Delete package.json.backup
|
||||||
|
7. Display summary of changes
|
||||||
575
commands/finish-worktree.md
Normal file
575
commands/finish-worktree.md
Normal file
@@ -0,0 +1,575 @@
|
|||||||
|
---
|
||||||
|
description: Merge a finished feature branch from a worktree while maintaining linear history
|
||||||
|
argument-hint: [main-worktree-path]
|
||||||
|
---
|
||||||
|
|
||||||
|
# /finish-worktree
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Merge a completed feature branch from a git worktree into main using rebase and fast-forward merge to maintain a linear commit history. This command ensures no merge commits are created.
|
||||||
|
|
||||||
|
## Contract
|
||||||
|
|
||||||
|
**Inputs:** `main-worktree-path` (optional) — Path to the main worktree directory (prompts user if not provided)
|
||||||
|
**Outputs:** Summary of merge operation and next steps
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### 1. Validate Current State
|
||||||
|
|
||||||
|
**Before starting:**
|
||||||
|
|
||||||
|
- Verify you're currently in a git worktree (not the main repository)
|
||||||
|
- Check that working tree is clean: `git status`
|
||||||
|
- If working tree has uncommitted changes, warn user and exit:
|
||||||
|
|
||||||
|
```text
|
||||||
|
⚠️ Working tree has uncommitted changes. Please commit or stash before finishing the worktree.
|
||||||
|
```
|
||||||
|
|
||||||
|
- Get the current feature branch name: `git branch --show-current`
|
||||||
|
|
||||||
|
### 2. Rebase Feature Branch onto Main
|
||||||
|
|
||||||
|
**Goal:** Replay feature branch commits on top of latest main
|
||||||
|
|
||||||
|
1. **Detect if rebase is already in progress:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
test -d .git/rebase-merge || test -d .git/rebase-apply
|
||||||
|
```
|
||||||
|
|
||||||
|
**If rebase IS in progress:**
|
||||||
|
- Skip to step 3 (conflict handling)
|
||||||
|
- Do NOT attempt to start a new rebase
|
||||||
|
|
||||||
|
**If rebase is NOT in progress:**
|
||||||
|
- Continue to step 2 (fetch and start rebase)
|
||||||
|
|
||||||
|
2. **Fetch latest main (only if no rebase in progress):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git fetch origin main:main
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Start rebase (only if no rebase in progress):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git rebase main
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Handle rebase conflicts:**
|
||||||
|
|
||||||
|
**If rebase completed successfully:**
|
||||||
|
- Proceed to step 3 (Navigate to Main Worktree)
|
||||||
|
|
||||||
|
**If conflicts exist:**
|
||||||
|
- List conflicting files: `git status --short | grep '^UU'`
|
||||||
|
- Instruct user to resolve conflicts:
|
||||||
|
|
||||||
|
```text
|
||||||
|
⚠️ Rebase conflicts detected in:
|
||||||
|
<list of conflicting files>
|
||||||
|
|
||||||
|
Please resolve these conflicts and run:
|
||||||
|
git add <resolved-files>
|
||||||
|
git rebase --continue
|
||||||
|
|
||||||
|
Then call /tools:finish-worktree again to continue.
|
||||||
|
```
|
||||||
|
|
||||||
|
- Exit and wait for user to resolve conflicts
|
||||||
|
|
||||||
|
**If conflicts were resolved but rebase hasn't continued:**
|
||||||
|
- Check status: `git status`
|
||||||
|
- If status shows "all conflicts fixed: run 'git rebase --continue'":
|
||||||
|
|
||||||
|
```text
|
||||||
|
✅ Conflicts resolved but rebase not continued.
|
||||||
|
|
||||||
|
Please run:
|
||||||
|
git rebase --continue
|
||||||
|
|
||||||
|
Then call /tools:finish-worktree again to continue.
|
||||||
|
```
|
||||||
|
|
||||||
|
- Exit and wait for user to continue rebase
|
||||||
|
|
||||||
|
5. **Repeat conflict resolution:**
|
||||||
|
- After user runs `git rebase --continue`, conflicts may occur again
|
||||||
|
- Repeat step 4 until rebase completes
|
||||||
|
- **Critical:** Never use `git rebase --skip` — every commit must be preserved
|
||||||
|
- **Critical:** Never alter commit messages unless user explicitly requests it
|
||||||
|
|
||||||
|
6. **Verify rebase completion:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status
|
||||||
|
```
|
||||||
|
|
||||||
|
- Ensure output shows "nothing to commit, working tree clean"
|
||||||
|
- Ensure no rebase in progress (no `.git/rebase-merge` or `.git/rebase-apply` directory)
|
||||||
|
- Only proceed to step 3 once rebase is fully complete
|
||||||
|
|
||||||
|
### 3. Navigate to Main Worktree
|
||||||
|
|
||||||
|
**Goal:** Switch to the worktree containing the main branch
|
||||||
|
|
||||||
|
1. **Check for main worktree argument:**
|
||||||
|
- If `main-worktree-path` argument was provided, use it
|
||||||
|
- If not provided, attempt to auto-detect:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git worktree list | grep 'main]$'
|
||||||
|
```
|
||||||
|
|
||||||
|
- If auto-detection finds exactly one main worktree, use its path
|
||||||
|
- If auto-detection fails or finds multiple, prompt user:
|
||||||
|
|
||||||
|
```text
|
||||||
|
📍 Please provide the path to your main worktree directory.
|
||||||
|
|
||||||
|
Hint: Run `git worktree list` to see all worktrees.
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Validate main worktree path:**
|
||||||
|
- Check directory exists
|
||||||
|
- Check it's a git repository: `test -d <path>/.git || test -f <path>/.git`
|
||||||
|
- Navigate to directory: `cd <main-worktree-path>`
|
||||||
|
- Verify branch is main: `git branch --show-current` should output "main"
|
||||||
|
- If not on main branch, error and exit:
|
||||||
|
|
||||||
|
```text
|
||||||
|
❌ Error: Directory is not on main branch (currently on: <branch-name>)
|
||||||
|
Please provide the correct path to the main worktree.
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Ensure main is clean:**
|
||||||
|
- Check status: `git status`
|
||||||
|
- If uncommitted changes exist, warn and exit:
|
||||||
|
|
||||||
|
```text
|
||||||
|
⚠️ Main worktree has uncommitted changes. Please commit or stash before finishing.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Fast-Forward Main to Feature Branch
|
||||||
|
|
||||||
|
**Goal:** Update main to point to the rebased feature branch
|
||||||
|
|
||||||
|
**Critical constraint:** This operation must be a fast-forward. Any divergence indicates history was not properly rebased.
|
||||||
|
|
||||||
|
1. **Attempt fast-forward merge:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git merge --ff-only <feature-branch-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Handle fast-forward outcomes:**
|
||||||
|
|
||||||
|
**Success (exit code 0):**
|
||||||
|
- Proceed to step 5 (Delete Feature Worktree)
|
||||||
|
|
||||||
|
**Failure (non-zero exit code):**
|
||||||
|
- Check if feature branch doesn't exist locally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git show-ref --verify --quiet refs/heads/<feature-branch-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
- If branch doesn't exist, it might be in the worktree only
|
||||||
|
- Attempt to reference it from worktree:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get the commit SHA from the feature worktree
|
||||||
|
FEATURE_SHA=$(git rev-parse <feature-branch-name> 2>/dev/null || \
|
||||||
|
cd <feature-worktree-path> && git rev-parse HEAD)
|
||||||
|
git merge --ff-only $FEATURE_SHA
|
||||||
|
```
|
||||||
|
|
||||||
|
- If still fails, this indicates history divergence:
|
||||||
|
|
||||||
|
```text
|
||||||
|
❌ Error: Cannot fast-forward main to feature branch.
|
||||||
|
|
||||||
|
This usually means:
|
||||||
|
1. The feature branch was not properly rebased onto main, OR
|
||||||
|
2. Main has new commits since the rebase started
|
||||||
|
|
||||||
|
Please choose an option:
|
||||||
|
1. Return to feature worktree and rebase again
|
||||||
|
2. Inspect the branches with: git log --oneline --graph --all
|
||||||
|
3. Abort this operation
|
||||||
|
|
||||||
|
What would you like to do?
|
||||||
|
```
|
||||||
|
|
||||||
|
- Wait for user decision and follow their instruction
|
||||||
|
- **Critical:** Do NOT attempt `git merge` without `--ff-only` flag
|
||||||
|
|
||||||
|
### 5. Delete Feature Worktree
|
||||||
|
|
||||||
|
**Goal:** Clean up the feature worktree directory after successful merge
|
||||||
|
|
||||||
|
1. **Store the feature worktree path:**
|
||||||
|
- Before switching to main worktree in step 3, store the feature worktree path
|
||||||
|
- Example: `FEATURE_WORKTREE_PATH=$(pwd)`
|
||||||
|
|
||||||
|
2. **Attempt to remove worktree:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git worktree remove <feature-worktree-path>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Handle removal outcomes:**
|
||||||
|
|
||||||
|
**Success (exit code 0):**
|
||||||
|
- Worktree removed successfully, proceed to step 6
|
||||||
|
|
||||||
|
**Failure - Untracked files (common):**
|
||||||
|
- Git will refuse with error like: "fatal: '<path>' contains modified or untracked files, use --force to delete it"
|
||||||
|
- Check for untracked files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd <feature-worktree-path>
|
||||||
|
git status --porcelain | grep '^??'
|
||||||
|
```
|
||||||
|
|
||||||
|
- If untracked files exist, prompt user:
|
||||||
|
|
||||||
|
```text
|
||||||
|
⚠️ Feature worktree contains untracked files:
|
||||||
|
<list of untracked files>
|
||||||
|
|
||||||
|
How would you like to proceed?
|
||||||
|
1. Create a new commit with these files
|
||||||
|
2. Amend the last commit to include these files
|
||||||
|
3. Force-delete the worktree (rm -rf) - files will be lost
|
||||||
|
4. Abort - leave worktree intact for manual cleanup
|
||||||
|
|
||||||
|
Enter your choice (1-4):
|
||||||
|
```
|
||||||
|
|
||||||
|
- Wait for user input and proceed accordingly:
|
||||||
|
|
||||||
|
**Option 1 - Create new commit:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd <feature-worktree-path>
|
||||||
|
git add -A
|
||||||
|
git commit -m "chore: add remaining untracked files"
|
||||||
|
# Now need to update main again with this commit
|
||||||
|
cd <main-worktree-path>
|
||||||
|
git merge --ff-only <feature-branch-name>
|
||||||
|
git worktree remove <feature-worktree-path>
|
||||||
|
git branch -d <feature-branch-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2 - Amend last commit:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd <feature-worktree-path>
|
||||||
|
git add -A
|
||||||
|
git commit --amend --no-edit
|
||||||
|
# Now need to update main again with amended commit
|
||||||
|
cd <main-worktree-path>
|
||||||
|
# Use reset since history was rewritten
|
||||||
|
FEATURE_SHA=$(cd <feature-worktree-path> && git rev-parse HEAD)
|
||||||
|
git reset --hard $FEATURE_SHA
|
||||||
|
git worktree remove <feature-worktree-path>
|
||||||
|
git branch -d <feature-branch-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 3 - Force delete:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm -rf <feature-worktree-path>
|
||||||
|
git worktree prune
|
||||||
|
```
|
||||||
|
|
||||||
|
- Warn user: "⚠️ Untracked files have been permanently deleted."
|
||||||
|
|
||||||
|
**Option 4 - Abort:**
|
||||||
|
- Leave worktree intact
|
||||||
|
- Inform user in output that worktree still exists
|
||||||
|
- User can manually investigate and decide later
|
||||||
|
|
||||||
|
**Failure - Other errors:**
|
||||||
|
- Display git error message
|
||||||
|
- Offer force delete with `git worktree remove --force` or manual `rm -rf`
|
||||||
|
- Ask user for guidance
|
||||||
|
|
||||||
|
4. **Verify worktree removal:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git worktree list
|
||||||
|
```
|
||||||
|
|
||||||
|
- Ensure feature worktree no longer appears in list
|
||||||
|
- If it still appears, run `git worktree prune`
|
||||||
|
|
||||||
|
### 6. Delete Feature Branch
|
||||||
|
|
||||||
|
**Goal:** Clean up the feature branch after successful merge
|
||||||
|
|
||||||
|
1. **Delete the local feature branch:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git branch -d <feature-branch-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Handle deletion outcomes:**
|
||||||
|
|
||||||
|
**Success:**
|
||||||
|
- Branch deleted successfully, proceed to step 7
|
||||||
|
|
||||||
|
**Failure (branch not fully merged):**
|
||||||
|
- This shouldn't happen since we just fast-forwarded
|
||||||
|
- If it does, check if there's a remote tracking branch issue
|
||||||
|
- Offer force deletion only if user confirms:
|
||||||
|
|
||||||
|
```text
|
||||||
|
⚠️ Git reports the branch is not fully merged.
|
||||||
|
This is unexpected after a successful fast-forward.
|
||||||
|
|
||||||
|
Use `git branch -D <feature-branch-name>` to force delete? (y/n)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Output Format
|
||||||
|
|
||||||
|
Provide clear summary of the completed operation:
|
||||||
|
|
||||||
|
```text
|
||||||
|
✅ Successfully merged feature branch '<feature-branch-name>' into main
|
||||||
|
|
||||||
|
Actions completed:
|
||||||
|
1. ✅ Rebased <feature-branch-name> onto main
|
||||||
|
2. ✅ Fast-forwarded main to <feature-branch-name>
|
||||||
|
3. ✅ Removed feature worktree <feature-worktree-path>
|
||||||
|
4. ✅ Deleted local branch <feature-branch-name>
|
||||||
|
|
||||||
|
Current state:
|
||||||
|
📍 Location: <main-worktree-path>
|
||||||
|
🌿 Branch: main
|
||||||
|
📝 Commits: <commit-summary>
|
||||||
|
|
||||||
|
✨ Linear history preserved — no merge commits created!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** If worktree removal was skipped (user chose option 4), modify output to say:
|
||||||
|
|
||||||
|
```text
|
||||||
|
⚠️ Feature worktree was NOT removed (user choice)
|
||||||
|
📁 Worktree location: <feature-worktree-path>
|
||||||
|
Manual cleanup: git worktree remove <feature-worktree-path> --force
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Let auto-detect find main worktree
|
||||||
|
/tools:finish-worktree
|
||||||
|
|
||||||
|
# Specify main worktree path explicitly
|
||||||
|
/tools:finish-worktree ~/projects/my-project
|
||||||
|
|
||||||
|
# From within a feature worktree
|
||||||
|
cd ~/projects/my-project-feature
|
||||||
|
/tools:finish-worktree ../my-project
|
||||||
|
```
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- **NEVER create merge commits** — linear history is paramount
|
||||||
|
- **NEVER skip commits** during rebase
|
||||||
|
- **NEVER alter commit messages** without explicit user permission
|
||||||
|
- **NEVER use `git merge` without `--ff-only` flag**
|
||||||
|
- **ALWAYS verify working tree is clean** before operations
|
||||||
|
- **ALWAYS use rebase for history integration**
|
||||||
|
- **ALWAYS validate fast-forward is possible** before merging
|
||||||
|
|
||||||
|
## Edge Cases
|
||||||
|
|
||||||
|
### 1. Rebase Conflicts
|
||||||
|
|
||||||
|
**Scenario:** Feature branch conflicts with main during rebase
|
||||||
|
|
||||||
|
**Handling:**
|
||||||
|
|
||||||
|
- Stop and list conflicting files
|
||||||
|
- Provide clear instructions for resolution
|
||||||
|
- Wait for user to resolve and continue
|
||||||
|
- User calls `/tools:finish-worktree` again after resolving conflicts
|
||||||
|
- Command detects rebase in progress and resumes (doesn't restart)
|
||||||
|
- Never automatically skip or abort rebase
|
||||||
|
|
||||||
|
### 2. Resuming After Rebase Conflicts
|
||||||
|
|
||||||
|
**Scenario:** User calls `/tools:finish-worktree` again after resolving rebase conflicts
|
||||||
|
|
||||||
|
**Handling:**
|
||||||
|
|
||||||
|
- Detect rebase in progress by checking for `.git/rebase-merge` or `.git/rebase-apply`
|
||||||
|
- Skip fetch and rebase initiation steps
|
||||||
|
- Jump directly to conflict handling (Step 2.4)
|
||||||
|
- Check current rebase state:
|
||||||
|
- If conflicts still exist → show conflict resolution instructions
|
||||||
|
- If conflicts resolved but not continued → instruct to run `git rebase --continue`
|
||||||
|
- If rebase completed → proceed to step 3 (Navigate to Main Worktree)
|
||||||
|
- This allows iterative conflict resolution across multiple command invocations
|
||||||
|
- Prevents "rebase already in progress" errors
|
||||||
|
|
||||||
|
**Implementation notes:**
|
||||||
|
|
||||||
|
- Use `test -d .git/rebase-merge || test -d .git/rebase-apply` to detect
|
||||||
|
- Use `git status` to determine current state within rebase
|
||||||
|
- Never attempt `git rebase main` if rebase already in progress
|
||||||
|
|
||||||
|
### 3. No Main Worktree Found
|
||||||
|
|
||||||
|
**Scenario:** Auto-detection cannot find main worktree
|
||||||
|
|
||||||
|
**Handling:**
|
||||||
|
|
||||||
|
- Prompt user for path
|
||||||
|
- Validate provided path thoroughly
|
||||||
|
- Show `git worktree list` output to help user
|
||||||
|
- Accept either absolute or relative paths
|
||||||
|
|
||||||
|
### 4. Fast-Forward Impossible
|
||||||
|
|
||||||
|
**Scenario:** Main has diverged from feature branch
|
||||||
|
|
||||||
|
**Handling:**
|
||||||
|
|
||||||
|
- Show detailed error message
|
||||||
|
- Offer three options: re-rebase, inspect history, abort
|
||||||
|
- Never attempt regular merge
|
||||||
|
- Explain why fast-forward failed
|
||||||
|
- Guide user through resolution
|
||||||
|
|
||||||
|
### 5. Working Tree Not Clean
|
||||||
|
|
||||||
|
**Scenario:** Uncommitted changes in current or main worktree
|
||||||
|
|
||||||
|
**Handling:**
|
||||||
|
|
||||||
|
- Detect before any operations begin
|
||||||
|
- Show clear error message
|
||||||
|
- Suggest `git stash` or committing changes
|
||||||
|
- Exit without making any changes
|
||||||
|
|
||||||
|
### 6. Multiple Main Worktrees
|
||||||
|
|
||||||
|
**Scenario:** User has multiple worktrees checked out to main (unusual but possible)
|
||||||
|
|
||||||
|
**Handling:**
|
||||||
|
|
||||||
|
- List all candidates
|
||||||
|
- Ask user to specify which one to use
|
||||||
|
- Validate selection before proceeding
|
||||||
|
|
||||||
|
### 7. Feature Branch Doesn't Exist Locally in Main Worktree
|
||||||
|
|
||||||
|
**Scenario:** Feature branch only exists in its worktree, not visible from main worktree
|
||||||
|
|
||||||
|
**Handling:**
|
||||||
|
|
||||||
|
- Attempt to resolve branch reference from feature worktree
|
||||||
|
- Get commit SHA and use that for fast-forward merge
|
||||||
|
- If that fails, explain that branch needs to be visible
|
||||||
|
- Ask user to investigate why branch isn't visible in main worktree
|
||||||
|
|
||||||
|
### 8. Worktree Contains Untracked Files
|
||||||
|
|
||||||
|
**Scenario:** Git refuses to delete worktree because it contains untracked files
|
||||||
|
|
||||||
|
**Handling:**
|
||||||
|
|
||||||
|
- Detect untracked files with `git status --porcelain | grep '^??'`
|
||||||
|
- List all untracked files to user
|
||||||
|
- Provide four clear options:
|
||||||
|
1. Create new commit with untracked files (then update main)
|
||||||
|
2. Amend last commit to include untracked files (then update main)
|
||||||
|
3. Force-delete worktree with `rm -rf` (permanent data loss)
|
||||||
|
4. Abort and leave worktree for manual inspection
|
||||||
|
- Wait for explicit user choice
|
||||||
|
- Execute chosen option carefully
|
||||||
|
- If option 1 or 2: Must update main branch again since new commits were added, then delete the branch
|
||||||
|
- If option 3: Warn about permanent deletion before executing
|
||||||
|
- If option 4: Document worktree location in output for later cleanup
|
||||||
|
- Never automatically force-delete without user permission
|
||||||
|
|
||||||
|
**Important:** Options 1 and 2 require going back to update the main branch since new commits were created after the initial fast-forward merge, then removing the worktree, then deleting the branch. This maintains the guarantee of linear history.
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
### Git Worktree Primer
|
||||||
|
|
||||||
|
- **Worktree:** Separate working directory for the same repository
|
||||||
|
- **Main worktree:** Original checkout (typically on main branch)
|
||||||
|
- **Linked worktree:** Additional checkouts for feature branches
|
||||||
|
|
||||||
|
### Fast-Forward Merge Requirement
|
||||||
|
|
||||||
|
A fast-forward merge is only possible when:
|
||||||
|
|
||||||
|
- Target branch (main) is an ancestor of source branch (feature)
|
||||||
|
- No divergent commits exist
|
||||||
|
- History is linear after rebase
|
||||||
|
|
||||||
|
### Why Linear History Matters
|
||||||
|
|
||||||
|
- Easier to understand project evolution
|
||||||
|
- Simpler to bisect and revert
|
||||||
|
- Cleaner git log output
|
||||||
|
- No "merge commit noise"
|
||||||
|
|
||||||
|
### Command Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all worktrees
|
||||||
|
git worktree list
|
||||||
|
|
||||||
|
# Show current branch
|
||||||
|
git branch --show-current
|
||||||
|
|
||||||
|
# Rebase onto main
|
||||||
|
git rebase main
|
||||||
|
|
||||||
|
# Fast-forward only merge
|
||||||
|
git merge --ff-only <branch>
|
||||||
|
|
||||||
|
# Delete merged branch
|
||||||
|
git branch -d <branch>
|
||||||
|
|
||||||
|
# Force delete branch
|
||||||
|
git branch -D <branch>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "fatal: Needed a single revision"
|
||||||
|
|
||||||
|
**Cause:** Branch name is ambiguous or doesn't exist in current context
|
||||||
|
|
||||||
|
**Fix:** Use full commit SHA or ensure branch is visible in current worktree
|
||||||
|
|
||||||
|
### "fatal: Not possible to fast-forward, aborting"
|
||||||
|
|
||||||
|
**Cause:** Main has commits not in feature branch (history diverged)
|
||||||
|
|
||||||
|
**Fix:** Return to feature worktree, pull latest main, rebase again
|
||||||
|
|
||||||
|
### "error: Cannot delete branch (not fully merged)"
|
||||||
|
|
||||||
|
**Cause:** Git detects commits in feature branch not in main
|
||||||
|
|
||||||
|
**Fix:** Verify fast-forward actually succeeded with `git log --oneline --graph`
|
||||||
|
|
||||||
|
## Related Commands
|
||||||
|
|
||||||
|
- `git worktree list` — View all worktrees
|
||||||
|
- `git worktree remove <path>` — Remove worktree after merge
|
||||||
|
- `git rebase --abort` — Cancel rebase if needed
|
||||||
|
- `git reflog` — Recover from mistakes
|
||||||
16
hooks/hooks.json
Normal file
16
hooks/hooks.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"PostToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Write|Edit",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/eslint-fix.cjs",
|
||||||
|
"timeout": 30
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
136
hooks/scripts/eslint-fix.cjs
Executable file
136
hooks/scripts/eslint-fix.cjs
Executable file
@@ -0,0 +1,136 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
const { readFileSync } = require('fs');
|
||||||
|
const { basename, relative } = require('path');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} HookInput
|
||||||
|
* @property {string} [cwd]
|
||||||
|
* @property {string} [file_path]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} ESLintMessage
|
||||||
|
* @property {number} line
|
||||||
|
* @property {number} column
|
||||||
|
* @property {string} message
|
||||||
|
* @property {number} severity
|
||||||
|
* @property {string} [ruleId]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} ESLintResult
|
||||||
|
* @property {ESLintMessage[]} [messages]
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Read hook input from stdin
|
||||||
|
/** @type {HookInput} */
|
||||||
|
let input;
|
||||||
|
try {
|
||||||
|
input = JSON.parse(readFileSync(0, 'utf-8'));
|
||||||
|
} catch {
|
||||||
|
// Invalid JSON, skip gracefully
|
||||||
|
console.log(JSON.stringify({ continue: true }));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { cwd, file_path } = input;
|
||||||
|
|
||||||
|
// Validate required file_path field
|
||||||
|
if (!file_path) {
|
||||||
|
console.log(JSON.stringify({ continue: true }));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only process JS/TS files (including .d.ts declaration files)
|
||||||
|
const JS_TS_EXTENSIONS = /\.(ts|tsx|js|jsx|cjs|mjs|mts|cts)$/i;
|
||||||
|
if (!JS_TS_EXTENSIONS.test(file_path)) {
|
||||||
|
console.log(JSON.stringify({ continue: true }));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if eslint is available
|
||||||
|
try {
|
||||||
|
execSync('npx eslint --version', {
|
||||||
|
cwd,
|
||||||
|
stdio: 'ignore',
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// ESLint not available, skip silently
|
||||||
|
console.log(JSON.stringify({ continue: true }));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run eslint --fix with JSON output
|
||||||
|
// Use relative path to avoid ESLint base path issues
|
||||||
|
const relativePath = cwd ? relative(cwd, file_path) : file_path;
|
||||||
|
/** @type {string} */
|
||||||
|
let output;
|
||||||
|
try {
|
||||||
|
output = execSync(`npx eslint --fix --format json "${relativePath}"`, {
|
||||||
|
cwd,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
stdio: 'pipe',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// ESLint exits with non-zero when errors exist
|
||||||
|
const stdout =
|
||||||
|
error && typeof error === 'object' && 'stdout' in error
|
||||||
|
? error.stdout
|
||||||
|
: null;
|
||||||
|
output = (typeof stdout === 'string' ? stdout : null) || '[]';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse results and extract remaining errors
|
||||||
|
/** @type {ESLintResult[]} */
|
||||||
|
let results;
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(output);
|
||||||
|
results = Array.isArray(parsed) ? parsed : [];
|
||||||
|
} catch {
|
||||||
|
// Invalid JSON, assume no errors
|
||||||
|
results = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const errors =
|
||||||
|
results[0]?.messages?.filter(
|
||||||
|
(/** @type {ESLintMessage} */ msg) => msg.severity === 2,
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
// Format and report errors if any exist
|
||||||
|
if (errors.length > 0) {
|
||||||
|
const fileName = basename(file_path);
|
||||||
|
const MAX_ERRORS_TO_SHOW = 5;
|
||||||
|
|
||||||
|
let errorList;
|
||||||
|
if (errors.length > MAX_ERRORS_TO_SHOW) {
|
||||||
|
const shown = errors.slice(0, MAX_ERRORS_TO_SHOW);
|
||||||
|
errorList = shown
|
||||||
|
.map(
|
||||||
|
(/** @type {ESLintMessage} */ e) =>
|
||||||
|
`Line ${e.line}:${e.column}: ${e.message} (${e.ruleId || 'unknown'})`,
|
||||||
|
)
|
||||||
|
.join('\n');
|
||||||
|
errorList += `\n...and ${errors.length - MAX_ERRORS_TO_SHOW} more error(s)`;
|
||||||
|
} else {
|
||||||
|
errorList = errors
|
||||||
|
.map(
|
||||||
|
(/** @type {ESLintMessage} */ e) =>
|
||||||
|
`Line ${e.line}:${e.column}: ${e.message} (${e.ruleId || 'unknown'})`,
|
||||||
|
)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = `ESLint found ${errors.length} error(s) in ${fileName}:\n${errorList}`;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
JSON.stringify({
|
||||||
|
continue: true,
|
||||||
|
systemMessage: message,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify({ continue: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
69
plugin.lock.json
Normal file
69
plugin.lock.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||||
|
"pluginId": "gh:boneskull/claude-plugins:plugins/tools",
|
||||||
|
"normalized": {
|
||||||
|
"repo": null,
|
||||||
|
"ref": "refs/tags/v20251128.0",
|
||||||
|
"commit": "53a680f600566f64eb5581844f29419498cdf69f",
|
||||||
|
"treeHash": "db736367145f5979ce54df00506b1452afa4ceb524adfdb66ff4e695bb75b78e",
|
||||||
|
"generatedAt": "2025-11-28T10:14:19.561642Z",
|
||||||
|
"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": "tools",
|
||||||
|
"description": "Skills and documentation for various CLI, development, and language-specific tools",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "README.md",
|
||||||
|
"sha256": "1a22d112b27481def9e8cf7539b81abd7c86c753e400c44e9329f59032fb901f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/hooks.json",
|
||||||
|
"sha256": "66be7bf5ca4b6cf8c671265f1fc092cd2212a65a2ae8b4b29b3438aed630fd13"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/scripts/eslint-fix.cjs",
|
||||||
|
"sha256": "0d2a32f1d771f2181a1fe8d36cee4446abd6c8ca815a8cbf21930ad555c39a43"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".claude-plugin/plugin.json",
|
||||||
|
"sha256": "04210789387f1263ac33d89be378ef0f793f30bfaf2818c5054564e21d7dbe45"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "commands/apply-template.md",
|
||||||
|
"sha256": "837114dcfe6cba188c089acf3f088d775b33d5da303a4f50776999a7af70fd3e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "commands/finish-worktree.md",
|
||||||
|
"sha256": "b4218be16032004fab9852d709b6ace3a584dcab9581b1afe1180b81d835e4f9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/skill-rules.json",
|
||||||
|
"sha256": "5e8d5b303d83c37c53408491e112a1d510cfd63d2aa8cb6a4c23732ca56931e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/git-directory-management/SKILL.md",
|
||||||
|
"sha256": "08c08bc36f03fac8b2be2bf02b3f7e05c35ea2e236617a93723be65fb6587b43"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/git-commit-messages/SKILL.md",
|
||||||
|
"sha256": "f10ba43aa95f73e9624971bffb305f86fa2c8ab34dff3b30f6f90e0965ea0361"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dirSha256": "db736367145f5979ce54df00506b1452afa4ceb524adfdb66ff4e695bb75b78e"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"scannedAt": null,
|
||||||
|
"scannerVersion": null,
|
||||||
|
"flags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
145
skills/git-commit-messages/SKILL.md
Normal file
145
skills/git-commit-messages/SKILL.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
---
|
||||||
|
name: git-commit-messages
|
||||||
|
description: Format git commit messages correctly, avoiding HEREDOC syntax issues in favor of multiline strings
|
||||||
|
---
|
||||||
|
|
||||||
|
# Git Commit Message Formatting
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
Use this skill when creating git commits with the Bash tool, especially for commits with:
|
||||||
|
|
||||||
|
- Multi-line messages
|
||||||
|
- Body text or explanations
|
||||||
|
- Co-authored-by footers
|
||||||
|
- Claude Code attribution
|
||||||
|
|
||||||
|
## Core Principle
|
||||||
|
|
||||||
|
**Avoid HEREDOC syntax** - Use `-m` with multiline strings instead.
|
||||||
|
|
||||||
|
## Pattern to Follow
|
||||||
|
|
||||||
|
### ✅ DO - Use multiline string with -m
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit -m "feat(github): add github plugin
|
||||||
|
|
||||||
|
This adds comprehensive GitHub workflow support including
|
||||||
|
commands for PR management and review workflows.
|
||||||
|
|
||||||
|
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||||
|
|
||||||
|
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key points:**
|
||||||
|
|
||||||
|
- Single `-m` flag with entire message in quotes
|
||||||
|
- Newlines preserved within the quoted string
|
||||||
|
- No special syntax or escaping needed
|
||||||
|
- Works reliably across all environments
|
||||||
|
|
||||||
|
### ❌ DON'T - Use HEREDOC syntax
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# This FAILS in Claude Code's Bash tool
|
||||||
|
git commit -m "$(cat <<'EOF'
|
||||||
|
feat(github): add github plugin
|
||||||
|
|
||||||
|
This adds comprehensive GitHub workflow support.
|
||||||
|
|
||||||
|
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||||
|
|
||||||
|
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this fails:**
|
||||||
|
|
||||||
|
- HEREDOC syntax has shell interpretation issues
|
||||||
|
- Fails in certain execution contexts
|
||||||
|
- More complex than necessary
|
||||||
|
- Unreliable across environments
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Simple commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit -m "fix: resolve path configuration issue"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commit with body
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit -m "feat: add progressive disclosure pattern
|
||||||
|
|
||||||
|
Skills now support references/ subdirectory for
|
||||||
|
detailed documentation that loads on-demand."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commit with footer
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit -m "chore: update dependencies
|
||||||
|
|
||||||
|
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||||
|
|
||||||
|
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Full conventional commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit -m "feat(tools): add git commit message skill
|
||||||
|
|
||||||
|
This skill teaches Claude to format git commit messages
|
||||||
|
using multiline strings instead of HEREDOC syntax, which
|
||||||
|
fails in certain shell environments.
|
||||||
|
|
||||||
|
The pattern uses a single -m flag with the entire message
|
||||||
|
in quotes, preserving newlines naturally.
|
||||||
|
|
||||||
|
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||||
|
|
||||||
|
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why It Matters
|
||||||
|
|
||||||
|
Using `-m` with multiline strings is:
|
||||||
|
|
||||||
|
- **Reliable:** Works consistently in all environments
|
||||||
|
- **Simple:** No complex shell syntax needed
|
||||||
|
- **Portable:** Standard git behavior
|
||||||
|
- **Direct:** Git handles newlines correctly
|
||||||
|
|
||||||
|
HEREDOC syntax causes issues because:
|
||||||
|
|
||||||
|
- Shell interpretation varies by environment
|
||||||
|
- Execution context in Claude Code differs from terminal
|
||||||
|
- Quote handling becomes complex
|
||||||
|
- Unnecessary complexity for the task
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
**Template for commits with Claude Code attribution:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit -m "<type>[optional scope]: <description>
|
||||||
|
|
||||||
|
[optional body]
|
||||||
|
|
||||||
|
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||||
|
|
||||||
|
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Remember:**
|
||||||
|
|
||||||
|
- One `-m` flag
|
||||||
|
- Entire message in double quotes
|
||||||
|
- Newlines work naturally
|
||||||
|
- No HEREDOC needed
|
||||||
209
skills/git-directory-management/SKILL.md
Normal file
209
skills/git-directory-management/SKILL.md
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
---
|
||||||
|
name: git-directory-management
|
||||||
|
description: Manage git-tracked directories correctly - never create .gitkeep files in directories that will immediately contain tracked files
|
||||||
|
---
|
||||||
|
|
||||||
|
# Git Directory Management
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
Use this skill when creating new directories in a git repository, especially when:
|
||||||
|
|
||||||
|
- Setting up project structure
|
||||||
|
- Creating plugin/package directories
|
||||||
|
- Organizing code into new folders
|
||||||
|
- Adding configuration directories
|
||||||
|
|
||||||
|
## Core Principle
|
||||||
|
|
||||||
|
**Never create `.gitkeep` files in directories you're about to populate with tracked files.**
|
||||||
|
|
||||||
|
`.gitkeep` is ONLY for keeping truly empty directories in version control.
|
||||||
|
|
||||||
|
## Pattern to Follow
|
||||||
|
|
||||||
|
### ✅ DO - Add actual files directly
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create directory and add actual file
|
||||||
|
mkdir -p plugins/new-plugin/skills
|
||||||
|
# Now add your actual files
|
||||||
|
# (Write SKILL.md, plugin.json, etc.)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key points:**
|
||||||
|
|
||||||
|
- Git automatically tracks directories when they contain files
|
||||||
|
- Empty directory + file creation can happen in one step
|
||||||
|
- No `.gitkeep` needed - the real files track the directory
|
||||||
|
|
||||||
|
### ❌ DON'T - Create .gitkeep then immediately add files
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# This is wasteful and wrong
|
||||||
|
mkdir -p plugins/new-plugin/skills
|
||||||
|
touch plugins/new-plugin/skills/.gitkeep # ❌ Unnecessary!
|
||||||
|
git add plugins/new-plugin/skills/.gitkeep
|
||||||
|
git commit -m "Add empty directory"
|
||||||
|
|
||||||
|
# Then immediately add real files
|
||||||
|
# (Write SKILL.md)
|
||||||
|
git add plugins/new-plugin/skills/
|
||||||
|
git commit -m "Add actual skill"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this is wrong:**
|
||||||
|
|
||||||
|
- `.gitkeep` serves no purpose if files are coming
|
||||||
|
- Creates unnecessary commits
|
||||||
|
- Clutters directory with placeholder file
|
||||||
|
- Extra file to maintain/remove later
|
||||||
|
|
||||||
|
## When to Use .gitkeep
|
||||||
|
|
||||||
|
`.gitkeep` is appropriate ONLY when:
|
||||||
|
|
||||||
|
1. **The directory must exist but remain empty**
|
||||||
|
2. **The empty directory is required for the application to function**
|
||||||
|
3. **No files will be added to the directory immediately**
|
||||||
|
|
||||||
|
**Valid use case example:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Application requires logs/ directory to exist on startup
|
||||||
|
mkdir -p logs
|
||||||
|
touch logs/.gitkeep
|
||||||
|
git add logs/.gitkeep
|
||||||
|
git commit -m "chore: add logs directory for runtime output"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this is valid:**
|
||||||
|
|
||||||
|
- Directory must exist before application runs
|
||||||
|
- Directory will be populated at runtime (not in version control)
|
||||||
|
- `.gitkeep` ensures the empty directory is tracked
|
||||||
|
|
||||||
|
## Common Scenarios
|
||||||
|
|
||||||
|
### Scenario: Creating a new plugin structure
|
||||||
|
|
||||||
|
✅ **DO:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p plugins/new-plugin/{skills,commands}
|
||||||
|
# Then immediately create your files:
|
||||||
|
# Write plugins/new-plugin/.claude-plugin/plugin.json
|
||||||
|
# Write plugins/new-plugin/skills/skill-name/SKILL.md
|
||||||
|
# Write plugins/new-plugin/README.md
|
||||||
|
# Add all files in one commit
|
||||||
|
git add plugins/new-plugin/
|
||||||
|
git commit -m "feat: add new-plugin"
|
||||||
|
```
|
||||||
|
|
||||||
|
❌ **DON'T:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p plugins/new-plugin/skills
|
||||||
|
touch plugins/new-plugin/skills/.gitkeep # ❌ Wrong!
|
||||||
|
# Then add real files later
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scenario: Creating empty directories for runtime
|
||||||
|
|
||||||
|
✅ **DO:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p tmp/cache
|
||||||
|
touch tmp/cache/.gitkeep
|
||||||
|
git add tmp/cache/.gitkeep
|
||||||
|
git commit -m "chore: add cache directory for runtime"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this is correct:** Cache directory must exist but contents are not tracked.
|
||||||
|
|
||||||
|
### Scenario: Setting up tool configuration directories
|
||||||
|
|
||||||
|
✅ **DO:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p .config/tool
|
||||||
|
# Immediately add configuration file
|
||||||
|
# Write .config/tool/config.json
|
||||||
|
git add .config/tool/config.json
|
||||||
|
git commit -m "feat: add tool configuration"
|
||||||
|
```
|
||||||
|
|
||||||
|
❌ **DON'T:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p .config/tool
|
||||||
|
touch .config/tool/.gitkeep # ❌ Wrong! You're about to add config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Decision Tree
|
||||||
|
|
||||||
|
```text
|
||||||
|
Creating a new directory?
|
||||||
|
│
|
||||||
|
├─ Will you add tracked files immediately?
|
||||||
|
│ └─ YES → No .gitkeep needed, just add the files
|
||||||
|
│
|
||||||
|
└─ Will the directory stay empty in version control?
|
||||||
|
│
|
||||||
|
├─ YES, and it must exist → Use .gitkeep
|
||||||
|
│
|
||||||
|
└─ NO, files coming later → Wait until files exist, then commit
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why It Matters
|
||||||
|
|
||||||
|
**Benefits of not using .gitkeep unnecessarily:**
|
||||||
|
|
||||||
|
- Cleaner repository (fewer placeholder files)
|
||||||
|
- Fewer commits (one commit with actual content)
|
||||||
|
- No cleanup needed later (no .gitkeep to remove)
|
||||||
|
- Clear intent (tracked files show directory purpose)
|
||||||
|
|
||||||
|
**Problems with unnecessary .gitkeep:**
|
||||||
|
|
||||||
|
- Clutters directories with meaningless files
|
||||||
|
- Creates confusing git history (empty → populated)
|
||||||
|
- Requires eventual cleanup
|
||||||
|
- Adds maintenance burden
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
**Rule of thumb:**
|
||||||
|
|
||||||
|
- **About to add files?** → No `.gitkeep`
|
||||||
|
- **Directory stays empty?** → Use `.gitkeep`
|
||||||
|
- **Not sure yet?** → Wait until files exist
|
||||||
|
|
||||||
|
**Remember:**
|
||||||
|
|
||||||
|
- Git tracks directories through their files
|
||||||
|
- `.gitkeep` is for truly empty directories
|
||||||
|
- One commit with actual content beats two commits (empty + populated)
|
||||||
|
- When in doubt, add the real files first
|
||||||
|
|
||||||
|
## Examples in This Repository
|
||||||
|
|
||||||
|
**Correct usage (runtime directories):**
|
||||||
|
|
||||||
|
- None currently - all directories contain tracked files
|
||||||
|
|
||||||
|
**What NOT to do:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ❌ DON'T create .gitkeep in plugins/tools/skills/
|
||||||
|
# This directory is meant to contain skills, not stay empty
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct approach:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ✅ Just add your skill directly
|
||||||
|
# Write plugins/tools/skills/new-skill/SKILL.md
|
||||||
|
git add plugins/tools/skills/new-skill/
|
||||||
|
git commit -m "feat(tools): add new-skill"
|
||||||
|
```
|
||||||
48
skills/skill-rules.json
Normal file
48
skills/skill-rules.json
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"description": "Skill activation rules for tools plugin",
|
||||||
|
"skills": {
|
||||||
|
"tools@boneskull-plugins:git-commit-messages": {
|
||||||
|
"type": "guardrail",
|
||||||
|
"enforcement": "suggest",
|
||||||
|
"priority": "high",
|
||||||
|
"description": "Format git commit messages correctly using multiline strings instead of HEREDOC",
|
||||||
|
"promptTriggers": {
|
||||||
|
"keywords": [
|
||||||
|
"git commit",
|
||||||
|
"commit message",
|
||||||
|
"create commit",
|
||||||
|
"commit changes",
|
||||||
|
"commit with message"
|
||||||
|
],
|
||||||
|
"intentPatterns": [
|
||||||
|
"(create|make|add|write).*commit",
|
||||||
|
"commit.*message",
|
||||||
|
"(how to|how do I).*commit"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tools@boneskull-plugins:git-directory-management": {
|
||||||
|
"type": "guardrail",
|
||||||
|
"enforcement": "block",
|
||||||
|
"priority": "critical",
|
||||||
|
"description": "Prevent unnecessary .gitkeep files in directories that will contain tracked files",
|
||||||
|
"promptTriggers": {
|
||||||
|
"keywords": [
|
||||||
|
".gitkeep",
|
||||||
|
"git keep",
|
||||||
|
"track empty directory",
|
||||||
|
"keep directory in git",
|
||||||
|
"empty directory git"
|
||||||
|
],
|
||||||
|
"intentPatterns": [
|
||||||
|
"create.*\\.gitkeep",
|
||||||
|
"add.*\\.gitkeep",
|
||||||
|
"(track|keep).*empty.*dir",
|
||||||
|
"git.*track.*directory"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"blockMessage": "⚠️ BLOCKED - .gitkeep Anti-Pattern Detected\n\n📋 REQUIRED ACTION:\n1. Use Skill tool: 'tools@boneskull-plugins:git-directory-management'\n2. Review when .gitkeep is actually needed (rarely!)\n3. Add actual files instead of .gitkeep\n\nReason: .gitkeep is ONLY for truly empty directories.\nIf you're about to add files, just add them directly.\n\n💡 TIP: Git tracks directories automatically when they contain files"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version": "1.0"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user