Initial commit
This commit is contained in:
12
.claude-plugin/plugin.json
Normal file
12
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "handbook-dotnet",
|
||||||
|
"description": ".NET development tools including automatic CSharpier formatting for C# files",
|
||||||
|
"version": "1.12.0",
|
||||||
|
"author": {
|
||||||
|
"name": "nikiforovall",
|
||||||
|
"url": "https://github.com/nikiforovall"
|
||||||
|
},
|
||||||
|
"hooks": [
|
||||||
|
"./hooks"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# handbook-dotnet
|
||||||
|
|
||||||
|
.NET development tools including automatic CSharpier formatting for C# files
|
||||||
51
hooks/format-csharp.sh
Normal file
51
hooks/format-csharp.sh
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Check if hook is disabled via environment variable
|
||||||
|
if [ "${CC_HANDBOOK_DOTNET_DISABLE_HOOKS:-false}" = "true" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read JSON input from stdin
|
||||||
|
input=$(cat)
|
||||||
|
|
||||||
|
# Extract file path from tool_input
|
||||||
|
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
|
||||||
|
|
||||||
|
# Exit silently if no file path
|
||||||
|
if [ -z "$file_path" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only process C# files (.cs, .CS, .csx, .CSX)
|
||||||
|
if [[ ! "$file_path" =~ \.(cs|CS|csx|CSX)$ ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if file exists
|
||||||
|
if [ ! -f "$file_path" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine which CSharpier command to use
|
||||||
|
# Try dotnet csharpier first (local or global dotnet tool)
|
||||||
|
if dotnet csharpier --version &>/dev/null; then
|
||||||
|
FORMATTER_CMD="dotnet csharpier"
|
||||||
|
# Fall back to csharpier executable (global standalone)
|
||||||
|
elif command -v csharpier &>/dev/null; then
|
||||||
|
FORMATTER_CMD="csharpier"
|
||||||
|
else
|
||||||
|
echo "Error: CSharpier not found. Install with: dotnet tool install -g csharpier" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run CSharpier with specified options
|
||||||
|
# --skip-validation: Skip validation for better performance
|
||||||
|
# --compilation-errors-as-warnings: Don't block on compilation errors
|
||||||
|
if ! $FORMATTER_CMD format "$file_path" --skip-validation --compilation-errors-as-warnings 2>&1; then
|
||||||
|
echo "Warning: CSharpier formatting failed for $file_path" >&2
|
||||||
|
exit 1 # Non-zero exit (not 2) - warns user but doesn't block Claude
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
17
hooks/hooks.json
Normal file
17
hooks/hooks.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"description": "Automatic CSharpier formatting for C# files",
|
||||||
|
"hooks": {
|
||||||
|
"PostToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Write|Edit",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/format-csharp.sh\"",
|
||||||
|
"timeout": 30
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
162
hooks/test-format-hook.sh
Normal file
162
hooks/test-format-hook.sh
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
# Test script for CSharpier formatter hook
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Get script directory and setup
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
HOOK_SCRIPT="$SCRIPT_DIR/format-csharp.sh"
|
||||||
|
TEST_DIR=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||||
|
|
||||||
|
# Test counters
|
||||||
|
PASSED=0
|
||||||
|
FAILED=0
|
||||||
|
SKIPPED=0
|
||||||
|
|
||||||
|
# Convert paths for Windows (MINGW/Git Bash)
|
||||||
|
to_path() {
|
||||||
|
if command -v cygpath &>/dev/null; then
|
||||||
|
cygpath -w "$1" | sed 's/\\/\\\\/g'
|
||||||
|
else
|
||||||
|
echo "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run a test
|
||||||
|
test_hook() {
|
||||||
|
local name="$1"
|
||||||
|
local json="$2"
|
||||||
|
local env="${3:-}"
|
||||||
|
|
||||||
|
echo -n " $name ... "
|
||||||
|
|
||||||
|
local exit_code=0
|
||||||
|
if [ -n "$env" ]; then
|
||||||
|
echo "$json" | $env bash "$HOOK_SCRIPT" &>/dev/null || exit_code=$?
|
||||||
|
else
|
||||||
|
echo "$json" | bash "$HOOK_SCRIPT" &>/dev/null || exit_code=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$exit_code" -eq 0 ]; then
|
||||||
|
echo "✓"
|
||||||
|
PASSED=$((PASSED + 1))
|
||||||
|
else
|
||||||
|
echo "✗ (exit code: $exit_code)"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "=============================="
|
||||||
|
echo "CSharpier Hook Test Suite"
|
||||||
|
echo "=============================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# File Type Filtering Tests
|
||||||
|
echo "File Type Filtering:"
|
||||||
|
echo "namespace Test{class X{}}" > "$TEST_DIR/test.cs"
|
||||||
|
test_hook "Processes .cs files" "{\"tool_input\":{\"file_path\":\"$(to_path "$TEST_DIR/test.cs")\"}}"
|
||||||
|
|
||||||
|
echo "class Test{}" > "$TEST_DIR/test.CS"
|
||||||
|
test_hook "Processes .CS files" "{\"tool_input\":{\"file_path\":\"$(to_path "$TEST_DIR/test.CS")\"}}"
|
||||||
|
|
||||||
|
echo "Console.WriteLine(\"test\");" > "$TEST_DIR/test.csx"
|
||||||
|
test_hook "Processes .csx files" "{\"tool_input\":{\"file_path\":\"$(to_path "$TEST_DIR/test.csx")\"}}"
|
||||||
|
|
||||||
|
echo "text" > "$TEST_DIR/test.txt"
|
||||||
|
test_hook "Ignores .txt files" "{\"tool_input\":{\"file_path\":\"$(to_path "$TEST_DIR/test.txt")\"}}"
|
||||||
|
|
||||||
|
echo "text" > "$TEST_DIR/test.cs.bak"
|
||||||
|
test_hook "Ignores .cs.bak files" "{\"tool_input\":{\"file_path\":\"$(to_path "$TEST_DIR/test.cs.bak")\"}}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Input Validation:"
|
||||||
|
test_hook "Handles missing file_path" "{\"tool_input\":{}}"
|
||||||
|
test_hook "Handles empty file_path" "{\"tool_input\":{\"file_path\":\"\"}}"
|
||||||
|
test_hook "Handles nonexistent file" "{\"tool_input\":{\"file_path\":\"$(to_path "$TEST_DIR/missing.cs")\"}}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Environment Variable:"
|
||||||
|
|
||||||
|
# Test that env var actually prevents formatting
|
||||||
|
cat > "$TEST_DIR/env-test.cs" << 'EOF'
|
||||||
|
class Example
|
||||||
|
{
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
BEFORE=$(cat "$TEST_DIR/env-test.cs")
|
||||||
|
ENV_PATH=$(to_path "$TEST_DIR/env-test.cs")
|
||||||
|
|
||||||
|
echo -n " Respects DISABLE_HOOKS=true ... "
|
||||||
|
echo "{\"tool_input\":{\"file_path\":\"$ENV_PATH\"}}" | CC_HANDBOOK_DOTNET_DISABLE_HOOKS=true bash "$HOOK_SCRIPT" &>/dev/null || true
|
||||||
|
AFTER=$(cat "$TEST_DIR/env-test.cs")
|
||||||
|
if [ "$BEFORE" = "$AFTER" ]; then
|
||||||
|
echo "✓"
|
||||||
|
PASSED=$((PASSED + 1))
|
||||||
|
else
|
||||||
|
echo "✗ (file was formatted despite env var)"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify formatting works without env var
|
||||||
|
cat > "$TEST_DIR/env-test2.cs" << 'EOF'
|
||||||
|
class Example
|
||||||
|
{
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
BEFORE=$(cat "$TEST_DIR/env-test2.cs")
|
||||||
|
ENV_PATH2=$(to_path "$TEST_DIR/env-test2.cs")
|
||||||
|
|
||||||
|
echo -n " Works by default (no env var) ... "
|
||||||
|
echo "{\"tool_input\":{\"file_path\":\"$ENV_PATH2\"}}" | bash "$HOOK_SCRIPT" &>/dev/null || true
|
||||||
|
AFTER=$(cat "$TEST_DIR/env-test2.cs")
|
||||||
|
if [ "$BEFORE" != "$AFTER" ]; then
|
||||||
|
echo "✓"
|
||||||
|
PASSED=$((PASSED + 1))
|
||||||
|
else
|
||||||
|
echo "✗ (file was not formatted)"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Code Formatting:"
|
||||||
|
|
||||||
|
# Check if CSharpier is available
|
||||||
|
if ! command -v csharpier &>/dev/null && ! dotnet csharpier --version &>/dev/null 2>&1; then
|
||||||
|
echo " Formats C# code ... ⊘ (CSharpier not installed)"
|
||||||
|
SKIPPED=$((SKIPPED + 1))
|
||||||
|
else
|
||||||
|
# Create badly formatted code
|
||||||
|
cat > "$TEST_DIR/format-test.cs" << 'EOF'
|
||||||
|
class Example
|
||||||
|
{
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
BEFORE=$(cat "$TEST_DIR/format-test.cs")
|
||||||
|
|
||||||
|
# Run formatter
|
||||||
|
FMT_PATH=$(to_path "$TEST_DIR/format-test.cs")
|
||||||
|
echo "{\"tool_input\":{\"file_path\":\"$FMT_PATH\"}}" | bash "$HOOK_SCRIPT" &>/dev/null || true
|
||||||
|
|
||||||
|
AFTER=$(cat "$TEST_DIR/format-test.cs")
|
||||||
|
|
||||||
|
echo -n " Formats C# code ... "
|
||||||
|
if [ "$BEFORE" != "$AFTER" ]; then
|
||||||
|
echo "✓"
|
||||||
|
PASSED=$((PASSED + 1))
|
||||||
|
else
|
||||||
|
echo "✗ (no formatting occurred)"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Results
|
||||||
|
echo ""
|
||||||
|
echo "=============================="
|
||||||
|
TOTAL=$((PASSED + FAILED + SKIPPED))
|
||||||
|
echo "Results: $PASSED/$TOTAL passed"
|
||||||
|
[ "$SKIPPED" -gt 0 ] && echo " $SKIPPED skipped"
|
||||||
|
echo "=============================="
|
||||||
|
|
||||||
|
[ "$FAILED" -eq 0 ]
|
||||||
53
plugin.lock.json
Normal file
53
plugin.lock.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||||
|
"pluginId": "gh:NikiforovAll/claude-code-rules:plugins/handbook-dotnet",
|
||||||
|
"normalized": {
|
||||||
|
"repo": null,
|
||||||
|
"ref": "refs/tags/v20251128.0",
|
||||||
|
"commit": "033b5e46483609dd68d996c73efea0e2b4c42a2b",
|
||||||
|
"treeHash": "d8373a4b2761bd13c70ebe7f0924157e1713e521f1d5718185e5852ae7c5e0f4",
|
||||||
|
"generatedAt": "2025-11-28T10:12:15.433975Z",
|
||||||
|
"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": "handbook-dotnet",
|
||||||
|
"description": ".NET development tools including automatic CSharpier formatting for C# files",
|
||||||
|
"version": "1.12.0"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "README.md",
|
||||||
|
"sha256": "4895a29f5670e4b603b954f63798cfb27a45c4ee2cda03c57932bb00c227c091"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/test-format-hook.sh",
|
||||||
|
"sha256": "c7f9b961f853224cd8a49a657841ea266dc1857eb2ddc0ae46d998c0a3db2182"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/hooks.json",
|
||||||
|
"sha256": "9cc521cb96780bc6c90e5cd01250b88016591c8f8bd9f38eb29a341189aa6d4c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/format-csharp.sh",
|
||||||
|
"sha256": "592dd618170b925257f42215953807c9bda5fb8c6486a9bb847804905676c2f6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".claude-plugin/plugin.json",
|
||||||
|
"sha256": "8869d80dc0ea9ae931df0200e1d7ee10f6171e54ceb67ae65da2cdae14384a5a"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dirSha256": "d8373a4b2761bd13c70ebe7f0924157e1713e521f1d5718185e5852ae7c5e0f4"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"scannedAt": null,
|
||||||
|
"scannerVersion": null,
|
||||||
|
"flags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user