diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json deleted file mode 100644 index d4291379f..000000000 --- a/.claude-plugin/marketplace.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "superpowers-dev", - "description": "Development marketplace for Superpowers core skills library", - "owner": { - "name": "Jesse Vincent", - "email": "jesse@fsck.com" - }, - "plugins": [ - { - "name": "superpowers", - "description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques", - "version": "3.6.2", - "source": "./", - "author": { - "name": "Jesse Vincent", - "email": "jesse@fsck.com" - } - } - ] -} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index da89c3177..f9c071e7f 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "superpowers", - "description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques", + "description": "Core skills library with writing-plans skill and automation-over-documentation guidance", "version": "3.6.2", "author": { "name": "Jesse Vincent", @@ -9,5 +9,5 @@ "homepage": "https://github.com/obra/superpowers", "repository": "https://github.com/obra/superpowers", "license": "MIT", - "keywords": ["skills", "tdd", "debugging", "collaboration", "best-practices", "workflows"] + "keywords": ["skills", "tdd", "debugging", "collaboration", "best-practices", "workflows", "writing-plans"] } diff --git a/.codex/superpowers-codex b/.codex/superpowers-codex index 1d9a0efb6..aec64c99d 100755 --- a/.codex/superpowers-codex +++ b/.codex/superpowers-codex @@ -1,9 +1,9 @@ #!/usr/bin/env node -const fs = require('fs'); -const path = require('path'); -const os = require('os'); -const skillsCore = require('../lib/skills-core'); +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import * as skillsCore from '../lib/skills-core.js'; // Paths const homeDir = os.homedir(); diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..e25006586 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,248 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a **Claude Code plugin** that provides a core skills library. Unlike traditional Node.js projects, this repository contains **no build system, no package.json, and no traditional test suite**. Skills are markdown documentation files that are loaded directly by Claude Code. + +**Key Architecture:** + +- `skills/` - 21 skills organized by category (testing, debugging, collaboration, meta) +- `commands/` - Slash commands that activate corresponding skills +- `agents/` - Agent definitions (e.g., code-reviewer) +- `hooks/` - Session lifecycle hooks (auto-loads skills system) +- `.claude-plugin/` - Plugin manifest and marketplace configuration +- `llm/` - Local-only planning documents (NOT tracked by git) + +## Development Workflow + +### Creating New Skills + +**Don't improvise.** Use the meta-skills that define the process: + +1. **Read `skills/writing-skills/SKILL.md` first** - Complete TDD methodology for documentation +2. **Test with `skills/testing-skills-with-subagents/SKILL.md`** - Run baseline tests, verify skill fixes violations +3. **Contribute with `skills/sharing-skills/SKILL.md`** - Fork, branch, test, PR workflow + +**TDD for Documentation Approach:** + +- RED: Run baseline with subagent (without skill) - observe failures +- GREEN: Write skill that addresses violations - verify compliance +- REFACTOR: Close loopholes, tighten language against rationalization + +### Local Plugin Development + +When developing superpowers locally and testing changes in Claude Code: + +1. **Edit skills** in the repository's `skills/` directory +2. **Commit changes** to your branch +3. **Reload plugin** to reflect changes in Claude Code (paste both lines): + ```bash + /plugin uninstall superpowers + /plugin install superpowers + ``` +4. **Test changes** in a new Claude Code session + +**Important:** Plugin changes only take effect after reload. Skills are loaded at session start, so existing sessions won't see updates. + +### PR Creation Safety + +**Approval pattern:** finishing-a-development-branch skill enforces preview-then-confirm for PR creation. + +**Expected flow:** +1. User selects option 2 (Push and create PR) +2. Claude pushes branch +3. Claude shows PR title/body preview +4. Claude asks: "Create this PR? (yes/no)" +5. User must type "yes" to proceed + +**Defense-in-depth:** +- Skill-level approval gate (primary) +- Permission rules in ~/.claude/settings.json (secondary) +- Permission system prompts on first use (tertiary) + +**Similar patterns:** +- jira-publisher skill (safety-critical approval gates) +- Option 4 discard confirmation (typed "discard" required) + +### Local Development with Worktrees + +**Use git worktrees to test changes in isolation while keeping main stable.** + +**Creating a worktree for experiments:** + +```bash +# From main repository +cd /path/to/superpowers + +# Create worktree with new branch +git worktree add .worktrees/feature-name -b feature/my-experiment + +# Work in worktree +cd .worktrees/feature-name +# Edit skills, commit changes, then reload plugin: +/plugin uninstall superpowers +/plugin install superpowers +# (Paste both lines together) +``` + +**When satisfied with changes:** + +```bash +# Return to main +cd /path/to/superpowers + +# Merge feature branch +git merge feature/my-experiment + +# Remove worktree +git worktree remove .worktrees/feature-name + +# Delete feature branch (optional) +git branch -d feature/my-experiment +``` + +**Benefits:** +- Main directory always stable +- Test changes in isolation without affecting running Claude sessions +- Run multiple worktrees for parallel experiments +- Easy cleanup when done + +### Skill Structure Requirements + +**Directory and Naming:** + +```plaintext +skills/ + skill-name/ # lowercase-with-hyphens only (no special chars) + SKILL.md # Required: main skill content + example.ts # Optional: reusable tool/example code + scripts/ # Optional: supporting utilities + reference-docs.md # Optional: heavy reference material +``` + +**Frontmatter (required in SKILL.md):** + +```yaml +--- +name: skill-name # Must match directory name exactly +description: Use when [trigger] - [what it does] # Appears in skill list +--- +``` + +**Supporting Files Patterns:** + +- Self-contained skill → Only `SKILL.md` +- Skill with reusable tool → `SKILL.md` + `example.ts` (see `condition-based-waiting`) +- Skill with heavy reference → `SKILL.md` + reference docs + `scripts/` (see `root-cause-tracing`) + +### Skill Types and Treatment + +1. **Discipline-Enforcing Skills** (e.g., `test-driven-development`, `verification-before-completion`) + + - Contain rigid rules tested against pressure scenarios + - Follow exactly - don't adapt away the discipline + +2. **Technique Skills** (e.g., `condition-based-waiting`, `root-cause-tracing`) + + - How-to guides with concrete steps + +3. **Pattern Skills** (e.g., `brainstorming`, `systematic-debugging`) + + - Mental models and flexible patterns - adapt to context + +4. **Reference Skills** + - Heavy documentation, APIs, guides + +The skill itself indicates which type it is. + +## Testing Skills + +**No traditional test suite exists.** Skills are tested using: + +1. **Subagent Testing** - Spawn subagents with/without skill, compare behavior +2. **Pressure Scenarios** - Test if agents comply when tempted to skip steps +3. **Baseline Testing** - Run without skill to demonstrate violations +4. **TDD Cycle** - Iteratively tighten language to close loopholes + +See `skills/testing-skills-with-subagents/SKILL.md` for complete methodology. + +## Contributing Workflow + +**Standard fork-based workflow:** + +1. Fork repository (if you have `gh` CLI configured) +2. Create feature branch: `add-skillname-skill` or `improve-skillname-skill` +3. Create/edit skill following `writing-skills` guidelines +4. Test with subagents to verify behavior changes +5. Commit with clear message (avoid mentioning "Claude" in commits) +6. Push to your fork +7. Create PR to upstream + +**Branch off `main`** - this is the primary branch. + +## Version Management + +Update plugin version in `.claude-plugin/plugin.json`: + +```json +{ + "name": "superpowers", + "version": "X.Y.Z", + ... +} +``` + +Follow semantic versioning: + +- MAJOR: Breaking changes to skill interfaces +- MINOR: New skills, backward-compatible improvements +- PATCH: Bug fixes, documentation improvements + +## Important Notes + +**llm/ Directory:** + +- Contains local-only planning documents +- NOT tracked by git (per `.gitignore`) +- Safe for scratch notes, implementation plans +- Do NOT reference these files in skills or commits + +**Skill References:** + +- Skills are namespace-qualified: `superpowers:skill-name` +- Use slash commands to activate: `/superpowers:brainstorm` +- Session hook auto-loads `using-superpowers` at startup + +**No Legacy Systems:** + +- Skills overlay system removed in v2.0 +- First-party skills system adopted in v3.0 +- No backward compatibility with old skill formats + +## Key Reference Files + +**Essential reading for contributors:** + +- `skills/writing-skills/SKILL.md` - How to create effective skills +- `skills/using-superpowers/SKILL.md` - How the skills system works +- `skills/testing-skills-with-subagents/SKILL.md` - Testing methodology +- `skills/sharing-skills/SKILL.md` - Contributing workflow +- `README.md` - Installation, quick start, skills overview + +**Example skills demonstrating patterns:** + +- `skills/systematic-debugging/SKILL.md` - Complex skill with flowchart (Graphviz DOT notation) +- `skills/condition-based-waiting/` - Skill with supporting TypeScript example +- `skills/brainstorming/SKILL.md` - Command-activated skill with clear triggers + +**Supporting documentation in writing-skills:** + +- `anthropic-best-practices.md` - Official Anthropic skill authoring guide +- `graphviz-conventions.dot` - Flowchart style rules +- `persuasion-principles.md` - Psychology of effective documentation + +## Philosophy + +Skills are TDD for documentation. Write tests (baseline runs) first, then write documentation that makes tests pass. Iterate to close loopholes where agents rationalize around requirements. The result: battle-tested process documentation that actually changes agent behavior. diff --git a/README.md b/README.md index 98436c9dc..ec06ba71d 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ Next up, once you say "go", it launches a *subagent-driven-development* process, There's a bunch more to it, but that's the core of the system. And because the skills trigger automatically, you don't need to do anything special. Your coding agent just has Superpowers. +> **Fork Note:** This is a compatibility-focused fork of [obra/superpowers](https://github.com/obra/superpowers) with production-tested improvements including mechanical enforcement and safety gates. All existing workflows and commands work unchanged. [See what's different →](docs/IMPROVEMENTS.md) + ## Sponsorship @@ -110,7 +112,7 @@ Fetch and follow instructions from https://raw.githubusercontent.com/obra/superp - **verification-before-completion** - Ensure it's actually fixed - **defense-in-depth** - Multiple validation layers -**Collaboration** +**Collaboration** - **brainstorming** - Socratic design refinement - **writing-plans** - Detailed implementation plans - **executing-plans** - Batch execution with checkpoints diff --git a/docs/IMPROVEMENTS.md b/docs/IMPROVEMENTS.md new file mode 100644 index 000000000..32eb8aaa1 --- /dev/null +++ b/docs/IMPROVEMENTS.md @@ -0,0 +1,57 @@ +# Production-Tested Improvements + +This fork of [obra/superpowers](https://github.com/obra/superpowers) adds mechanical enforcement and safety gates developed through real-world usage. + +## Compatibility + +All existing Superpowers workflows, commands, and skills work unchanged. Users familiar with the original can maintain their existing workflows. + +## Key Improvements + +### 1. Writing-Plans Automation Framework + +Enhances the existing `writing-plans` skill with executable wrapper scripts that enforce plan creation through lock files and git pre-commit hooks. + +- **Lock file mechanism** ensures plans exist before coding begins +- **Git pre-commit hooks** prevent commits without corresponding implementation plans +- **Shifts from documentation to mechanical constraints** - addresses incidents where agents rationalized away requirements + +### 2. Automation Over Documentation Framework + +A new guidance document establishing when to use code vs. documentation for skill requirements. + +- **Clear decision criteria**: objective, programmatically detectable requirements should trigger automation +- **Reduces documentation churn** - when violations are 100% detectable, use automation +- **Framework for future skill development** - establishes pattern for similar enforcement needs + +### 3. PR Creation Safety Gate + +Adds mandatory user preview and approval before pull request creation in the `finishing-a-development-branch` skill. + +- **Defense-in-depth approach**: skill-level approval gate + permission rules + system prompts +- **Preview-then-confirm pattern** prevents bypassing approval workflows through rationalization +- **Follows pattern from `jira-publisher` skill** - proven safety-critical workflow + +### 4. Continuous Execution Pattern + +Removes artificial 3-task batching checkpoints from `executing-plans`, allowing uninterrupted workflow through multiple tasks. + +- **Improved development velocity** - no artificial pauses +- **Only genuine blockers cause pauses** - maintains quality gates +- **Preserves all safety checks** - verification still runs at appropriate times + +### 5. Writing-Skills Governance + +Strengthens skill modification governance with enhanced discoverability and rationalization counters. + +- **Enhanced discoverability** of the writing-skills meta-skill +- **Rationalization counters** against "just adding a section" shortcuts +- **Clear guidance on TDD treatment vs. quick updates** - when skills need full testing methodology + +## Philosophy + +**Automation over documentation** for objective constraints, while preserving judgment-based guidance for subjective decisions. Skills are TDD for documentation—we test first, then write documentation that makes tests pass, iterating to close loopholes where agents rationalize around requirements. + +--- + +[View upstream project](https://github.com/obra/superpowers) diff --git a/scripts/reload-plugin.sh b/scripts/reload-plugin.sh new file mode 100755 index 000000000..173552f50 --- /dev/null +++ b/scripts/reload-plugin.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# +# reload-plugin.sh - Helper for reloading superpowers plugin in Claude Code +# +# This script outputs the slash commands needed to reload the superpowers plugin +# after making changes. Copy and paste both commands into Claude Code. +# +# Usage: +# ./scripts/reload-plugin.sh +# rls # if you have the alias set up +# + +# Extract marketplace name from marketplace.json +MARKETPLACE=$(jq -r '.name' "$(dirname "$0")/../.claude-plugin/marketplace.json" 2>/dev/null || echo "superpowers-dev") + +echo "==> Superpowers Plugin Reload Commands" +echo "" +echo "Copy and paste these into Claude Code (paste both lines at once):" +echo "" +echo "/plugin uninstall superpowers@${MARKETPLACE}" +echo "/plugin install superpowers@${MARKETPLACE}" +echo "" +echo "IMPORTANT: After reload, start a new session to see changes." diff --git a/skills/executing-plans/SKILL.md b/skills/executing-plans/SKILL.md index 47e545a67..f93ed00c0 100644 --- a/skills/executing-plans/SKILL.md +++ b/skills/executing-plans/SKILL.md @@ -1,15 +1,15 @@ --- name: executing-plans -description: Use when partner provides a complete implementation plan to execute in controlled batches with review checkpoints - loads plan, reviews critically, executes tasks in batches, reports for review between batches +description: Use when partner provides a complete implementation plan to execute - loads plan, reviews critically, executes all tasks continuously, stops only for blockers or ambiguity, reports when complete --- # Executing Plans ## Overview -Load plan, review critically, execute tasks in batches, report for review between batches. +Load plan, review critically, execute all tasks continuously, report when complete or blocked. -**Core principle:** Batch execution with checkpoints for architect review. +**Core principle:** Continuous execution with stops only for blockers or ambiguity. **Announce at start:** "I'm using the executing-plans skill to implement this plan." @@ -21,30 +21,37 @@ Load plan, review critically, execute tasks in batches, report for review betwee 3. If concerns: Raise them with your human partner before starting 4. If no concerns: Create TodoWrite and proceed -### Step 2: Execute Batch -**Default: First 3 tasks** +### Step 2: Execute All Tasks Continuously -For each task: +Execute each task in sequence: 1. Mark as in_progress 2. Follow each step exactly (plan has bite-sized steps) 3. Run verifications as specified 4. Mark as completed +5. Move immediately to next task -### Step 3: Report -When batch complete: -- Show what was implemented -- Show verification output -- Say: "Ready for feedback." +**Continue executing until:** +- All tasks complete, OR +- You hit a blocker (see "When to Stop and Ask for Help" below) + +**Do NOT stop between tasks to ask for feedback unless blocked.** + +## Common Rationalizations for Stopping (Don't Do These) -### Step 4: Continue -Based on feedback: -- Apply changes if needed -- Execute next batch -- Repeat until complete +**STOP means blocked, not:** +- ❌ "Completed several tasks, should check in" +- ❌ "Next tasks look complex, should get feedback first" +- ❌ "Been working a while, should pause" +- ❌ "Want to show progress" +- ❌ "Significant milestone reached" -### Step 5: Complete Development +**Unless blocked or unclear: keep executing.** + +### Step 3: Report When Complete After all tasks complete and verified: +- Show what was implemented +- Show verification output - Announce: "I'm using the finishing-a-development-branch skill to complete this work." - **REQUIRED SUB-SKILL:** Use superpowers:finishing-a-development-branch - Follow that skill to verify tests, present options, execute choice @@ -52,7 +59,7 @@ After all tasks complete and verified: ## When to Stop and Ask for Help **STOP executing immediately when:** -- Hit a blocker mid-batch (missing dependency, test fails, instruction unclear) +- Hit a blocker mid-execution (missing dependency, test fails, instruction unclear) - Plan has critical gaps preventing starting - You don't understand an instruction - Verification fails repeatedly @@ -62,15 +69,15 @@ After all tasks complete and verified: ## When to Revisit Earlier Steps **Return to Review (Step 1) when:** -- Partner updates the plan based on your feedback -- Fundamental approach needs rethinking +- Partner updates the plan after you've reported blockers +- Fundamental approach needs rethinking mid-execution **Don't force through blockers** - stop and ask. ## Remember - Review plan critically first +- Execute all tasks continuously (don't pause between tasks) - Follow plan steps exactly - Don't skip verifications - Reference skills when plan says to -- Between batches: just report and wait -- Stop when blocked, don't guess +- Stop ONLY when blocked or need clarification, don't guess diff --git a/skills/finishing-a-development-branch/SKILL.md b/skills/finishing-a-development-branch/SKILL.md index c308b43b4..cf0c4d327 100644 --- a/skills/finishing-a-development-branch/SKILL.md +++ b/skills/finishing-a-development-branch/SKILL.md @@ -1,6 +1,6 @@ --- name: finishing-a-development-branch -description: Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup +description: Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion with structured options and explicit approval gates for destructive operations --- # Finishing a Development Branch @@ -88,17 +88,39 @@ Then: Cleanup worktree (Step 5) #### Option 2: Push and Create PR +**Step 1: Push branch** + ```bash -# Push branch git push -u origin +``` -# Create PR -gh pr create --title "" --body "$(cat <<'EOF' -## Summary -<2-3 bullets of what changed> +**Step 2: Generate PR description** + +Draft PR title and body based on commit history. + +**Step 3: Show preview and request confirmation** + +Present: +``` +Ready to create PR with: + +Title: <title> +Body: +<body-preview> + +Create this PR? (yes/no) +``` + +**Wait for explicit "yes" confirmation.** -## Test Plan -- [ ] <verification steps> +- If user confirms "yes": Proceed to Step 4 +- If user says "no": Report "PR creation cancelled. Branch pushed to remote." + +**Step 4: Create PR only if confirmed** + +```bash +gh pr create --title "<title>" --body "$(cat <<'EOF' +<body> EOF )" ``` @@ -176,18 +198,45 @@ git worktree remove <worktree-path> - **Problem:** Accidentally delete work - **Fix:** Require typed "discard" confirmation -## Red Flags +**Skipping PR preview** +- **Problem:** Create PR without showing user the content first +- **Fix:** Always show title/body, wait for "yes" confirmation + +## Rationalization Table + +Common excuses for skipping approval steps: + +| Excuse | Reality | +|--------|---------| +| "User selected option 2, that's consent" | Selection is consent to PR workflow, not blanket approval. Show preview first. | +| "Permission system will catch it" | Permission system only prompts ONCE per session. Skill must enforce approval. | +| "Preview adds unnecessary friction" | Safety > convenience. PR creation is public and permanent. | +| "This is different from Option 4" | PRs are as permanent as deleted code. Same approval rigor required. | +| "I'll describe what I'm creating" | Description ≠ approval. Must show preview AND wait for "yes". | + +## Red Flags - You're About to Skip Approval + +If you catch yourself thinking: +- "User picked option 2, I can proceed" → WRONG. Show preview first. +- "I'll explain what PR will contain" → WRONG. Show actual content, get confirmation. +- "Permission system is enough" → WRONG. Skill must enforce its own approval gate. +- "This adds too much friction" → WRONG. Safety is mandatory for public operations. + +**All of these mean: STOP. Show preview. Wait for "yes" confirmation.** **Never:** - Proceed with failing tests - Merge without verifying tests on result - Delete work without confirmation - Force-push without explicit request +- Create PR without showing preview first +- Skip confirmation step "to save time" **Always:** - Verify tests before offering options - Present exactly 4 options - Get typed confirmation for Option 4 +- Show preview and get "yes" for Option 2 - Clean up worktree for Options 1 & 4 only ## Integration diff --git a/skills/using-superpowers/SKILL.md b/skills/using-superpowers/SKILL.md index e58576f7f..51954e549 100644 --- a/skills/using-superpowers/SKILL.md +++ b/skills/using-superpowers/SKILL.md @@ -43,6 +43,7 @@ If you catch yourself thinking ANY of these thoughts, STOP. You are rationalizin - "This doesn't count as a task" → WRONG. If you're taking action, it's a task. Check for skills. - "The skill is overkill for this" → WRONG. Skills exist because simple things become complex. Use it. - "I'll just do this one thing first" → WRONG. Check for skills BEFORE doing anything. +- "Just editing a skill file" → WRONG. Use writing-skills. Skills are code. Same TDD rules apply. **Why:** Skills document proven techniques that save time and prevent mistakes. Not using available skills means repeating solved problems and making known errors. diff --git a/skills/writing-plans/CHANGELOG.md b/skills/writing-plans/CHANGELOG.md new file mode 100644 index 000000000..e285f1aaa --- /dev/null +++ b/skills/writing-plans/CHANGELOG.md @@ -0,0 +1,60 @@ +# Changelog - writing-plans Skill + +## 2025-12-16 - Skill Name Reversion + +**Changed:** Reverted skill name from `record-plan` back to `writing-plans` + +**Reason:** +- Align with upstream naming for PR 170 submission +- Fork identity already established via plugin.json (`superpowers-fork`) +- Zero backward compatibility concerns (no external users yet) +- Reduces diff noise in PR review +- Easier upstream integration if mechanical enforcement improvements are accepted + +**Impact:** +- Slash command unchanged: `/write-plan` (always used same command) +- All mechanical enforcement logic preserved (wrapper scripts, lock files, validation) +- Only naming changed - no functional differences + +**Migration:** None needed (no external users) + +## 2025-12-12 - file-track Integration + +### Changed +- Date format now YYMMDDXX (8 digits) instead of MMDDXX (6 digits) +- Tight coupling with file-track CLI for automatic tracking + +### Added +- `scripts/track_with_filetrack.sh` - Integration script for file-track +- `scripts/tests/test_track_integration.sh` - Integration tests +- Automatic file-track invocation after rename in `rename_jot.py` +- Post-write workflow Step 3.5 for file-track tracking + +### Migration +- No migration needed for existing files +- New files will use YYMMDDXX format going forward + +## 2025-12-11 - Executable Wrapper + +### Added +- Executable wrapper script (scripts/write_plan.py) that forces file writing +- Shared script infrastructure in ~/.claude/scripts/record-tools/ +- "Red Flags" section to SKILL.md to prevent common rationalizations + +### Changed +- Skill now uses executable wrapper instead of documentation-only approach +- Script paths updated to use shared record-tools location +- Wrapper output strengthened with critical warnings + +### Fixed +- Bug where Claude would describe plans instead of writing them + +### Removed +- Duplicate validate-frontmatter.py and rename_jot.py (now shared) + +## Migration Notes + +**For users of old version:** +- Old behavior: Skill was documentation Claude sometimes followed +- New behavior: Skill invokes wrapper that FORCES correct workflow +- No breaking changes to file formats or workflows diff --git a/skills/writing-plans/SKILL.md b/skills/writing-plans/SKILL.md index b26b9d15e..90fbd9165 100644 --- a/skills/writing-plans/SKILL.md +++ b/skills/writing-plans/SKILL.md @@ -1,6 +1,6 @@ --- name: writing-plans -description: Use when design is complete and you need detailed implementation tasks for engineers with zero codebase context - creates comprehensive implementation plans with exact file paths, complete code examples, and verification steps assuming engineer has minimal domain knowledge +description: Use when design is complete and you need detailed implementation tasks - creates comprehensive plans with exact file paths, complete code examples, and verification steps. CRITICAL - invokes wrapper script that forces file writing - "create a plan" means invoke wrapper and write file, NOT describe in chat. SCOPE - this skill ONLY writes plans, never executes them. Mechanically enforced via lock file (attempting bypass = error). --- # Writing Plans @@ -13,9 +13,53 @@ Assume they are a skilled developer, but know almost nothing about our toolset o **Announce at start:** "I'm using the writing-plans skill to create the implementation plan." +**FIRST ACTION (mandatory):** Invoke wrapper script - DO NOT describe plan in chat first: + +**Step 1: Locate the script** +```bash +find ~/.claude/plugins/cache -path "*/skills/writing-plans/scripts/write_plan.py" 2>/dev/null | head -1 +``` + +**Step 2: Invoke with the path from Step 1** +```bash +python3 <path-from-step-1> \ + --working-dir <working-directory> \ + --plan-name <descriptive-name> +``` + +**Note:** Command substitution `$(...)` doesn't work in Bash tool execution environment, so use two-step approach. + +**Mechanical enforcement:** Wrapper creates lock file enabling Write tool for implementation plans. Attempting to write without invoking wrapper will fail. + +**Production incident:** 2025-12-13 - Agent skipped wrapper despite warnings. File never registered with file-track. Now mechanically enforced via lock file pattern. + +**DO NOT before invoking wrapper:** +- Describe plan content in chat +- "Show" the plan structure +- Output plan deliverables/tasks +- List what the plan will contain + +**"Create a plan" = invoke wrapper script immediately. Nothing else.** + +**Execution Mode:** This skill has an executable wrapper that FORCES file writing. + +**How it works:** +1. You invoke the wrapper script: `write_plan.py` (auto-located in plugin cache) +2. The wrapper prints directives: "USE WRITE TOOL to create file at X" +3. You MUST follow the directives - no describing, only executing +4. The wrapper guides you through post-write workflow + **Context:** This should be run in a dedicated worktree (created by brainstorming skill). -**Save plans to:** `docs/plans/YYYY-MM-DD-<feature-name>.md` +**Save plans to:** +- **In plan mode:** Write to `~/.claude/plans/<plan-name>.md` (staging area, as specified by plan mode system prompt) +- **In regular mode:** Write to `<working-directory>/<target-dir>/<plan-name>.md` + - Default: `<working-directory>/llm/implementation-plans/<plan-name>.md` + - Configurable via `--target-dir` parameter + +**Note:** The target directory structure is workflow-specific. The default assumes an `llm/` directory pattern, but this can be customized for projects with different conventions. + +**Note:** After writing, the file will be renamed to `YYMMDDXX-<slug>.md` format by the rename script (where YYMMDD is year/month/day, XX is auto-sequenced). If written to staging area, it must be copied to the working directory's target directory before rename. ## Bite-Sized Task Granularity @@ -44,6 +88,94 @@ Assume they are a skilled developer, but know almost nothing about our toolset o --- ``` +**IMPORTANT:** The "For Claude" instruction above is FOR THE EXECUTOR (future session using executing-plans), NOT for writing-plans. When you write this header, you are creating instructions for a future Claude - not instructions for yourself. + +## File Requirements + +**Every plan file MUST include these elements:** + +1. **First line:** `<!-- jot:md-rename -->` (required for rename script detection) + +2. **YAML frontmatter** (required for metadata and indexing): + ```yaml + --- + title: Clear, descriptive title + date: YYYY-MM-DD # Current date + type: implementation-plan + status: draft # Or: active, completed, archived + tags: [relevant, tags, here] + project: PROJECT-KEY # Optional: e.g., NPCP-2495 + phase: ep001 # Optional: project phase + --- + ``` + +3. **H1 heading:** Feature name + +4. **Header section:** Goal, Architecture, Tech Stack (as shown above) + +**If a Jira ticket is referenced** (e.g., NPCP-1234), it will be included at the beginning of the final filename: `YYMMDDXX-NPCP-1234-<slug>.md` + +## Path Requirements + +- ✅ **ALWAYS use absolute paths**: `<working-directory>/<target-dir>/file.md` +- ❌ **NEVER use relative paths**: `llm/implementation-plans/file.md` +- **Default target directory**: `llm/implementation-plans/` (can be overridden with `--target-dir`) +- **Git repository awareness**: Files are tracked relative to repository root, handling nested git repos correctly + +The working directory is shown as "Working directory" in the environment context at the start of each conversation. + +**Workflow flexibility:** While the default assumes an `llm/` subdirectory pattern, the scripts now support any directory structure within a git repository. Use `--target-dir` to specify custom locations (e.g., `docs/plans/`, `planning/implementation/`). + +**Nested git repositories:** If llm/ is its own git repository (has llm/.git), the tooling automatically finds the parent repository to ensure correct path tracking. + +## Repository Detection + +The writing-plans scripts automatically detect the git repository root and handle nested git repositories: + +**Nested llm/ repositories:** If your llm/ directory is its own git repository (common pattern for keeping ephemeral docs separate), the scripts automatically skip past it to find the parent repository. This ensures file paths are tracked relative to the main project repository, not the nested llm/ repo. + +**Example:** +- Working in `/Users/name/project/.claude/llm/plans/` +- llm/ has its own `.git` directory (nested repo) +- Scripts find parent `/Users/name/project/.claude/` (main repo) +- Paths reported as `llm/plans/file.md` (relative to main repo) + +**Custom usage:** +```bash +# Step 1: Find script path +find ~/.claude/plugins/cache -path "*/skills/writing-plans/scripts/write_plan.py" 2>/dev/null | head -1 + +# Step 2: Invoke with custom target directory +python3 <path-from-step-1> \ + --working-dir /path/to/repo \ + --plan-name my-feature \ + --target-dir docs/architecture +``` + +This flexibility allows the writing-plans skill to work with different project organizational conventions while maintaining backward compatibility with existing "llm/" workflows. + +## Enforcement Mechanism + +**Lock file pattern:** +1. Wrapper creates `.writing-plans-active` in working directory +2. Lock file contains authorized file path +3. Write tool can only create plan if lock exists (future: full integration) +4. Rename script removes lock after complete workflow + +**Current enforcement layers:** +- Lock file created by wrapper (implemented) +- Git pre-commit hook catches format violations (implemented) +- Future: Write tool gating for complete prevention + +**Manual check (optional):** +```bash +# Step 1: Find script +find ~/.claude/plugins/cache -path "*/skills/writing-plans/scripts/check_lock.py" 2>/dev/null | head -1 + +# Step 2: Run check +python3 <path-from-step-1> <working-dir> <file-path> +``` + ## Task Structure ```markdown @@ -94,23 +226,287 @@ git commit -m "feat: add specific feature" - Reference relevant skills with @ syntax - DRY, YAGNI, TDD, frequent commits -## Execution Handoff +## Red Flags - You're About to Violate the Skill + +**Why you're reading this section:** You already rationalized around skill boundaries. + +**Two violation types:** + +### Violation 1: Not writing the plan file + +**Stop. Delete any plan content you wrote. Go back and invoke wrapper script.** + +If you caught yourself thinking: +- "I'll describe the plan structure first" → You're already violating. Stop now. +- "Let me show the plan content" → You're already violating. Stop now. +- "The wrapper is just guidance" → WRONG. The wrapper is mandatory. +- "I can write without invoking wrapper" → WRONG. Wrapper ensures correct workflow. +- "Plan is simple, skip wrapper" → WRONG. Wrapper prevents the bug this plan fixes. +- "Create a plan" means output in chat → WRONG. "Create" means invoke wrapper. + +**Production incident:** 2025-12-13 - Agent described entire plan in chat instead of writing file. User had to explicitly correct: "You need to write that plan file." + +**All of these mean: Delete any plan content. Invoke wrapper script. Follow its directives exactly.** + +### Violation 2: Executing the plan after writing + +**Stop. writing-plans does NOT execute plans.** + +If you caught yourself thinking: +- "Plan header says use executing-plans, so I should execute" → WRONG. That's for the EXECUTOR, not writing-plans. +- "User asked to create plan for task 2, so I should do task 2" → WRONG. Create = write plan only. +- "Plan already exists, let me execute it" → WRONG. writing-plans writes, never executes. +- "I'll just start the first task" → WRONG. STOP after writing. + +**Production incident:** 2025-12-13 - Agent saw existing plan, decided to execute using superpowers:execute-plan. User had to interrupt: "This is a bug... writing-plans should write and STOP." + +**Scope boundaries:** +- writing-plans = WRITE plans only +- executing-plans = EXECUTE plans only +- These are separate skills. Never cross boundaries. + +## Post-Write Workflow + +After writing the plan file, MUST complete these steps: + +### Step 0: Copy from Staging (if in plan mode) + +**How to know if you're in plan mode:** Check the system prompt at conversation start. If it specifies a path like `~/.claude/plans/<name>.md`, you're in plan mode. + +**If file was written to `~/.claude/plans/`** (plan mode staging area), copy it to the working directory: + +```bash +# Ensure target directory exists +mkdir -p <working-directory>/llm/implementation-plans + +# Copy from staging to final location +cp ~/.claude/plans/<plan-name>.md <working-directory>/llm/implementation-plans/<plan-name>.md +``` + +**All subsequent steps operate on the file in `<working-directory>/llm/implementation-plans/`**, not the staging copy. + +**If file was written directly to `<working-directory>/llm/implementation-plans/`**, skip this step. + +**Note:** The staging copy in `~/.claude/plans/` can remain (user may want it for reference), or be deleted with `rm ~/.claude/plans/<plan-name>.md` if preferred. + +### Step 0.5: Initialize Progress Log (if needed) + +**Check if progress.md exists:** +```bash +test -f <working-directory>/llm/progress.md && echo "EXISTS" || echo "MISSING" +``` + +**If output is "MISSING"**, you MUST copy the template (do NOT create your own format): + +```bash +# Verify template exists first +if [ ! -f ~/.claude/templates/progress.md ]; then + echo "ERROR: Template not found at ~/.claude/templates/progress.md" + echo "Contact your human partner - Task 1 must be completed first" + exit 1 +fi + +# Copy template AS-IS - do NOT create custom format +cp ~/.claude/templates/progress.md <working-directory>/llm/progress.md +echo "✓ Copied template to <working-directory>/llm/progress.md" +``` + +**IMPORTANT:** You must use `cp` command to copy the template. Do NOT: +- Create your own custom progress.md format +- Write a new file from scratch +- "Improve" the template structure +- Use Write tool to create progress.md + +The template has a specific structure for cross-session state. Use it exactly as-is. + +**After copying, fill in the template placeholders:** + +Use Read tool to view the template, then Edit tool to fill in: +1. Replace plan path placeholder with actual path (you'll know this after rename step) +2. Fill in `Branch:` with current git branch (`git branch --show-current`) +3. Fill in `Last Commit:` with current commit SHA (`git rev-parse --short HEAD`) +4. Replace `YYYY-MM-DD` with today's date +5. Add initial session goal and task + +**If output is "EXISTS"**, skip this step entirely - progress.md already initialized. + +**Why this matters:** Consistent template structure ensures future Claude instances can parse cross-session state reliably. + +### Step 1: Validate Frontmatter + +```bash +# Find script +find ~/.claude/plugins/cache -path "*/skills/writing-plans/scripts/validate-frontmatter.py" 2>/dev/null | head -1 + +# Run validation +python3 <path-from-above> <absolute-path-to-written-file> +``` + +Expected output: `✓ Frontmatter validation passed` + +If validation fails: +- Fix the reported errors in the frontmatter +- Re-run validation until it passes + +### Step 2: Invoke Rename Script + +```bash +# Find script +find ~/.claude/plugins/cache -path "*/skills/writing-plans/scripts/rename_jot.py" 2>/dev/null | head -1 + +# Run rename +python3 <path-from-above> <absolute-path-to-written-file> +``` + +The script will: +- Rename file to `YYMMDD-XX-slug.md` format (where YYMMDD is year/month/day, XX is sequence number) +- Automatically track with file-track if available (silent fallback if not installed) + +Expected output: +``` +✓ Renamed plan-name.md → 251213-01-plan-name.md +``` + +**Note:** File tracking is automatic. Use `file-track` (TUI) or `file-track list` to browse created files. + +### Step 3: Generate Acceptance Criteria (Optional) + +**Check if user wants acceptance tracking:** + +Ask: "Would you like to generate acceptance.json for this plan? (enables regression testing and progress tracking)" + +**If YES:** + +1. **Generate acceptance.json from plan structure:** + ```bash + python3 ~/.claude/skills/writing-plans/scripts/generate_acceptance.py \ + --plan-file <working-directory>/llm/implementation-plans/<renamed-file>.md \ + --output <working-directory>/llm/acceptance.json + ``` + +2. **Validate generated file:** + ```bash + ~/.claude/templates/validate-acceptance.sh <working-directory>/llm/acceptance.json + ``` + + Expected output: `✓ Validation passed` + +**If NO:** Skip this step - acceptance.json can be added later if needed. + +**Why optional:** Not all plans need acceptance tracking. Use for: +- Multi-session feature work +- Complex implementations with many sub-tasks +- When regression testing is critical + +## Common Mistakes + +### Mistake 1: Operating on staging file after copy +**Problem:** Running validation/rename on `~/.claude/plans/<name>.md` instead of `<working-directory>/llm/implementation-plans/<name>.md` + +**Fix:** After Step 0 copy, ALL subsequent steps use the file in `llm/implementation-plans/`, not the staging copy + +### Mistake 2: Forgetting to copy in plan mode +**Problem:** Validating/renaming staging file, then wondering why it's not in the correct location + +**Fix:** Always check system prompt at conversation start. If in plan mode, Step 0 is mandatory + +### Mistake 3: Using relative paths +**Problem:** Writing to `llm/implementation-plans/file.md` instead of absolute path + +**Fix:** Always use `<working-directory>/llm/implementation-plans/file.md` where `<working-directory>` is from environment context + +### Mistake 4: Skipping frontmatter validation +**Problem:** Rename script fails with cryptic errors due to invalid frontmatter + +**Fix:** ALWAYS run validation (Step 1) before rename (Step 2). Fix all errors before proceeding + +### Mistake 5: Trying to use command substitution in one line +**Problem:** Getting parse errors like `(eval):1: parse error near '('` when trying to use `SCRIPT_PATH=$(find...)` pattern + +**Root cause:** The Bash tool passes commands through shell evaluation that doesn't support command substitution `$(...)` syntax + +**Fix:** Use the two-step approach documented throughout this skill: +1. Run `find` command to get the path +2. Copy the path and use it in the next command + +**Alternative (if desperate):** Wrap in bash: `bash -c 'SCRIPT_PATH=$(find...); python3 "$SCRIPT_PATH" ...'` + +## Troubleshooting + +### Script Path Issues + +**Symptom:** Error like "can't open file '/skills/writing-plans/scripts/write_plan.py'" + +**Cause:** Script path variable is empty or malformed + +**Solution:** +1. Verify script exists: `find ~/.claude/plugins/cache -path "*/skills/writing-plans/scripts/write_plan.py"` +2. Check output - should show one absolute path +3. If no path found: plugin not installed correctly - reinstall from marketplace +4. If path found: use that literal path in python3 command + +**Symptom:** Parse error with parentheses: `parse error near '('` + +**Cause:** Shell evaluation doesn't support `$(...)` command substitution + +**Solution:** Use two-step approach (documented throughout skill) + +### Lock File Issues + +**Symptom:** "Lock file not found" or permission errors + +**Solution:** +1. Verify wrapper script was invoked first (creates lock file) +2. Check `.writing-plans-active` exists in working directory +3. If missing: must invoke wrapper before Write tool + +## STOP: Plan Writing Complete + +**After completing post-write workflow, STOP. Do NOT execute the plan.** + +**This skill's scope:** +- ✅ Write implementation plans +- ✅ Complete post-write workflow (validate, rename, track) +- ❌ **NOT** execute plans +- ❌ **NOT** dispatch subagents to implement +- ❌ **NOT** use executing-plans or subagent-driven-development + +**Report to user:** + +``` +Plan complete: llm/implementation-plans/<filename>.md + +Next step: Use /superpowers:execute-plan OR open new session with executing-plans skill. + +[STOP - writing-plans skill scope ends here] +``` + +**Common Rationalization:** + +"The plan header says 'REQUIRED SUB-SKILL: Use executing-plans' - I should execute it now" -After saving the plan, offer execution choice: +**Reality:** That instruction is FOR THE EXECUTOR (future Claude session), NOT for this skill. writing-plans ONLY writes, never executes. -**"Plan complete and saved to `docs/plans/<filename>.md`. Two execution options:** +**Production incident:** 2025-12-13 - Agent saw existing plan, decided to execute it instead of stopping. User had to interrupt: "This is a bug... writing-plans should write and STOP." -**1. Subagent-Driven (this session)** - I dispatch fresh subagent per task, review between tasks, fast iteration +## Testing Verification -**2. Parallel Session (separate)** - Open new session with executing-plans, batch execution with checkpoints +**Date:** 2025-12-13 +**Approach:** Mechanical enforcement (automation-over-documentation.md) +**Method:** Lock file pattern + git pre-commit hook -**Which approach?"** +**Test results:** +- ✅ Normal flow (wrapper → write → rename): Lock created and removed correctly +- ✅ Violation attempt (skip wrapper): Git hook rejects commit with clear error +- ✅ Manual validation (check_lock.py): Correctly identifies missing lock +- ✅ Error messages: Clear guidance on correct usage -**If Subagent-Driven chosen:** -- **REQUIRED SUB-SKILL:** Use superpowers:subagent-driven-development -- Stay in this session -- Fresh subagent per task + code review +**Evidence for automation approach:** +- Previous violation (2025-12-13) despite strong documentation warnings +- automation-over-documentation.md: "Mechanical constraints belong in code" +- Cost-benefit: 30 min implementation vs 2-3 hours iterating documentation -**If Parallel Session chosen:** -- Guide them to open new session in worktree -- **REQUIRED SUB-SKILL:** New session uses superpowers:executing-plans +**Enforcement layers:** +1. Lock file (primary - prevents unauthorized writes) +2. Validation script (agent self-check) +3. Git hook (catches violations at commit time) diff --git a/skills/writing-plans/scripts/check_lock.py b/skills/writing-plans/scripts/check_lock.py new file mode 100755 index 000000000..ade0028cd --- /dev/null +++ b/skills/writing-plans/scripts/check_lock.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""Check if writing-plans lock file exists before writing.""" + +import os +import sys + +def check_lock(working_dir: str, file_path: str) -> bool: + """Check if lock file exists and authorizes this file.""" + lock_file = os.path.join(working_dir, '.writing-plans-active') + + if not os.path.exists(lock_file): + print("❌ ERROR: No active writing-plans session") + print("MUST invoke wrapper first:") + print(" python3 ~/.claude/skills/writing-plans/scripts/write_plan.py \\") + print(f" --working-dir {working_dir} \\") + print(" --plan-name <descriptive-name>") + return False + + try: + # Check if lock authorizes this specific file + with open(lock_file, encoding='utf-8') as f: + authorized_path = f.readline().strip() + except (OSError, IOError) as e: + print(f"❌ ERROR: Cannot read lock file: {e}") + return False + + # Normalize both paths and compare full paths (not just basename) + authorized_norm = os.path.normpath(os.path.abspath(authorized_path)) + file_norm = os.path.normpath(os.path.abspath(file_path)) + + if authorized_norm != file_norm: + print(f"❌ ERROR: Lock file authorizes {authorized_path}, not {file_path}") + return False + + print(f"✓ Lock file valid for {file_path}") + return True + +if __name__ == "__main__": + if len(sys.argv) != 3: + print("Usage: check_lock.py <working_dir> <file_path>") + sys.exit(1) + + working_dir = sys.argv[1] + file_path = sys.argv[2] + + sys.exit(0 if check_lock(working_dir, file_path) else 1) diff --git a/skills/writing-plans/scripts/generate_acceptance.py b/skills/writing-plans/scripts/generate_acceptance.py new file mode 100755 index 000000000..b58f8c11a --- /dev/null +++ b/skills/writing-plans/scripts/generate_acceptance.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +""" +Generate acceptance.json from implementation plan markdown. + +Extracts tasks from plan and creates acceptance criteria structure. +""" +import argparse +import json +import re +from datetime import date +from pathlib import Path + +def extract_tasks(plan_content: str) -> list[dict]: + """Extract tasks from markdown plan.""" + features = [] + task_pattern = r'^### Task \d+: (.+)$' + + # Find all task headers + for line in plan_content.split('\n'): + match = re.match(task_pattern, line) + if match: + task_name = match.group(1) + features.append({ + "id": f"task-{len(features)+1:03d}", + "category": "functional", + "description": task_name, + "steps": [ + "Review task requirements", + "Implement changes", + "Test functionality", + "Verify completion" + ], + "passes": False, + "notes": "" + }) + + return features + +def generate_acceptance(plan_file: Path, output_file: Path): + """Generate acceptance.json from plan file.""" + plan_content = plan_file.read_text() + features = extract_tasks(plan_content) + + # Warn if no tasks were found + if not features: + print(f"WARNING: No tasks found in {plan_file.name}") + print(" Expected pattern: ### Task N: Description") + print(" Generating empty acceptance.json") + + acceptance = { + "plan": str(plan_file.relative_to(Path.cwd())), + "generated": date.today().isoformat(), + "total_features": len(features), + "passing_features": 0, + "features": features, + "_rules": { + "immutable_fields": ["id", "category", "description", "steps"], + "mutable_fields": ["passes", "notes"], + "catastrophic_actions": [ + "remove features", + "edit descriptions", + "modify steps" + ] + } + } + + output_file.write_text(json.dumps(acceptance, indent=2) + '\n') + print(f"✓ Generated {len(features)} acceptance criteria") + print(f" Output: {output_file}") + +def main(): + parser = argparse.ArgumentParser(description='Generate acceptance.json from plan') + parser.add_argument('--plan-file', required=True, type=Path) + parser.add_argument('--output', required=True, type=Path) + args = parser.parse_args() + + if not args.plan_file.exists(): + print(f"ERROR: Plan file not found: {args.plan_file}") + return 1 + + generate_acceptance(args.plan_file, args.output) + return 0 + +if __name__ == '__main__': + exit(main()) diff --git a/skills/writing-plans/scripts/install-hook.sh b/skills/writing-plans/scripts/install-hook.sh new file mode 100755 index 000000000..002fe7186 --- /dev/null +++ b/skills/writing-plans/scripts/install-hook.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# Install pre-commit hook for writing-plans enforcement + +HOOK_CONTENT='#!/bin/bash +# Pre-commit hook: Validate implementation plan format + +# Validate YYMMDD-XX format (6 digits, dash, 2 digits) +if git diff --cached --name-only | grep -qE "llm/implementation-plans/[^/]*\.md$" | grep -qvE "(^|/)[0-9]{6}-[0-9]{2}[^/]*\.md$"; then + echo "❌ ERROR: Implementation plan does not follow YYMMDD-XX format" + echo "" + echo "Files must be created via wrapper script:" + echo " python3 \"\${CLAUDE_SKILLS_DIR:-~/.claude/skills}/writing-plans/scripts/write_plan.py\" \\" + echo " --working-dir \$(pwd) \\" + echo " --plan-name <descriptive-name>" + echo "" + echo "Wrapper ensures:" + echo " - Correct YYMMDD-XX naming" + echo " - Frontmatter validation" + echo " - Automatic file-track registration" + exit 1 +fi +' + +# Default search path (override with REPOS_DIR environment variable) +SEARCH_PATH="${REPOS_DIR:-$HOME/dev}" + +# Find all repos with llm/implementation-plans/ directory +for repo in "$SEARCH_PATH"/*/; do + if [ -d "$repo/llm/implementation-plans" ] && [ -d "$repo/.git" ]; then + hook_path="$repo/.git/hooks/pre-commit" + + # Backup existing hook if present + if [ -f "$hook_path" ]; then + backup_path="${hook_path}.backup.$(date +%s)" + if cp "$hook_path" "$backup_path" 2>/dev/null; then + echo "⚠️ Backed up existing hook: $backup_path" + else + echo "❌ Failed to backup existing hook: $hook_path" + continue + fi + fi + + # Install new hook with error handling + if echo "$HOOK_CONTENT" > "$hook_path" 2>/dev/null && chmod +x "$hook_path" 2>/dev/null; then + echo "✓ Installed hook: $hook_path" + else + echo "❌ Failed to install hook: $hook_path" + fi + fi +done diff --git a/skills/writing-plans/scripts/rename_jot.py b/skills/writing-plans/scripts/rename_jot.py new file mode 100755 index 000000000..e2a413542 --- /dev/null +++ b/skills/writing-plans/scripts/rename_jot.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 +""" +Rename jot files to YYMMDD-XX-slug.md format. + +This script is invoked after writing a file via record-findings or writing-plans skills. +It ensures consistent naming and tracks files with file-track when available. + +Format: YYMMDD-XX-slug.md + - YYMMDD: 6-digit date (year, month, day) + - XX: 2-digit sequence number (01-99) + - slug: URL-safe title from H1 heading or filename + +Usage: + python3 rename_jot.py <path-to-markdown-file> + +Example: + python3 rename_jot.py /path/to/llm/implementation-plans/my-plan.md +""" +import sys +import os +import re +import datetime +import unicodedata +import subprocess +from pathlib import Path + + +# Try importing from file-track (canonical source) +_USE_FILETRACK_RENAME = False +try: + from file_track.rename import ( + rename_file as ft_rename_file, + slugify as ft_slugify, + extract_h1_title as ft_extract_h1_title, + get_next_sequence_number as ft_get_next_sequence_number, + ) + _USE_FILETRACK_RENAME = True +except ImportError: + pass # Fallback mode + + +# ============================================================================ +# FALLBACK IMPLEMENTATIONS (used only when file-track unavailable) +# ============================================================================ + +def _fallback_slugify(text: str) -> str: + """Convert text to a slug format (fallback implementation). + + Matches file-track's slugify behavior: + - Lowercase, ASCII-only + - Replace spaces/underscores with hyphens + - Remove special chars + - Collapse multiple hyphens + - Truncate to 50 chars + - Fallback to "untitled" + """ + # Normalize to ASCII + text = unicodedata.normalize("NFKD", text).encode("ascii", "ignore").decode("ascii") + + # Lowercase + text = text.lower() + + # Replace spaces and underscores with hyphens + text = text.replace(" ", "-").replace("_", "-") + + # Remove special chars (keep only alphanumeric and hyphens) + text = re.sub(r"[^a-z0-9-]+", "", text) + + # Collapse multiple hyphens + text = re.sub(r"-+", "-", text) + + # Strip leading/trailing hyphens + text = text.strip("-") + + # Truncate to 50 chars + if len(text) > 50: + text = text[:50].rstrip("-") + + return text or "untitled" + + +def _fallback_extract_h1_title(file_path: Path) -> str | None: + """Extract first H1 heading from markdown file (fallback implementation). + + Skips code blocks to avoid false matches. + """ + try: + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + + in_code_block = False + for line in content.split('\n'): + # Track code block boundaries + if line.strip().startswith('```'): + in_code_block = not in_code_block + continue + + # Skip lines inside code blocks + if in_code_block: + continue + + # Check for H1 heading + m = re.match(r'^#\s+(.+)$', line) + if m: + return m.group(1).strip() + + return None + except Exception: + return None + + +def _fallback_get_next_sequence(directory: Path, date_prefix: str) -> int: + """Get next sequence number for a given date prefix (fallback implementation). + + Scans directory for files matching YYMMDD-XX-* pattern. + Returns max_seq + 1 (with graceful degradation if > 99). + """ + max_seq = 0 + pattern = re.compile(rf"^{date_prefix}-(\d{{2}})-") + + try: + for name in os.listdir(directory): + m = pattern.match(name) + if m: + try: + seq = int(m.group(1)) + max_seq = max(max_seq, seq) + except ValueError: + pass + except FileNotFoundError: + pass + + next_seq = max_seq + 1 + + # Graceful degradation if > 99 + if next_seq > 99: + print(f"Warning: Sequence number exceeds 99 for {date_prefix}", file=sys.stderr) + next_seq = 99 + + return next_seq + + +def _fallback_rename_file(path: Path) -> Path: + """Rename file to YYMMDD-XX-slug.md format (fallback implementation).""" + # Skip if already matches new format + if re.match(r"^\d{6}-\d{2}-", path.name): + return path + + # Generate date prefix + now = datetime.datetime.now() + date_prefix = now.strftime('%y%m%d') # YYMMDD + + # Extract slug from H1 or filename + title = _fallback_extract_h1_title(path) + if title: + slug = _fallback_slugify(title) + else: + # Use filename (strip any existing prefixes) + stem = path.stem + raw_slug = re.sub(r"^(?:(?:\d{4}|\d{6}|\d{8})-?)+", "", stem) + slug = _fallback_slugify(raw_slug) if raw_slug else "untitled" + + # Get next sequence number + seq = _fallback_get_next_sequence(path.parent, date_prefix) + + # Generate new filename + new_name = f"{date_prefix}-{seq:02d}-{slug}.md" + new_path = path.parent / new_name + + # Rename + try: + path.rename(new_path) + return new_path + except Exception as e: + print(f"Error renaming file: {e}", file=sys.stderr) + raise + + +# ============================================================================ +# PUBLIC INTERFACE (delegates to file-track or fallback) +# ============================================================================ + +def rename_file(path: Path) -> Path: + """Rename file using file-track if available, otherwise use fallback.""" + if _USE_FILETRACK_RENAME: + return ft_rename_file(path) + + print("Warning: file-track not installed, using fallback", file=sys.stderr) + return _fallback_rename_file(path) + + +# ============================================================================ +# MAIN CLI +# ============================================================================ + +def main(): + """Main entry point for CLI.""" + if len(sys.argv) < 2: + print("Usage: python3 rename_jot.py <path-to-markdown-file>", file=sys.stderr) + sys.exit(1) + + path_str = sys.argv[1] + path = Path(path_str) + + if not path.exists(): + print(f"Error: File not found: {path}", file=sys.stderr) + sys.exit(1) + + if path.suffix != ".md": + print(f"Error: File must be a .md file: {path}", file=sys.stderr) + sys.exit(1) + + # Store old name for output + old_name = path.name + + # Rename file + try: + new_path = rename_file(path) + + # Only print message if actually renamed + if new_path.name != old_name: + print(f"✓ Renamed {old_name} → {new_path.name}") + + except Exception as e: + print(f"Error: Failed to rename file: {e}", file=sys.stderr) + sys.exit(1) + + # Track with file-track CLI if available (uses shared wrapper) + track_script = Path.home() / ".claude/scripts/track_with_filetrack.sh" + if track_script.exists(): + try: + subprocess.run( + [str(track_script), str(new_path), "--no-rename"], + check=True, + capture_output=True + ) + except subprocess.CalledProcessError as e: + print(f"Warning: file-track tracking failed: {e}", file=sys.stderr) + # Don't fail the rename operation if tracking fails + + # Remove lock file created by writing-plans wrapper + # Find git root by walking up from the file + working_dir = new_path.parent + while working_dir != working_dir.parent: + if (working_dir / '.git').exists(): + break + working_dir = working_dir.parent + + lock_file = working_dir / '.writing-plans-active' + if lock_file.exists(): + lock_file.unlink() + # Lock file removed silently + + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/skills/writing-plans/scripts/validate-frontmatter.py b/skills/writing-plans/scripts/validate-frontmatter.py new file mode 100755 index 000000000..2b362ae25 --- /dev/null +++ b/skills/writing-plans/scripts/validate-frontmatter.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +""" +Validate frontmatter in markdown files. + +This script checks that a markdown file contains valid frontmatter with required fields. +Used by the record-findings skill to ensure data quality. + +Usage: + python3 validate-frontmatter.py <path-to-markdown-file> + +Returns: + 0 if validation passes + 1 if validation fails (with error message on stderr) +""" +import sys +import re +from typing import Dict, List, Optional + + +def extract_frontmatter(content: str) -> Optional[Dict[str, str]]: + """Extract YAML frontmatter from markdown content.""" + # Skip jot comment if present and match YAML frontmatter between --- delimiters + # Allow for <!-- jot:md-rename --> or similar comments before frontmatter + match = re.match(r'^(?:<!--.*?-->\s*\n)?---\s*\n(.*?)\n---\s*\n', content, re.DOTALL) + if not match: + return None + + frontmatter_text = match.group(1) + frontmatter = {} + + # Parse simple YAML (key: value pairs) + for line in frontmatter_text.split('\n'): + line = line.strip() + if not line or line.startswith('#'): + continue + + # Match key: value + match = re.match(r'^(\w+):\s*(.*)$', line) + if match: + key, value = match.groups() + frontmatter[key] = value.strip() + + return frontmatter + + +def validate_frontmatter(frontmatter: Optional[Dict[str, str]]) -> tuple[bool, List[str]]: + """ + Validate frontmatter has required fields. + + Required fields: title, date, type, status + Optional fields: tags, related, phase, project + + Returns: (is_valid, list_of_errors) + """ + errors = [] + + if frontmatter is None: + errors.append("No frontmatter found. Add YAML frontmatter between --- delimiters at the start of the file.") + return False, errors + + required_fields = ['title', 'date', 'type', 'status'] + + for field in required_fields: + if field not in frontmatter or not frontmatter[field]: + errors.append(f"Missing required field: {field}") + + # Validate field values if present + if 'type' in frontmatter: + valid_types = ['implementation-plan', 'research-finding', 'analysis', 'investigation', 'design'] + if frontmatter['type'] and frontmatter['type'] not in valid_types: + errors.append(f"Invalid type '{frontmatter['type']}'. Valid types: {', '.join(valid_types)}") + + if 'status' in frontmatter: + valid_statuses = ['draft', 'in-progress', 'completed', 'active', 'archived'] + if frontmatter['status'] and frontmatter['status'] not in valid_statuses: + errors.append(f"Invalid status '{frontmatter['status']}'. Valid statuses: {', '.join(valid_statuses)}") + + # Validate date format (YYYY-MM-DD) + if 'date' in frontmatter and frontmatter['date']: + if not re.match(r'^\d{4}-\d{2}-\d{2}$', frontmatter['date']): + errors.append(f"Invalid date format '{frontmatter['date']}'. Use YYYY-MM-DD format.") + + return len(errors) == 0, errors + + +def main(): + if len(sys.argv) < 2: + print("Usage: python3 validate-frontmatter.py <path-to-markdown-file>", file=sys.stderr) + sys.exit(1) + + path = sys.argv[1] + + try: + with open(path, 'r') as f: + content = f.read() + except FileNotFoundError: + print(f"Error: File not found: {path}", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"Error reading file: {e}", file=sys.stderr) + sys.exit(1) + + frontmatter = extract_frontmatter(content) + is_valid, errors = validate_frontmatter(frontmatter) + + if is_valid: + print("✓ Frontmatter validation passed") + sys.exit(0) + else: + print("✗ Frontmatter validation failed:", file=sys.stderr) + for error in errors: + print(f" - {error}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/skills/writing-plans/scripts/write_plan.py b/skills/writing-plans/scripts/write_plan.py new file mode 100755 index 000000000..129a0a170 --- /dev/null +++ b/skills/writing-plans/scripts/write_plan.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +""" +Executable wrapper for writing-plans skill. + +This wrapper guides Claude through the file writing workflow instead of +letting Claude just describe what it would do. +""" +import sys +import os +import argparse +from datetime import datetime + +def main(): + parser = argparse.ArgumentParser( + description="Guide Claude through writing an implementation plan" + ) + parser.add_argument( + "--working-dir", + required=True, + help="Working directory path (from env context)" + ) + parser.add_argument( + "--plan-name", + required=True, + help="Name for the plan file (will be slugified)" + ) + parser.add_argument( + "--in-plan-mode", + action="store_true", + help="Set if currently in plan mode (uses staging area)" + ) + parser.add_argument( + "--target-dir", + default="llm/implementation-plans", + help="Target directory relative to working-dir (default: llm/implementation-plans)" + ) + + args = parser.parse_args() + + # Determine target path + if args.in_plan_mode: + staging_path = os.path.expanduser("~/.claude/plans") + target_file = f"{staging_path}/{args.plan_name}.md" + final_dir = os.path.join(args.working_dir, args.target_dir) + print(f"📝 PLAN MODE DETECTED") + print(f"✓ Write plan to: {target_file}") + print(f"✓ Then copy to: {final_dir}/{args.plan_name}.md") + else: + target_dir = os.path.join(args.working_dir, args.target_dir) + target_file = f"{target_dir}/{args.plan_name}.md" + print(f"📝 REGULAR MODE") + print(f"✓ Write plan to: {target_file}") + + # Create lock file to enable Write tool for this plan + lock_file = os.path.join(args.working_dir, '.writing-plans-active') + with open(lock_file, 'w') as f: + f.write(f"{target_file}\n") + f.write(f"created: {datetime.now().isoformat()}\n") + + print(f"🔓 Created lock file: {lock_file}") + + print(f"\n🔧 REQUIRED ACTIONS:") + print(f"1. Use Write tool to create file with:") + print(f" - First line: <!-- jot:md-rename -->") + print(f" - YAML frontmatter (title, date, type, status)") + print(f" - H1 heading with feature name") + print(f" - Implementation tasks following writing-plans format") + print(f"\n2. After writing, run post-write workflow:") + print(f" - Validate: python3 $CLAUDE_PLUGIN_ROOT/skills/writing-plans/scripts/validate-frontmatter.py {target_file}") + print(f" - Rename: python3 $CLAUDE_PLUGIN_ROOT/skills/writing-plans/scripts/rename_jot.py {target_file} (auto-tracks with file-track)") + print(f" ⚠️ IMPORTANT: Rename script will remove lock file") + + print(f"\n⚠️ CRITICAL: DO NOT just describe the plan!") + print(f"⚠️ You MUST use Write tool to create the actual file!") + print(f"⚠️ Describing = BUG. Writing = CORRECT.") + + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/skills/writing-skills/SKILL.md b/skills/writing-skills/SKILL.md index 984ebce57..84e11a4c4 100644 --- a/skills/writing-skills/SKILL.md +++ b/skills/writing-skills/SKILL.md @@ -1,6 +1,6 @@ --- name: writing-skills -description: Use when creating new skills, editing existing skills, or verifying skills work before deployment - applies TDD to process documentation by testing with subagents before writing, iterating until bulletproof against rationalization +description: Use when creating new skills, modifying existing skills, debugging skills that don't work, adding sections to skills, improving skill documentation, or verifying skills before deployment - applies TDD to process documentation by testing with subagents before writing, iterating until bulletproof against rationalization. REQUIRED before making ANY changes to skill files. --- # Writing Skills @@ -19,6 +19,21 @@ You write test cases (pressure scenarios with subagents), watch them fail (basel **Official guidance:** For Anthropic's official skill authoring best practices, see anthropic-best-practices.md. This document provides additional patterns and guidelines that complement the TDD-focused approach in this skill. +<EXTREMELY-IMPORTANT> +**Before modifying ANY skill file, you MUST consult this skill first.** + +If you're about to: +- Edit a skill file +- Add a section to a skill +- Improve skill documentation +- Debug why a skill isn't working +- Make "quick fixes" to a skill + +**STOP. Read this skill. Follow TDD process.** + +Modifying skills without testing = deploying untested code. It's the same violation. +</EXTREMELY-IMPORTANT> + ## What is a Skill? A **skill** is a reference guide for proven techniques, patterns, or tools. Skills help future Claude instances find and apply effective approaches. @@ -420,6 +435,9 @@ Different skill types need different test approaches: | "I'm confident it's good" | Overconfidence guarantees issues. Test anyway. | | "Academic review is enough" | Reading ≠ using. Test application scenarios. | | "No time to test" | Deploying untested skill wastes more time fixing it later. | +| "Just adding a section" | Edits need testing too. What if section is unclear or wrong? | +| "This is just documentation" | Skills ARE code. Untested edits = bugs. Same rules apply. | +| "Quick fix, no testing needed" | Quick fixes break things. Test changes regardless of size. | **All of these mean: Test before deploying. No exceptions.** @@ -497,6 +515,8 @@ Add to description: symptoms of when you're ABOUT to violate the rule: description: use when implementing any feature or bugfix, before writing implementation code ``` +**Deep dive:** See automation-over-documentation.md for case study showing documentation approach failing after 2 TDD cycles, mechanical enforcement succeeding immediately. Includes decision framework and cost-benefit analysis. + ## RED-GREEN-REFACTOR for Skills Follow the TDD cycle: diff --git a/skills/writing-skills/automation-over-documentation.md b/skills/writing-skills/automation-over-documentation.md new file mode 100644 index 000000000..5a5bcfe4e --- /dev/null +++ b/skills/writing-skills/automation-over-documentation.md @@ -0,0 +1,89 @@ +# Automation Over Documentation for Mechanical Enforcement + +**Core insight:** Mechanically enforce mechanical constraints. Don't fight LLM training with documentation. + +## The Problem + +**Goal:** Zero emojis in code review output (professional formatting). + +**Documentation approach (failed):** +- Cycle 1: "NEVER use emojis" → Agents used ✅ ❌ ⚠️ +- Cycle 2: "Not in summaries/headings" → Used in lists +- Result: 0/5 tests passed, RED-GREEN-REFACTOR never stabilized + +**Root cause:** Emojis signal status in LLM training data. Documentation fights natural behavior. + +## The Solution + +**Automation approach (succeeded):** + +```python +import emoji + +def strip_emojis(text): + return emoji.replace_emoji(text, replace='') + +# Or using regex for zero dependencies: +# import re +# emoji_pattern = re.compile(r'[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF]+') +# return emoji_pattern.sub('', text) +``` + +**Result:** 5/5 tests passed first try, 100% compliance, zero documentation needed. + +## When to Use Each Approach + +**Use Automation (Mechanical Enforcement):** +- Requirement is 100% objective (emoji yes/no, line length, format) +- Violation is programmatically detectable +- Agents consistently find rationalization loopholes +- Silent dependencies break workflows (e.g., registration gated by module import) +- Examples: emoji stripping, line length, JSON validation, whitespace removal, dependency gates + +**Use Documentation (Judgment Guidance):** +- Requirement needs human-like reasoning +- Context determines correct choice +- Trade-offs exist between options +- Examples: severity labeling (`blocking` vs `non-blocking`), actionable suggestions, test coverage decisions + +## Red Flags: Wrong Tool for the Job + +**Consider automation if you're:** +- Iterating rationalization tables without progress +- Adding "no really, I mean NEVER" language +- Multiple TDD cycles without REFACTOR stabilizing +- Silent fallbacks hiding failures (e.g., `if module_available: register()`) + +**Why:** You're using the wrong tool. REFACTOR phase won't stabilize when fighting natural LLM behavior. + +## Psychology Insight + +Agents rationalize around documentation when it conflicts with training data (see persuasion-principles.md, Cialdini 2021). Mechanical enforcement removes the conflict. + +## Application to Skill Creation + +**Skills should document DECISIONS, not MECHANICS.** + +- ✅ Good: "When to use X vs Y" (judgment required) +- ❌ Bad: "Never use X" (if mechanically enforceable) + +**TDD signal:** Multiple REFACTOR cycles without stabilization = wrong tool. Automate the mechanical constraint. + +**Defense-in-Depth:** Layer mechanical enforcement (code) + judgment guidance (skills) + outcome validation (tests). Example: Strip emojis (Python) → guide severity labeling (skill) → validate actionability (test). + +## Cost-Benefit + +| Approach | Cost | Benefit | Maintenance | +|----------|------|---------|-------------| +| Documentation | Multiple TDD cycles, bloat | None (0/5 tests) | High (whack-a-mole) | +| Automation | 15 lines code | 100% (5/5 tests) | Near-zero | + +**ROI:** Automation paid for itself first test run. + +## The Bottom Line + +**Mechanical constraints belong in code, not documentation.** + +When TDD cycles show agents consistently finding new rationalizations for the same mechanical rule, that's the signal to automate. Save documentation for judgment calls where human-like reasoning matters. + +**Evidence-based decision:** Let TDD tell you which tool to use. If REFACTOR phase doesn't stabilize after 2-3 cycles, switch to automation.