diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..edc5e8c --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,35 @@ +name: Pre-commit + +on: + pull_request: + push: + branches: + - main + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Install gitleaks + run: | + go install github.com/zricethezav/gitleaks/v8@latest + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + + - name: Install pre-commit + run: python -m pip install pre-commit + + - name: Run pre-commit + run: pre-commit run --all-files diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5979fb5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,13 @@ +repos: + - repo: local + hooks: + - id: repo-hygiene + name: repo hygiene checks + entry: bash tools/pre_commit_checks.sh + language: system + pass_filenames: false + - id: public-readiness + name: public readiness audit + entry: bash tools/audit_public.sh + language: system + pass_filenames: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eb96d6..5936787 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -137,8 +137,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New plugin with two skills for browser testing against any URL - `cmux-browser` skill: interactive browser testing via `cmux browser` (visible split pane) - `playwright-testing` skill: headless browser testing via `playwright-cli` -- URL-agnostic: works with local dev servers, staging, and production sites (e.g. `https://mon.eurekabuilder.xyz`) -- Migrated from project-local skills in `trader-dashboard/.claude/skills/` +- URL-agnostic: works with local dev servers, staging, and production sites (e.g. `https://staging.example.com`) +- Migrated from project-local `.claude/skills/` content ### Technical Details - Marketplace: 1.10.0 -> 1.11.0 diff --git a/CLAUDE.md b/CLAUDE.md index 849216e..920a77b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,6 +40,6 @@ Follow [Semantic Versioning](https://semver.org/). Users update via `/plugin upd ### Testing ```bash -/plugin marketplace add /Users/amit/src/amitkot/claude-code-tools +/plugin marketplace add . /plugin install plugin-name@amitkot ``` diff --git a/README.md b/README.md index ac7f18b..d9f3349 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,19 @@ Extracted documentation optimized for AI context: 2. **GitButler CLI** (for GitButler skill) - Install from [gitbutlerapp/gitbutler](https://github.com/gitbutlerapp/gitbutler) +## Public Readiness + +Before changing repository visibility or publishing a release, run the local audit: + +```bash +pre-commit run --all-files +just audit-public +gitleaks detect --source . --redact --no-banner +git status --short --ignored +``` + +The public-readiness gate targets the current tree and future commits. Repository history is not rewritten. + ### Installing Plugins #### Option 1: Via Plugin Marketplace (Recommended) diff --git a/ai_docs/claude-code/plugins-documentation.md b/ai_docs/claude-code/plugins-documentation.md index 5e002f9..9239ba4 100644 --- a/ai_docs/claude-code/plugins-documentation.md +++ b/ai_docs/claude-code/plugins-documentation.md @@ -1322,7 +1322,7 @@ const customServer = createSdkMcpServer({ `https://api.weather.com/v1/current?q=${args.location}&units=${args.units}` ); const data = await response.json(); - + return { content: [{ type: "text", @@ -1440,7 +1440,7 @@ tool( // args is fully typed based on the schema // TypeScript knows: args.data.name is string, args.data.age is number, etc. console.log(`Processing ${args.data.name}'s data as ${args.format}`); - + // Your processing logic here return { content: [{ @@ -1464,7 +1464,7 @@ tool( async (args) => { try { const response = await fetch(args.endpoint); - + if (!response.ok) { return { content: [{ @@ -1473,7 +1473,7 @@ tool( }] }; } - + const data = await response.json(); return { content: [{ @@ -1547,20 +1547,20 @@ const apiGatewayServer = createSdkMcpServer({ openai: { baseUrl: "https://api.openai.com/v1", key: process.env.OPENAI_KEY }, slack: { baseUrl: "https://slack.com/api", key: process.env.SLACK_TOKEN } }; - + const { baseUrl, key } = config[args.service]; const url = new URL(`${baseUrl}${args.endpoint}`); - + if (args.query) { Object.entries(args.query).forEach(([k, v]) => url.searchParams.set(k, v)); } - + const response = await fetch(url, { method: args.method, headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" }, body: args.body ? JSON.stringify(args.body) : undefined }); - + const data = await response.json(); return { content: [{ @@ -1593,7 +1593,7 @@ const calculatorServer = createSdkMcpServer({ // Use a safe math evaluation library in production const result = eval(args.expression); // Example only! const formatted = Number(result).toFixed(args.precision); - + return { content: [{ type: "text", @@ -1622,7 +1622,7 @@ const calculatorServer = createSdkMcpServer({ async (args) => { const amount = args.principal * Math.pow(1 + args.rate / args.n, args.n * args.time); const interest = amount - args.principal; - + return { content: [{ type: "text", diff --git a/beads-orchestration/skills/execute-beads-plan/SKILL.md b/beads-orchestration/skills/execute-beads-plan/SKILL.md index 040e2f6..326f528 100644 --- a/beads-orchestration/skills/execute-beads-plan/SKILL.md +++ b/beads-orchestration/skills/execute-beads-plan/SKILL.md @@ -248,7 +248,7 @@ Execution complete. Results: - Tasks completed: N - Verification passed: N -- +- Implementation plan: [path] Source plan: [path] diff --git a/beads-orchestration/skills/execute-beads-plan/references/agent-coordination.md b/beads-orchestration/skills/execute-beads-plan/references/agent-coordination.md index 4e72ca2..74a9e07 100644 --- a/beads-orchestration/skills/execute-beads-plan/references/agent-coordination.md +++ b/beads-orchestration/skills/execute-beads-plan/references/agent-coordination.md @@ -24,7 +24,7 @@ When Task Agents complete work, they leave breadcrumbs: ```markdown -**Notes:** Implemented validation in `src/api/validate.rs:15-45`. +**Notes:** Implemented validation in `src/api/validate.rs:15-45`. Added ValidationError enum in `src/errors.rs:20-30`. Uses existing `EmailValidator` from `src/utils/validators.rs`. ``` @@ -68,7 +68,7 @@ To run agents in parallel, use single message with multiple Task tool calls: subagent_type: general-purpose - model: sonnet + model: sonnet prompt: [Task Agent prompt for br-105] diff --git a/beads-orchestration/skills/plans-to-beads/SKILL.md b/beads-orchestration/skills/plans-to-beads/SKILL.md index 78d5477..464e8fb 100644 --- a/beads-orchestration/skills/plans-to-beads/SKILL.md +++ b/beads-orchestration/skills/plans-to-beads/SKILL.md @@ -84,7 +84,7 @@ Analyze the plan deeply before creating any beads: ```bash br dep add ``` - + 4. **Link tasks to epic**: ```bash br dep add -t parent-child diff --git a/justfile b/justfile index f9301c8..1a8eb0f 100644 --- a/justfile +++ b/justfile @@ -3,3 +3,7 @@ # Run session recovery TUI session-recover *ARGS: cd session_recovery && uv run --with textual --with typer python __main__.py {{ARGS}} + +# Run public-readiness checks used by pre-commit and CI +audit-public: + bash tools/audit_public.sh diff --git a/marketplace-author/skills/marketplace-author/SKILL.md b/marketplace-author/skills/marketplace-author/SKILL.md index a016e67..f2a489a 100644 --- a/marketplace-author/skills/marketplace-author/SKILL.md +++ b/marketplace-author/skills/marketplace-author/SKILL.md @@ -11,7 +11,7 @@ Authoring skills in the [`amitkot/claude-code-tools`](https://github.com/amitkot ## CRITICAL: Repo path -The marketplace repo is at `/Users/amit/src/amitkot/claude-code-tools/`. Run all commands from there. If a different machine path is needed, ask once before guessing. +Run all commands from the repository root. If the current working directory is unclear, ask once before guessing. ## Workflow diff --git a/tools/audit_public.sh b/tools/audit_public.sh new file mode 100644 index 0000000..256bfa0 --- /dev/null +++ b/tools/audit_public.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! git rev-parse --show-toplevel >/dev/null 2>&1; then + echo "audit-public: must run inside a git repository" >&2 + exit 1 +fi + +cd "$(git rev-parse --show-toplevel)" + +status=0 + +check_pattern() { + local label="$1" + local pattern="$2" + + if git grep -n -I -E "$pattern" -- . \ + ':(exclude).git' \ + ':(exclude)tools/audit_public.sh' \ + ':(exclude).pre-commit-config.yaml' >/tmp/audit-public-matches.txt; then + echo "Blocked public-readiness pattern: ${label}" >&2 + cat /tmp/audit-public-matches.txt >&2 + status=1 + fi +} + +check_pattern "internal project/domain references" "eurekabuilder|mon\\.eurekabuilder\\.xyz|trader-dashboard" +check_pattern "absolute local repo path" "/Users/amit/src/amitkot/claude-code-tools" + +rm -f /tmp/audit-public-matches.txt + +if command -v gitleaks >/dev/null 2>&1; then + gitleaks detect --source . --redact --no-banner +else + echo "gitleaks not found; install it and run: gitleaks detect --source . --redact --no-banner" >&2 +fi + +exit "$status" diff --git a/tools/pre_commit_checks.sh b/tools/pre_commit_checks.sh new file mode 100644 index 0000000..463d56a --- /dev/null +++ b/tools/pre_commit_checks.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! git rev-parse --show-toplevel >/dev/null 2>&1; then + echo "pre-commit checks: must run inside a git repository" >&2 + exit 1 +fi + +cd "$(git rev-parse --show-toplevel)" + +status=0 +max_bytes=$((1024 * 1024)) + +files=() +while IFS= read -r -d '' file; do + files+=("$file") +done < <(git ls-files -z) + +for file in "${files[@]}"; do + [[ -f "$file" ]] || continue + + size=$(wc -c <"$file" | tr -d ' ') + if [[ "$size" -gt "$max_bytes" ]]; then + echo "Large tracked file exceeds 1 MiB: $file (${size} bytes)" >&2 + status=1 + fi + + if LC_ALL=C grep -Iq . "$file"; then + if LC_ALL=C grep -n '[[:blank:]]$' "$file" >/tmp/pre-commit-trailing.txt; then + echo "Trailing whitespace in $file:" >&2 + cat /tmp/pre-commit-trailing.txt >&2 + status=1 + fi + + if [[ -s "$file" ]] && [[ "$(tail -c 1 "$file")" != "" ]]; then + echo "Missing final newline: $file" >&2 + status=1 + fi + + if LC_ALL=C grep -n -E -e '-----BEGIN (RSA |DSA |EC |OPENSSH |PGP )?PRIVATE KEY-----' "$file" >/tmp/pre-commit-private-key.txt; then + echo "Private key material detected in $file:" >&2 + cat /tmp/pre-commit-private-key.txt >&2 + status=1 + fi + fi + + case "$file" in + *.json) + if ! python3 -m json.tool "$file" >/dev/null; then + echo "Invalid JSON: $file" >&2 + status=1 + fi + ;; + *.yaml|*.yml) + if command -v ruby >/dev/null 2>&1; then + if ! ruby -e 'require "yaml"; YAML.load_file(ARGV.fetch(0))' "$file"; then + echo "Invalid YAML: $file" >&2 + status=1 + fi + else + echo "ruby not found; cannot validate YAML file: $file" >&2 + status=1 + fi + ;; + esac +done + +rm -f /tmp/pre-commit-trailing.txt /tmp/pre-commit-private-key.txt + +exit "$status"