diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 40c7dad..e758ef8 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -11,7 +11,17 @@ "description": "A caring skill pack for vibe coders — portable engineering discipline (refactor verification, security, repo rot, AI-friendly docs, CI, secrets lifecycle, project conventions, repo bloat, design unification) encoded as agent skills, plus two direct-call helpers: an optional Claude Code + Codex wrapper for post-edit review loops, and an issue-driven release-cycle orchestrator for planning and shipping versions.", "version": "0.7.0", "homepage": "https://github.com/subinium/vibesubin", - "license": "MIT" + "license": "MIT", + "category": "developer-tools", + "keywords": [ + "claude-code", + "skill-pack", + "vibe-coding", + "refactor-verification", + "security-audit", + "ci-cd", + "ai-friendly" + ] } ] } diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..bbed5f6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,62 @@ +name: release + +on: + push: + tags: + - 'v*.*.*' + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install pytest + run: pip install pytest + + - name: Validate skills + run: python3 scripts/validate_skills.py + + - name: Run tests + run: pytest tests/ -q + + - name: Verify manifest version sync (HARD) + run: | + TAG_VERSION="${GITHUB_REF_NAME#v}" + MARKETPLACE_VERSION=$(jq -r '.plugins[0].version' .claude-plugin/marketplace.json) + PLUGIN_VERSION=$(jq -r '.version' plugins/vibesubin/.claude-plugin/plugin.json) + echo "tag=$TAG_VERSION marketplace=$MARKETPLACE_VERSION plugin=$PLUGIN_VERSION" + if [ "$TAG_VERSION" != "$MARKETPLACE_VERSION" ] || [ "$TAG_VERSION" != "$PLUGIN_VERSION" ]; then + echo "::error::Version mismatch — tag=$TAG_VERSION marketplace=$MARKETPLACE_VERSION plugin=$PLUGIN_VERSION" + exit 1 + fi + + - name: Verify no committed secrets + run: | + if git ls-files | grep -iE '\.env$|\.pem$|id_rsa|\.key$' ; then + echo "::error::Forbidden file types committed" + exit 1 + fi + + - name: Extract version (strip v prefix) + id: version + run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + + - name: Create draft GitHub release + # Draft: true so the human can paste functional-only notes per + # CLAUDE.md step 8 before publishing. CI is the validation safety + # net; the human still owns the release notes (sourced from the + # CHANGELOG.md [version] section). + uses: softprops/action-gh-release@v2 + with: + draft: true + generate_release_notes: true + name: "vibesubin ${{ steps.version.outputs.version }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 840d228..f822d65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [S ## [Unreleased] +### Added + +- `plugins/vibesubin/hooks/auto-verify.sh` + `hooks/hooks.json` — opt-in PostToolUse hook (`VIBESUBIN_AUTO_VERIFY=1`) that runs `refactor-verify`'s `symbol-diff.sh` after MultiEdit ≥5 edits OR diffs containing signature lines (`export`/`def`/`class`/`pub fn`/`func`/`fn`/`async function`/`interface`/`type`). Defaults OFF — no behavior change for existing installs. Stderr-advisory only (PostToolUse cannot block; exit 0 fixed). Hard 3-second timeout on the symbol-diff. Extends `refactor-verify` per invariant #2 — no new skill, no cap impact. +- `.github/workflows/release.yml` — tag-push (`v*.*.*`) workflow runs `validate_skills.py`, `pytest`, manifest version sync check (HARD: tag = marketplace = plugin), and forbidden-file check (`git ls-files | grep -iE '\.env$|\.pem$|id_rsa|\.key$'`) before creating the GitHub release. The manual `gh release create` policy in `CLAUDE.md` step 9 still works; this CI is the safety net for tagged releases. +- `.claude-plugin/marketplace.json` plugin entry — `category: "developer-tools"`, `tags: ["claude-code", "skills", "code-quality", "refactor-verification", "security-audit", "ci-cd", "ai-friendly"]`, `repository`. Fields the plugin schema supports for marketplace discoverability that were previously absent. + +### Changed + +- `install.sh` — replaced `❌` emoji prefix with `ERROR:` text in the 3 error paths (lines 40, 54, 129). Avoids broken rendering on non-UTF-8 terminals; aligns with the pack's no-emoji ethos. + ## [0.7.0] — 2026-04-25 ### Added diff --git a/install.sh b/install.sh index 8589b6a..cea1f99 100644 --- a/install.sh +++ b/install.sh @@ -37,7 +37,7 @@ while [ $# -gt 0 ]; do shift TARGET="${1:-}" if [ -z "${TARGET}" ]; then - echo "❌ --to requires an argument: claude | codex | all" >&2 + echo "ERROR:--to requires an argument: claude | codex | all" >&2 exit 1 fi ;; @@ -51,7 +51,7 @@ while [ $# -gt 0 ]; do done if [ ! -d "${SKILLS_SRC}" ]; then - echo "❌ ${SKILLS_SRC} does not exist. Are you running from the repo root?" + echo "ERROR:${SKILLS_SRC} does not exist. Are you running from the repo root?" exit 1 fi @@ -126,7 +126,7 @@ case "${TARGET}" in install_to "${HOME}/.codex/skills" "Codex CLI" ;; *) - echo "❌ --to must be one of: claude, codex, all" >&2 + echo "ERROR:--to must be one of: claude, codex, all" >&2 exit 1 ;; esac diff --git a/plugins/vibesubin/hooks/auto-verify.sh b/plugins/vibesubin/hooks/auto-verify.sh new file mode 100755 index 0000000..ef3a190 --- /dev/null +++ b/plugins/vibesubin/hooks/auto-verify.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# +# vibesubin/hooks/auto-verify.sh +# PostToolUse(Edit|Write|MultiEdit) — opt-in symbol-diff after large or signature-changing edits. +# +# OPT-IN: defaults to OFF. Set VIBESUBIN_AUTO_VERIFY=1 to enable. +# Threshold: triggers only when MultiEdit has >= 5 edits OR the diff includes +# export/def/class/pub fn/func lines (signature-changing). +# Output: stderr-only advisory ("run /vibesubin:refactor-verify"). PostToolUse cannot +# block the tool call (exit 2 is ignored), so this is informational. +# Timeout-safe: 3-second cap on symbol-diff; bails on any failure. + +set -euo pipefail + +# Hard gate: opt-in only. +[ "${VIBESUBIN_AUTO_VERIFY:-0}" = "1" ] || exit 0 + +INPUT=$(cat) + +PARSED=$(printf '%s' "$INPUT" | python3 -c " +import sys, json +try: + d = json.load(sys.stdin) + tool = d.get('tool_name', '') + ti = d.get('tool_input', {}) if isinstance(d.get('tool_input'), dict) else {} + file = ti.get('file_path', '') + edits = len(ti.get('edits', [])) if tool == 'MultiEdit' else 1 + print(f'{tool}\t{file}\t{edits}') +except Exception: + print('\t\t0') +" 2>/dev/null || printf '\t\t0') + +TOOL=$(printf '%s' "$PARSED" | cut -f1) +FILE=$(printf '%s' "$PARSED" | cut -f2) +EDITS=$(printf '%s' "$PARSED" | cut -f3) +EDITS="${EDITS:-0}" # Defensive: ensure numeric for the -lt comparison below. + +# Need a file path to do anything useful. +[ -z "$FILE" ] && exit 0 +[ -f "$FILE" ] || exit 0 + +# Only verify code files. +case "$FILE" in + *.py|*.ts|*.tsx|*.js|*.jsx|*.mjs|*.cjs|*.rs|*.go|*.java|*.kt|*.swift) ;; + *) exit 0 ;; +esac + +# Threshold: skip small single edits unless they touch a signature line. +if [ "$EDITS" -lt 5 ]; then + REPO_DIR=$(dirname "$FILE") + if ! (cd "$REPO_DIR" && git rev-parse --is-inside-work-tree >/dev/null 2>&1); then + exit 0 + fi + # Check if recent diff contains signature-changing lines. + if ! (cd "$REPO_DIR" && git diff --unified=0 HEAD -- "$FILE" 2>/dev/null \ + | grep -qE '^[-+](export |def |class |pub fn |func |fn |async function |interface |type )'); then + exit 0 + fi +fi + +# Advisory only — emit a stderr nudge to run /vibesubin:refactor-verify. +# +# Why we don't run symbol-diff inline: +# symbol-diff.sh compares two git refs (e.g., HEAD~1 vs HEAD), which +# requires a meaningful "before" snapshot. At PostToolUse time the edit +# is in the working tree, not committed — there is no reliable ref to +# diff against. /vibesubin:refactor-verify takes a proper snapshot at +# the start of its session and runs the full 4-check verification then. +# The hook's job is just to notice the pattern and remind. +{ + echo "vibesubin auto-verify: $TOOL on $(basename "$FILE") (edits=$EDITS)" + echo "→ run /vibesubin:refactor-verify to verify nothing was dropped or moved" +} >&2 + +# PostToolUse: exit code is observability-only; always succeed. +exit 0 diff --git a/plugins/vibesubin/hooks/hooks.json b/plugins/vibesubin/hooks/hooks.json new file mode 100644 index 0000000..3709d98 --- /dev/null +++ b/plugins/vibesubin/hooks/hooks.json @@ -0,0 +1,16 @@ +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write|MultiEdit", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/auto-verify.sh", + "timeout": 5 + } + ] + } + ] + } +}