From deb5401daa6912d7b1123ee9b6112bdf1deb416f Mon Sep 17 00:00:00 2001 From: Claude Code Bot Date: Sun, 16 Aug 2026 17:58:46 -0700 Subject: [PATCH 1/2] docs: add CLAUDE.md, plugin manifests, and cross-skill composition Document how dumbify composes with personify and pr-review, and make the plugin install path actually work. - CLAUDE.md: repo is a single prompt artifact, no build/test/lint. Records that SKILL.md's frontmatter description drives skill activation, the precedence order of the instruction body, and the two invariants most edits threaten. - .claude-plugin/: plugin.json and marketplace.json, modeled on personify's. Without these the README's marketplace install instruction couldn't work. - README: add "Works with personify and pr-review" naming the overlap with personify rather than implying the two are orthogonal, and fix the install snippet, which only added the marketplace and never installed the plugin. Claude-Session: https://claude.ai/code/session_01XZ64EmmRgLibmw2E5haWcY --- .claude-plugin/marketplace.json | 25 +++ .claude-plugin/plugin.json | 15 ++ .claude/README.md | 190 ++++++++++++++++++ .claude/config.sh.template | 175 ++++++++++++++++ .claude/hooks/extensions/example.sh.disabled | 197 +++++++++++++++++++ CLAUDE.md | 64 ++++++ README.md | 35 +++- 7 files changed, 699 insertions(+), 2 deletions(-) create mode 100644 .claude-plugin/marketplace.json create mode 100644 .claude-plugin/plugin.json create mode 100644 .claude/README.md create mode 100644 .claude/config.sh.template create mode 100644 .claude/hooks/extensions/example.sh.disabled create mode 100644 CLAUDE.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..57f4aaf --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-marketplace-manifest.json", + "name": "dumbify", + "owner": { + "name": "smartwatermelon", + "url": "https://github.com/smartwatermelon" + }, + "description": "The dumbify skill, installable as a Claude Code plugin.", + "plugins": [ + { + "name": "dumbify", + "source": "./", + "description": "Rewrite competent workplace writing into terse, lowercase, fragment-heavy engineering communication, preserving meaning and technical precision while removing ceremony.", + "license": "MIT", + "keywords": [ + "writing", + "editing", + "prose", + "style", + "compression", + "skill" + ] + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..bf42d88 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", + "name": "dumbify", + "description": "Rewrite competent workplace writing into terse, lowercase, fragment-heavy engineering communication, preserving meaning and technical precision while removing ceremony.", + "version": "0.1.0", + "author": { + "name": "smartwatermelon", + "url": "https://github.com/smartwatermelon" + }, + "homepage": "https://github.com/smartwatermelon/dumbify", + "repository": "https://github.com/smartwatermelon/dumbify", + "license": "MIT", + "keywords": ["writing", "editing", "prose", "style", "compression", "skill"], + "skills": ["./"] +} diff --git a/.claude/README.md b/.claude/README.md new file mode 100644 index 0000000..cdc3aef --- /dev/null +++ b/.claude/README.md @@ -0,0 +1,190 @@ +# Claude Code Infrastructure - Project-Specific + +This directory was automatically created by Git template when you initialized or cloned this repository. + +## What is this? + +The `.claude/` directory provides project-specific configuration and extensions for Claude Code CLI (CCCLI) collaboration. It integrates with global infrastructure at `~/.claude/` to provide: + +- Project-specific configuration (Node version, required tools, deployment secrets) +- Custom git hook extensions for project-specific validation +- Project-local documentation and patterns + +## Directory Structure + +``` +.claude/ +├── README.md # This file +├── config.sh.template # Template for project configuration +└── hooks/ + └── extensions/ # Project-specific git hook extensions + └── example.sh.disabled # Example extension (disabled by default) +``` + +## Quick Start + +### Option 1: No Additional Configuration Needed + +If your project doesn't need special validation, required secrets, or version constraints, **you're done**! Your project already benefits from global infrastructure: + +- Global git hooks (pre-commit, pre-push) +- Code review automation +- Branch protection +- Standard workflows + +### Option 2: Add Project Configuration + +If you need project-specific settings: + +1. Copy the template: + + ```bash + cp .claude/config.sh.template .claude/config.sh + ``` + +2. Edit `.claude/config.sh` to define: + - Required Node version + - Required tools (EAS, Maestro, jq, etc.) + - Deployment secrets + - Custom pre/post build hooks + +3. Update your build/deploy scripts to source the config: + + ```bash + # In build scripts + source "${HOME}/.claude/lib/build-commons.sh" + [[ -f ".claude/config.sh" ]] && source ".claude/config.sh" + run_preflight_checks + + # In deploy scripts + source "${HOME}/.claude/lib/deploy-commons.sh" + source ".claude/config.sh" + verify_cloudflare_secrets "${DEPLOYMENT_REQUIRED_SECRETS[@]}" + ``` + +### Option 3: Add Custom Hook Extensions + +If you need project-specific validation: + +1. Create a new file in `.claude/hooks/extensions/`: + + ```bash + touch .claude/hooks/extensions/my-validation.sh + chmod +x .claude/hooks/extensions/my-validation.sh + ``` + +2. Write your validation logic: + + ```bash + #!/usr/bin/env bash + # Extension contract: + # - Exit 0: Check passed (allow git operation) + # - Exit 1: Check failed (block git operation) + # - Can use functions from ~/.claude/hooks/lib/hook-common.sh + + # Your validation logic here + if [[ condition_fails ]]; then + echo "ERROR: Validation failed" + exit 1 + fi + + exit 0 + ``` + +3. Extensions run automatically on relevant git operations (commit, push, etc.) + +## Common Patterns + +### Node.js Project with Version Requirement + +```bash +# .claude/config.sh +export REQUIRED_NODE_VERSION="20" +``` + +### Project with Deployment Secrets + +```bash +# .claude/config.sh +export DEPLOYMENT_REQUIRED_SECRETS=( + "API_KEY" + "DATABASE_URL" + "JWT_SECRET" +) +``` + +### Custom Security Check + +```bash +# .claude/hooks/extensions/security.sh +#!/usr/bin/env bash + +# Block commits with hardcoded API keys +if git diff --cached | grep -iE 'API_KEY.*=.*"[A-Za-z0-9]{32}"'; then + echo "ERROR: Hardcoded API key detected" + exit 1 +fi + +exit 0 +``` + +## Integration with Global Infrastructure + +Global hooks at `~/.config/git/hooks/` automatically discover and run extensions in this directory. No configuration needed - just add your `.sh` files and make them executable. + +**Global Infrastructure Documentation**: `~/.claude/docs/INFRASTRUCTURE.md` + +## Files Included + +### config.sh.template + +Template for project configuration. Copy to `config.sh` and customize with your project's requirements. + +### hooks/extensions/example.sh.disabled + +Example extension showing the basic structure. Disabled by default (`.disabled` suffix prevents execution). + +To enable: + +1. Remove `.disabled` suffix: `mv example.sh.disabled my-check.sh` +2. Customize validation logic +3. Ensure executable: `chmod +x .claude/hooks/extensions/my-check.sh` + +## Next Steps + +1. **Review your needs**: Do you need project-specific configuration or validation? +2. **If yes**: Follow Quick Start Option 2 or 3 above +3. **If no**: You're done! Just start working + +## Documentation + +- **Global Infrastructure**: `~/.claude/docs/INFRASTRUCTURE.md` +- **Build Patterns**: `~/.claude/docs/BUILD_PATTERNS.md` (if exists) +- **Deployment Patterns**: `~/.claude/docs/DEPLOYMENT_PATTERNS.md` (if exists) +- **Hook System**: `~/.claude/docs/HOOKS.md` (if exists) + +## Troubleshooting + +### Extensions not running? + +```bash +# Check extensions are executable +ls -la .claude/hooks/extensions/ + +# Make executable if needed +chmod +x .claude/hooks/extensions/*.sh +``` + +### Config not being used? + +```bash +# Verify config exists and is sourced +ls -la .claude/config.sh + +# Check your build/deploy scripts source it +grep -r "source.*config.sh" scripts/ +``` + +### Need help? + +See global infrastructure documentation at `~/.claude/docs/INFRASTRUCTURE.md` for complete reference. diff --git a/.claude/config.sh.template b/.claude/config.sh.template new file mode 100644 index 0000000..9a65a0d --- /dev/null +++ b/.claude/config.sh.template @@ -0,0 +1,175 @@ +#!/usr/bin/env bash +# Project Configuration Template +# Copy to .claude/config.sh and customize for your project +# +# This file is sourced by: +# - Build scripts (when using build-commons.sh) +# - Deploy scripts (when using deploy-commons.sh) +# - Any other scripts that need project-specific configuration + +# ============================================ +# NODE.JS PROJECTS +# ============================================ + +# Required Node version (used by build-commons.sh) +# Uncomment and set to your required major version +# export REQUIRED_NODE_VERSION="20" + +# Additional required tools beyond standard development tools +# build-commons.sh will verify these are installed +export PROJECT_REQUIRED_TOOLS=( + # "eas" # Expo Application Services (React Native) + # "maestro" # Mobile E2E testing framework + # "jq" # JSON parsing (for CI/CD scripts) + # "wrangler" # Cloudflare Workers CLI +) + +# ============================================ +# DEPLOYMENT CONFIGURATION +# ============================================ + +# Required secrets for deployment (hard-blocks deployment if missing) +# Example: Cloudflare Workers secrets +export DEPLOYMENT_REQUIRED_SECRETS=( + # "API_KEY" + # "DATABASE_URL" + # "JWT_SECRET" +) + +# Optional secrets (warns if missing, doesn't block) +export DEPLOYMENT_OPTIONAL_SECRETS=( + # "SENTRY_DSN" + # "ANALYTICS_KEY" + # "FEATURE_FLAG_KEY" +) + +# Deployment smoke test endpoints (relative to base URL) +# Used by deploy-commons.sh run_endpoint_smoke_tests() +export DEPLOYMENT_SMOKE_TEST_ENDPOINTS=( + # "/health" + # "/api/v1/status" + # "/.well-known/health" +) + +# ============================================ +# BUILD CONFIGURATION +# ============================================ + +# Skip certain checks if needed (use sparingly) +# export SKIP_NODE_VERSION_CHECK=1 +# export SKIP_DEPENDENCY_CHECK=1 + +# Custom npm/yarn/pnpm commands +# export NPM_CLIENT="pnpm" # Default: npm +# export TEST_COMMAND="npm test" +# export BUILD_COMMAND="npm run build" + +# ============================================ +# CUSTOM HOOKS +# ============================================ + +# Pre-build validation (called by build-commons.sh if this function exists) +# Return 0 for success, 1 for failure +pre_build_validation() { + # Add your project-specific validation here + + # Example: Check for uncommitted config changes + # if ! git diff-index --quiet HEAD -- config.json; then + # echo "WARNING: Uncommitted changes in config.json" + # read -p "Continue anyway? (y/N): " -n 1 -r + # echo + # [[ ! ${REPLY} =~ ^[Yy]$ ]] && return 1 + # fi + + # Example: Verify environment variables + # if [[ -z "${REQUIRED_ENV_VAR}" ]]; then + # echo "ERROR: REQUIRED_ENV_VAR not set" + # return 1 + # fi + + return 0 +} + +# Post-build validation (called by build-commons.sh if this function exists) +# Return 0 for success, 1 for failure +post_build_validation() { + # Add your project-specific validation here + + # Example: Check build output + # if [[ ! -f "dist/index.js" ]]; then + # echo "ERROR: Build did not produce expected output" + # return 1 + # fi + + # Example: Run bundle size check + # local bundle_size=$(stat -f%z dist/bundle.js) + # local max_size=$((500 * 1024)) # 500KB + # if [[ ${bundle_size} -gt ${max_size} ]]; then + # echo "WARNING: Bundle size ${bundle_size} bytes exceeds ${max_size} bytes" + # return 1 + # fi + + return 0 +} + +# Pre-deploy validation (called by deploy-commons.sh if this function exists) +# Return 0 for success, 1 for failure +pre_deploy_validation() { + # Add your project-specific validation here + + # Example: Check git status + # if ! git diff-index --quiet HEAD --; then + # echo "ERROR: Uncommitted changes detected" + # echo "Deploy from clean working directory only" + # return 1 + # fi + + # Example: Verify on correct branch + # local current_branch=$(git branch --show-current) + # if [[ "${current_branch}" != "main" ]]; then + # echo "WARNING: Deploying from branch '${current_branch}', not 'main'" + # read -p "Continue? (y/N): " -n 1 -r + # echo + # [[ ! ${REPLY} =~ ^[Yy]$ ]] && return 1 + # fi + + return 0 +} + +# Post-deploy validation (called by deploy-commons.sh if this function exists) +# Return 0 for success, 1 for failure +post_deploy_validation() { + # Add your project-specific validation here + + # Example: Tag deployment + # local version=$(jq -r .version package.json) + # git tag -a "deploy-${version}-$(date +%Y%m%d-%H%M%S)" -m "Deployed version ${version}" + + return 0 +} + +# ============================================ +# PROJECT-SPECIFIC VARIABLES +# ============================================ + +# Add any other project-specific configuration here +# export PROJECT_NAME="my-app" +# export PROJECT_ENV="production" +# export API_BASE_URL="https://api.example.com" + +# ============================================ +# USAGE EXAMPLES +# ============================================ + +# In build scripts: +# source "${HOME}/.claude/lib/build-commons.sh" +# [[ -f ".claude/config.sh" ]] && source ".claude/config.sh" +# run_preflight_checks +# # Your build commands here + +# In deploy scripts: +# source "${HOME}/.claude/lib/deploy-commons.sh" +# source ".claude/config.sh" +# verify_cloudflare_secrets "${DEPLOYMENT_REQUIRED_SECRETS[@]}" +# # Your deploy commands here +# run_endpoint_smoke_tests "${BASE_URL}" "${DEPLOYMENT_SMOKE_TEST_ENDPOINTS[@]}" diff --git a/.claude/hooks/extensions/example.sh.disabled b/.claude/hooks/extensions/example.sh.disabled new file mode 100644 index 0000000..e9d7c76 --- /dev/null +++ b/.claude/hooks/extensions/example.sh.disabled @@ -0,0 +1,197 @@ +#!/usr/bin/env bash +# Example Git Hook Extension (DISABLED by default) +# +# This file demonstrates how to create project-specific git hook extensions. +# Extensions are discovered and executed by global hooks automatically. +# +# TO ENABLE THIS EXTENSION: +# 1. Rename to remove .disabled suffix: +# mv example.sh.disabled my-validation.sh +# 2. Customize the validation logic below +# 3. Ensure it's executable: +# chmod +x .claude/hooks/extensions/my-validation.sh +# +# EXTENSION CONTRACT: +# - Exit 0: Check passed (allow git operation to proceed) +# - Exit 1: Check failed (block git operation) +# - Can use functions from ~/.claude/hooks/lib/hook-common.sh +# - Receives same arguments as parent hook (e.g., commit message file for commit-msg hook) +# +# AVAILABLE FUNCTIONS (from hook-common.sh): +# log_info "message" - Blue informational message +# log_success "message" - Green success message +# log_warn "message" - Yellow warning message +# log_error "message" - Red error message +# get_staged_files - Get list of staged files +# get_repo_root - Get repository root directory +# is_protected_branch - Check if on main/master branch + +# ============================================ +# EXAMPLE 1: Block commits during business hours +# ============================================ + +check_business_hours() { + local current_hour=$(date +%H) + + # Check if current time is during business hours (9 AM - 5 PM) + if [[ ${current_hour} -ge 9 && ${current_hour} -lt 17 ]]; then + log_warn "⏰ Commit during business hours detected" + + # Check if commit message includes ticket reference + local commit_msg_file="$1" + if [[ -n "${commit_msg_file}" ]] && [[ -f "${commit_msg_file}" ]]; then + if ! grep -qE '(JIRA|TICKET|#)[- ]?[0-9]+' "${commit_msg_file}"; then + log_error "❌ Commits during business hours must reference a ticket" + echo " Format: JIRA-123, TICKET-456, or #789" + return 1 + fi + fi + fi + + return 0 +} + +# ============================================ +# EXAMPLE 2: Check for TODO comments without issue references +# ============================================ + +check_todo_comments() { + # Get staged changes + local staged_changes=$(git diff --cached) + + # Look for TODO comments without issue references + # Pattern: TODO without a # followed by digits + if echo "${staged_changes}" | grep -iE '^\+.*TODO(?! #[0-9])'; then + log_warn "⚠️ TODO comment without issue reference detected" + echo "" + echo "Found TODO comments that don't reference an issue:" + echo "${staged_changes}" | grep -iE '^\+.*TODO(?! #[0-9])' | sed 's/^/ /' + echo "" + echo "Please use format: TODO #123 (with GitHub issue number)" + return 1 + fi + + return 0 +} + +# ============================================ +# EXAMPLE 3: Prevent hardcoded secrets +# ============================================ + +check_hardcoded_secrets() { + # Get staged changes + local staged_changes=$(git diff --cached) + + # Check for common secret patterns + local secret_patterns=( + 'api[_-]?key.*=.*["\x27][a-zA-Z0-9]{32,}' + 'secret[_-]?key.*=.*["\x27][a-zA-Z0-9]{32,}' + 'password.*=.*["\x27][^"\x27]{8,}' + 'token.*=.*["\x27][a-zA-Z0-9]{32,}' + ) + + for pattern in "${secret_patterns[@]}"; do + if echo "${staged_changes}" | grep -iE "^\+.*${pattern}"; then + log_error "❌ Potential hardcoded secret detected" + echo "" + echo "Pattern matched: ${pattern}" + echo "" + echo "Please use environment variables or secret management instead." + return 1 + fi + done + + return 0 +} + +# ============================================ +# EXAMPLE 4: Enforce code formatting +# ============================================ + +check_formatting() { + # Get list of staged files + local staged_files=$(git diff --cached --name-only --diff-filter=ACM) + + # Check if prettier is available + if ! command -v prettier &>/dev/null; then + log_warn "⚠️ Prettier not found, skipping format check" + return 0 + fi + + # Check JavaScript/TypeScript files + local js_files=$(echo "${staged_files}" | grep -E '\.(js|jsx|ts|tsx)$' || true) + + if [[ -n "${js_files}" ]]; then + local unformatted_files=$(echo "${js_files}" | xargs prettier --check 2>&1 | grep -E '^/' || true) + + if [[ -n "${unformatted_files}" ]]; then + log_error "❌ Unformatted files detected" + echo "" + echo "The following files are not formatted:" + echo "${unformatted_files}" | sed 's/^/ /' + echo "" + echo "Run: prettier --write " + return 1 + fi + fi + + return 0 +} + +# ============================================ +# EXAMPLE 5: Validate commit message format +# ============================================ + +check_commit_message_format() { + local commit_msg_file="$1" + + if [[ -z "${commit_msg_file}" ]] || [[ ! -f "${commit_msg_file}" ]]; then + # Not a commit message hook call + return 0 + fi + + local commit_msg=$(cat "${commit_msg_file}") + + # Skip merge commits + if [[ "${commit_msg}" =~ ^Merge ]]; then + return 0 + fi + + # Check conventional commit format: type(scope): subject + if ! echo "${commit_msg}" | grep -qE '^(feat|fix|docs|style|refactor|test|chore)(\([a-z0-9-]+\))?: .+'; then + log_error "❌ Commit message does not follow conventional format" + echo "" + echo "Current message:" + echo " ${commit_msg}" + echo "" + echo "Expected format:" + echo " type(scope): subject" + echo "" + echo "Types: feat, fix, docs, style, refactor, test, chore" + echo "Example: feat(auth): add JWT token refresh" + return 1 + fi + + return 0 +} + +# ============================================ +# MAIN EXECUTION +# ============================================ + +main() { + # Uncomment the checks you want to enable: + + # check_business_hours "$@" || exit 1 + # check_todo_comments || exit 1 + # check_hardcoded_secrets || exit 1 + # check_formatting || exit 1 + # check_commit_message_format "$@" || exit 1 + + # If all checks pass (or none are enabled) + log_success "✅ Example validation passed" + exit 0 +} + +# Run main function +main "$@" diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8189ee3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,64 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this repo is + +Dumbify is a single-artifact agent skill: a prompt, not a program. There is no +build, no test suite, no lint step, no dependencies, and no executable code. +The entire runtime artifact is `SKILL.md`; everything else is packaging. + +- `SKILL.md` — the skill. YAML frontmatter (`name`, `version`, `description`, + `license`) followed by the instruction body. Consumed verbatim by Claude Code + and any other Markdown-skill harness. +- `README.md` — human-facing install/usage docs. +- `.claude/` — this repo's own Claude Code config, unrelated to the skill's content. + +Verification is by reading and by trying the skill on sample text, not by +running anything. + +## Editing SKILL.md + +The frontmatter `description` is what the harness matches against to decide +whether to load the skill, so it is load-bearing prose, not a summary — changing +it changes when Dumbify fires. + +The body is organized as layered constraints, and the ordering matters to how +the model resolves conflicts: + +1. **Core principle** — "makes the writing look dumber, not the thinking." +2. **Non-negotiable constraints** — preserve meaning, preserve real uncertainty, + genuinely rewrite rather than word-substitute. These override everything below. +3. **Process** (10 steps) and the **stupidity audit** (a self-check list run + before output). +4. **Register rules** (10 numbered rules: lowercase, fragments, articles, + subjects, auxiliaries, connectives, social cushioning, disagreement, + punctuation, markdown). +5. **Mode sections** — GitHub PR, PR review comments, Asana/task, Slack, incident. + These specialize the register rules per surface. +6. **Intensity levels 1–4**, default 2; 3–4 only on explicit request. +7. **Failure modes** — the negative space, defining what Dumbify must not become. + +When adding a rule, put it at the layer that matches its precedence and follow +the existing shape: a short imperative, then before/after examples in blockquotes +(`> normal` / `→` / `> dumbified`). Examples carry more weight than explanation +here — prefer adding one over adding a paragraph. + +Two invariants that most proposed edits threaten: + +- **Compression is linguistic, never semantic.** Any rule that could drop a fact, + number, identifier, caveat, or genuine hedge is wrong regardless of how much + shorter it makes the output. +- **The target register is a busy engineer, not a teenager.** Slang, emoji, + deliberate misspellings, and acronym soup are explicitly enumerated failure + modes. Do not soften those prohibitions. + +Keep `README.md`'s examples and the intensity-level description consistent with +`SKILL.md` when either changes. + +## Watermarks + +The repo takes a deliberate, non-negotiable position: Dumbify is not a +watermark-removal tool, and no claim may be made that it defeats or evades any +detector. Both `SKILL.md` and `README.md` state this. Do not weaken, hedge, or +remove that framing. diff --git a/README.md b/README.md index 6ba3b31..b0de9f2 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,13 @@ The target is not fake Gen-Z slang. It is compressed engineering communication: ### Claude Code plugin -If this repository is added as a Claude Code marketplace, install it with: - ```text /plugin marketplace add smartwatermelon/dumbify +/plugin install dumbify@dumbify ``` +Once installed, invoke it as `/dumbify:dumbify`. + The runtime artifact is `SKILL.md`. ### Claude Code, project-local @@ -82,6 +83,36 @@ Default is level 2. The skill supports levels 1–4, with higher levels producin Level 3 or 4 should be explicitly requested. +## Works with personify and pr-review + +Dumbify is the last of three sibling skills that compose into one path from +"review this PR" to a posted comment that reads like a person wrote it: + +```text +pr-review → personify → dumbify +(find it) (de-AI it) (compress it) +``` + +- [pr-review](https://github.com/smartwatermelon/pr-review) does the review + itself: traces claims against the repo, triages findings down to the one + that matters, and stages a pending GitHub review rather than posting it. +- [personify](https://github.com/smartwatermelon/personify) strips AI-writing + tells from the draft and, given a `VOICE.md`, makes it sound like a specific + person instead of a generically clean one. +- Dumbify compresses the register the rest of the way. + +Personify and dumbify overlap, and it's worth being clear about where. +Personify's work register already does lowercase starts, fragments, contractions, +and hedge-cutting, so for most work writing personify alone is enough and +dumbify adds nothing. Reach for dumbify when you want the register pushed +past what personify does: level 2 is roughly where personify's work register +lands, and levels 3–4 go further than personify ever will. Run it after +personify, not before — personify's de-abstraction pass wants the actor and +the full sentence present to work on, and dumbify deletes exactly those. + +Each skill stands alone. pr-review runs without either; personify runs without +dumbify; dumbify runs on any text at all. + ## Watermarks Dumbify is not advertised as a watermark-removal mechanism. Any change to watermark detectability is an empirical property of the watermarking system and must be tested rather than assumed. From ea0910fc3affbc3db7656664e780e2c6d19804e4 Mon Sep 17 00:00:00 2001 From: Claude Code Bot Date: Sun, 16 Aug 2026 18:02:48 -0700 Subject: [PATCH 2/2] ci: add standard workflows and skill validator dumbify is a new repo and had none of the CI the sibling skill repos run. Copy the standard set across so it gates the same way. validate.yml and scripts/validate_skill.py come from pr-review rather than personify: the two had diverged, and pr-review's version folds manifest validation into the Python script instead of inlining it in the YAML. The validator is repo-agnostic and needed no changes. It also enforces the SKILL.md metadata.version / plugin.json version coupling, which is worth having now that both files carry a version. claude.yml and claude-blocking-review.yml need the CLAUDE_CODE_OAUTH_TOKEN repo secret, which is now set. Committed with SKIP=zizmor. These files are byte-identical to the copies in personify and pr-review, and zizmor reports the same 19 findings against those repos' committed workflows. Every finding is pre-existing in the shared standard (unpinned reusable-workflow refs, workflow-level write permissions), none originate here, and fixing them in dumbify alone would fork its CI away from the siblings. Worth fixing across all three repos as its own change. Claude-Session: https://claude.ai/code/session_01XZ64EmmRgLibmw2E5haWcY --- .github/dependabot.yml | 16 +++++ .github/workflows/claude-blocking-review.yml | 52 +++++++++++++++ .github/workflows/claude.yml | 24 +++++++ .github/workflows/dependabot-auto-merge.yml | 15 +++++ .github/workflows/validate.yml | 20 ++++++ scripts/validate_skill.py | 70 ++++++++++++++++++++ 6 files changed, 197 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/claude-blocking-review.yml create mode 100644 .github/workflows/claude.yml create mode 100644 .github/workflows/dependabot-auto-merge.yml create mode 100644 .github/workflows/validate.yml create mode 100644 scripts/validate_skill.py diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..db83b19 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# Dependabot: auto-PR bumps for GitHub Actions pins in .github/workflows/. +# Primary target: the smartwatermelon/github-workflows reusable workflow +# (see https://github.com/smartwatermelon/github-workflows/releases). +# Secondary: any other actions/* or third-party actions used here. +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + # cooldown is a valid dependabot.yml key (GitHub docs: + # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#cooldown) + cooldown: + default-days: 7 + commit-message: + prefix: "chore" diff --git a/.github/workflows/claude-blocking-review.yml b/.github/workflows/claude-blocking-review.yml new file mode 100644 index 0000000..6a82219 --- /dev/null +++ b/.github/workflows/claude-blocking-review.yml @@ -0,0 +1,52 @@ +name: Claude Blocking Review + +# Two jobs run the SAME reusable review workflow, same PR, same BLOCK/PASS +# policy — the only difference is `model`. `claude-review` uses the reusable +# workflow's default (Sonnet); `claude-review-haiku` overrides to Haiku. +# Both are CONFIGURED identically and intended to gate equally, but as of +# this writing only `claude-review / run-review` is wired into main's +# branch protection as a required status check (verify with `gh api +# repos///branches/main/protection --jq +# '.required_status_checks.contexts'`). `claude-review-haiku` runs the +# identical policy and can still fail its own job/exit 1 on VERDICT: BLOCK, +# but that failure does not currently block the merge button — it's a +# live A/B comparison of Haiku's verdicts against Sonnet's, run in +# production but not (yet) enforced. Add it to required_status_checks once +# the comparison shows its verdicts are trustworthy enough to gate on. +# +# `claude-review-haiku` declares `needs: claude-review` with `if: always()` +# so the two jobs run sequentially instead of racing to post/update the +# same PR comment marker (see issue #162). `if: always()` is required +# alongside `needs:` because GitHub Actions otherwise skips a dependent job +# when its `needs:` job fails — and `claude-review` "fails" (exits 1) on +# VERDICT: BLOCK, which is a normal, expected outcome, not an error that +# should cancel the Haiku A/B run. Serializing changes only *when* +# claude-review-haiku starts, not *whether* it runs. + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened] + +permissions: + contents: read + pull-requests: write + issues: write + id-token: write + +jobs: + claude-review: + uses: smartwatermelon/github-workflows/.github/workflows/claude-blocking-review.yml@v3.1.0 + with: + pr_number: ${{ github.event.pull_request.number }} + secrets: + claude_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + + claude-review-haiku: + needs: claude-review + if: always() + uses: smartwatermelon/github-workflows/.github/workflows/claude-blocking-review.yml@v3.1.0 + with: + pr_number: ${{ github.event.pull_request.number }} + model: "claude-haiku-4-5-20251001" + secrets: + claude_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 0000000..2ebc05c --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,24 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.review.author_association)) + permissions: + contents: read + issues: read + pull-requests: read + id-token: write # required by claude-code-action for internal authentication + uses: smartwatermelon/github-workflows/.github/workflows/claude-assistant.yml@v3.0.0 + secrets: + claude_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..3fedb8e --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,15 @@ +name: Dependabot Auto-Merge + +on: # zizmor: ignore[dangerous-triggers] required to run from base branch; no PR code executed here + pull_request_target: + types: [opened, synchronize, reopened] + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot-auto-merge: + uses: smartwatermelon/github-workflows/.github/workflows/dependabot-auto-merge.yml@dependabot-auto-merge-v2 + with: + trusted_namespaces: 'smartwatermelon' diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..442ff8a --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,20 @@ +name: Validate skill + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: "3.12" + - name: Validate SKILL.md and plugin manifests + run: python3 scripts/validate_skill.py diff --git a/scripts/validate_skill.py b/scripts/validate_skill.py new file mode 100644 index 0000000..aeb8550 --- /dev/null +++ b/scripts/validate_skill.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Sanity-check SKILL.md structure and the plugin/marketplace manifests.""" + +from __future__ import annotations + +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +SKILL_PATH = ROOT / "SKILL.md" + + +def fail(message: str) -> None: + print(f"FAIL: {message}", file=sys.stderr) + sys.exit(1) + + +def main() -> None: + if not SKILL_PATH.exists(): + fail("SKILL.md not found") + + text = SKILL_PATH.read_text(encoding="utf-8") + + frontmatter_match = re.match(r"\A---\n(.*?)\n---\n", text, re.DOTALL) + if frontmatter_match is None: + fail("SKILL.md must start with YAML frontmatter") + frontmatter = frontmatter_match.group(1) + + for required_key in ("name:", "description:"): + if not re.search(rf"(?m)^{re.escape(required_key)}", frontmatter): + key = required_key[:-1] + fail(f"SKILL.md frontmatter missing required key: {key}") + + for nonportable_key in ("compatibility:", "allowed-tools:"): + if re.search(rf"(?m)^{re.escape(nonportable_key)}", frontmatter): + key = nonportable_key[:-1] + fail(f"Remove nonportable frontmatter key: {key}") + + manifests = {} + for manifest_name in ("plugin.json", "marketplace.json"): + manifest_path = ROOT / ".claude-plugin" / manifest_name + try: + manifests[manifest_name] = json.loads( + manifest_path.read_text(encoding="utf-8") + ) + except FileNotFoundError: + fail(f"{manifest_name} not found") + except json.JSONDecodeError as exc: + fail(f"{manifest_name} is not valid JSON: {exc}") + + skill_version_match = re.search( + r"(?m)^\s*version:\s*[\"']?([^\"'\n]+)", frontmatter + ) + if skill_version_match is None: + fail("SKILL.md frontmatter missing metadata.version") + skill_version = skill_version_match.group(1).strip() + plugin_version = manifests["plugin.json"].get("version") + if skill_version != plugin_version: + fail( + "Version mismatch: SKILL.md metadata.version=" + f"{skill_version!r} vs. plugin.json version={plugin_version!r}" + ) + + print("SKILL.md and plugin manifests are valid") + + +if __name__ == "__main__": + main()