From a09fbc1876ea99f0422ff7c2de4ed07e4d371b87 Mon Sep 17 00:00:00 2001 From: Subin An Date: Sun, 26 Apr 2026 12:22:46 +0900 Subject: [PATCH 1/5] chore: harness cleanup and CI safety net MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add opt-in PostToolUse hook (VIBESUBIN_AUTO_VERIFY=1) that runs refactor-verify's symbol-diff after MultiEdit >=5 or signature-changing edits. Defaults OFF — no behavior change for existing installs. Extends refactor-verify per invariant #2 (no new skill, no cap impact). Add release.yml workflow on tag push (v*.*.*) — validates skills, runs pytest, enforces manifest version sync (tag = marketplace = plugin), and checks for forbidden file types before creating the GitHub release. The manual gh release create policy stays; this is the safety net. Add marketplace.json metadata (category, tags, repository) for plugin discoverability. install.sh: ERROR: text replaces ❌ emoji prefix. Validator: OK (12 skills, manifests synced). Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude-plugin/marketplace.json | 13 ++++- .github/workflows/release.yml | 53 ++++++++++++++++++ install.sh | 6 +-- plugins/vibesubin/hooks/auto-verify.sh | 75 ++++++++++++++++++++++++++ plugins/vibesubin/hooks/hooks.json | 16 ++++++ 5 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100755 plugins/vibesubin/hooks/auto-verify.sh create mode 100644 plugins/vibesubin/hooks/hooks.json diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 40c7dad..4892617 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -11,7 +11,18 @@ "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" + "repository": "https://github.com/subinium/vibesubin", + "license": "MIT", + "category": "developer-tools", + "tags": [ + "claude-code", + "skills", + "code-quality", + "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..6da1a38 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,53 @@ +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: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + name: "vibesubin ${{ github.ref_name }}" 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..0483f4c --- /dev/null +++ b/plugins/vibesubin/hooks/auto-verify.sh @@ -0,0 +1,75 @@ +#!/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) + +# 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 + +# Run the bundled symbol-diff with a hard 3s cap. +SCRIPT="${CLAUDE_PLUGIN_ROOT}/skills/refactor-verify/scripts/symbol-diff.sh" +if [ ! -x "$SCRIPT" ]; then + # Plugin not fully installed — silently skip. + exit 0 +fi + +REPO_DIR=$(cd "$(dirname "$FILE")" && git rev-parse --show-toplevel 2>/dev/null || dirname "$FILE") +{ + echo "vibesubin auto-verify: $TOOL on $(basename "$FILE") (edits=$EDITS)" + ( cd "$REPO_DIR" && timeout 3 "$SCRIPT" HEAD HEAD 2>&1 ) || true + echo "→ run /vibesubin:refactor-verify for the full 4-check verification" +} >&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 + } + ] + } + ] + } +} From 49da234a056140f73836b110abc1a19e4f892f3b Mon Sep 17 00:00:00 2001 From: Subin An Date: Sun, 26 Apr 2026 12:23:13 +0900 Subject: [PATCH 2/5] docs: changelog entries for harness cleanup Functional-only style per CLAUDE.md invariant #4. Documents the auto-verify hook (opt-in), release.yml safety net, marketplace.json metadata, and install.sh emoji removal added in the prior commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 From 8d5474cb47ba1840ac13e3bb2b4db2f3877ae6e8 Mon Sep 17 00:00:00 2001 From: Subin An Date: Sun, 26 Apr 2026 12:29:42 +0900 Subject: [PATCH 3/5] fix: auto-verify advisory-only, release CI as draft auto-verify.sh: remove symbol-diff inline invocation. The PostToolUse hook fired symbol-diff.sh with HEAD HEAD (comparing HEAD to itself, always 0 dropped) because at edit time there is no meaningful "before" snapshot. Advisory message alone is the correct behavior; the actual verification belongs to /vibesubin:refactor-verify which captures its own snapshot. release.yml: switch to draft: true. Auto-publishing conflicts with CLAUDE.md step 8 ("write release notes to a temp file, functional-only style"). CI now validates and creates a draft release the human edits before publishing. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/release.yml | 10 +++++++++- plugins/vibesubin/hooks/auto-verify.sh | 22 +++++++++++----------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6da1a38..4d229b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,8 +46,16 @@ jobs: exit 1 fi - - name: Create GitHub release + - 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. uses: softprops/action-gh-release@v2 with: + draft: true generate_release_notes: true name: "vibesubin ${{ github.ref_name }}" + body: | + Draft created by CI. Replace this body with the curated + functional-only release notes (per CLAUDE.md step 8) before + publishing. The auto-generated changelog above is for reference. diff --git a/plugins/vibesubin/hooks/auto-verify.sh b/plugins/vibesubin/hooks/auto-verify.sh index 0483f4c..ce24e48 100755 --- a/plugins/vibesubin/hooks/auto-verify.sh +++ b/plugins/vibesubin/hooks/auto-verify.sh @@ -57,18 +57,18 @@ if [ "$EDITS" -lt 5 ]; then fi fi -# Run the bundled symbol-diff with a hard 3s cap. -SCRIPT="${CLAUDE_PLUGIN_ROOT}/skills/refactor-verify/scripts/symbol-diff.sh" -if [ ! -x "$SCRIPT" ]; then - # Plugin not fully installed — silently skip. - exit 0 -fi - -REPO_DIR=$(cd "$(dirname "$FILE")" && git rev-parse --show-toplevel 2>/dev/null || dirname "$FILE") +# 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)" - ( cd "$REPO_DIR" && timeout 3 "$SCRIPT" HEAD HEAD 2>&1 ) || true - echo "→ run /vibesubin:refactor-verify for the full 4-check verification" + echo "vibesubin auto-verify: $TOOL on $(basename "$FILE") (edits=$EDITS, signature-changing)" + echo "→ run /vibesubin:refactor-verify to verify nothing was dropped or moved" } >&2 # PostToolUse: exit code is observability-only; always succeed. From 34e98587fb0b7eab9447540a38c1c3bc28d1f136 Mon Sep 17 00:00:00 2001 From: Subin An Date: Sun, 26 Apr 2026 13:42:01 +0900 Subject: [PATCH 4/5] fix: marketplace schema correctness and release CI polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit marketplace.json: drop `repository` (not in plugin schema — 0 of 160 plugins in claude-plugins-official use it; `homepage` covers the role). Replace `tags` with `keywords` to match the standard discovery field (vercel, openai-codex, and most plugins use `keywords`). release.yml: drop the instructional `body:` (conflicts with `generate_release_notes`). Add a version-extract step so the release title renders as "vibesubin 0.8.0" (matching CLAUDE.md step 9 example) instead of "vibesubin v0.8.0". Human still curates the notes from CHANGELOG.md before publishing. auto-verify.sh: defensive `EDITS="${EDITS:-0}"` guard before the numeric `-lt` comparison. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude-plugin/marketplace.json | 7 +++---- .github/workflows/release.yml | 13 +++++++------ plugins/vibesubin/hooks/auto-verify.sh | 1 + 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 4892617..e758ef8 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -11,13 +11,12 @@ "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", - "repository": "https://github.com/subinium/vibesubin", "license": "MIT", "category": "developer-tools", - "tags": [ + "keywords": [ "claude-code", - "skills", - "code-quality", + "skill-pack", + "vibe-coding", "refactor-verification", "security-audit", "ci-cd", diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4d229b4..bbed5f6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,16 +46,17 @@ jobs: 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. + # 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 ${{ github.ref_name }}" - body: | - Draft created by CI. Replace this body with the curated - functional-only release notes (per CLAUDE.md step 8) before - publishing. The auto-generated changelog above is for reference. + name: "vibesubin ${{ steps.version.outputs.version }}" diff --git a/plugins/vibesubin/hooks/auto-verify.sh b/plugins/vibesubin/hooks/auto-verify.sh index ce24e48..cd6b6e9 100755 --- a/plugins/vibesubin/hooks/auto-verify.sh +++ b/plugins/vibesubin/hooks/auto-verify.sh @@ -33,6 +33,7 @@ except Exception: 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 From cd11a395cfc9cec58284bf7952e2fb4f72b9928a Mon Sep 17 00:00:00 2001 From: Subin An Date: Sun, 26 Apr 2026 13:45:18 +0900 Subject: [PATCH 5/5] fix: drop "signature-changing" qualifier from auto-verify advisory The advisory fired for two paths (MultiEdit >=5 OR signature line in diff) but always claimed "signature-changing", which is wrong for the MultiEdit volume path. Drop the qualifier; the directive to run /vibesubin:refactor-verify is sufficient context. Co-Authored-By: Claude Opus 4.7 (1M context) --- plugins/vibesubin/hooks/auto-verify.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/vibesubin/hooks/auto-verify.sh b/plugins/vibesubin/hooks/auto-verify.sh index cd6b6e9..ef3a190 100755 --- a/plugins/vibesubin/hooks/auto-verify.sh +++ b/plugins/vibesubin/hooks/auto-verify.sh @@ -68,7 +68,7 @@ fi # 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, signature-changing)" + echo "vibesubin auto-verify: $TOOL on $(basename "$FILE") (edits=$EDITS)" echo "→ run /vibesubin:refactor-verify to verify nothing was dropped or moved" } >&2