Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:01:42 +08:00
commit b2589e5029
10 changed files with 1440 additions and 0 deletions

View 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
View File

@@ -0,0 +1,3 @@
# tools
Skills and documentation for various CLI, development, and language-specific tools

222
commands/apply-template.md Normal file
View 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
View 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
View 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
View 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
View 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": []
}
}

View 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

View 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
View 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"
}