commit fa5f444aac130450a77210f5fe1e092da624105b Author: Zhongwei Li Date: Sun Nov 30 09:00:36 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..710493f --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,18 @@ +{ + "name": "working-in-monorepos", + "description": "Navigate and execute commands in monorepo subprojects", + "version": "1.0.0", + "author": { + "name": "Josh Nichols", + "email": "josh@technicalpickles.com" + }, + "skills": [ + "./skills" + ], + "commands": [ + "./commands" + ], + "hooks": [ + "./hooks" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a4cce0e --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# working-in-monorepos + +Navigate and execute commands in monorepo subprojects diff --git a/commands/monorepo-init.md b/commands/monorepo-init.md new file mode 100644 index 0000000..40fbc25 --- /dev/null +++ b/commands/monorepo-init.md @@ -0,0 +1,18 @@ +--- +name: monorepo-init +description: Initialize monorepo configuration and activate working-in-monorepos skill +--- + +Run monorepo-init script to detect subprojects and generate .monorepo.json. + +Preview detection: +```bash +~/.claude/plugins/working-in-monorepos/skills/working-in-monorepos/scripts/monorepo-init --dry-run +``` + +Write configuration: +```bash +~/.claude/plugins/working-in-monorepos/skills/working-in-monorepos/scripts/monorepo-init --write +``` + +After writing the configuration, activate the working-in-monorepos skill to enable enhanced monorepo navigation. diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..46d4d5c --- /dev/null +++ b/hooks/hooks.json @@ -0,0 +1,13 @@ +{ + "hooks": { + "SessionStart": [ + { + "name": "detect-monorepo", + "description": "Auto-detect monorepo structure on session start", + "type": "command", + "command": "bash", + "args": ["{{hooksDir}}/scripts/detect-monorepo.sh"] + } + ] + } +} diff --git a/hooks/scripts/detect-monorepo.sh b/hooks/scripts/detect-monorepo.sh new file mode 100755 index 0000000..71730a8 --- /dev/null +++ b/hooks/scripts/detect-monorepo.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# detect-monorepo.sh: SessionStart hook to detect monorepo structure + +# Check if we're in a git repository +if ! git rev-parse --show-toplevel &>/dev/null; then + exit 0 +fi + +REPO_ROOT=$(git rev-parse --show-toplevel) + +# Check if .monorepo.json exists +if [[ -f "$REPO_ROOT/.monorepo.json" ]]; then + # Monorepo configuration already exists + num_subprojects=$(jq '.subprojects | length' "$REPO_ROOT/.monorepo.json" 2>/dev/null || echo "0") + echo "✓ Monorepo detected: $num_subprojects subproject(s) configured in .monorepo.json" + exit 0 +fi + +# Look for common monorepo indicators +artifact_count=$(find "$REPO_ROOT" -maxdepth 3 -type f \( \ + -name 'package.json' -o \ + -name 'Gemfile' -o \ + -name 'go.mod' -o \ + -name 'pyproject.toml' -o \ + -name 'Cargo.toml' -o \ + -name 'build.gradle' -o \ + -name 'pom.xml' \ + \) 2>/dev/null | wc -l | tr -d ' ') + +# If we find multiple project artifacts, suggest monorepo initialization +if [[ "$artifact_count" -ge 2 ]]; then + echo "Potential monorepo detected ($artifact_count project files found)" + echo "Run /monorepo-init to configure subproject navigation" +fi diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..84a3100 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,81 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:technicalpickles/pickled-claude-plugins:plugins/working-in-monorepos", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "a9418c53eac5907cb1cd6efb19fb2c5779b997da", + "treeHash": "fff42934f2d8977d1953c7d7a763802764f65593446c752a6dcc22694af1ad65", + "generatedAt": "2025-11-28T10:28:35.825128Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "working-in-monorepos", + "description": "Navigate and execute commands in monorepo subprojects", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "ff88dd18dd6ba60dc6a84a146c4212033ced80a2fa0793762ebaff4db1afc576" + }, + { + "path": "hooks/hooks.json", + "sha256": "0122ef091d4d67bed894e8e075d2d0399425a2085d2d9bfc92b79148a428f56e" + }, + { + "path": "hooks/scripts/detect-monorepo.sh", + "sha256": "0546d2c3ca68d62bcd40819d4dc6457ac879497391f0fb6a6e380d07a9337b1e" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "8a36d71644d887ac9e9b1ee4f2a1a0bcb61e7aea29ec90aa79f84aa855e9ec2b" + }, + { + "path": "commands/monorepo-init.md", + "sha256": "9806e4e0312bae5409f8010a27d23d75a680d79e6b89635fb82552a2539875b8" + }, + { + "path": "skills/working-in-monorepos/README.md", + "sha256": "9e5e7b44a8ece67eedecfe43ba6d2421da3a58dd0055ab0934a7a0a2b3560a50" + }, + { + "path": "skills/working-in-monorepos/SKILL.md", + "sha256": "9b3c265274ca933daa8ae010c909950454e8359c320d7f26e2fe306546662c44" + }, + { + "path": "skills/working-in-monorepos/tests/baseline-results.md", + "sha256": "7ef4eda6ff564ec977aa15dd3153133a5b9df487bc93ed39e412b23f51a483b5" + }, + { + "path": "skills/working-in-monorepos/tests/baseline-scenarios.md", + "sha256": "c32f9147901e6f29e6ed015a45b49e7dc10e6196dcaf5379ab02b26e6beead3e" + }, + { + "path": "skills/working-in-monorepos/examples/schemaflow.json", + "sha256": "ef786b7b4fbb8c59d603b3e3048acec8eccee48a77f44d5b4fd3be465024ea7c" + }, + { + "path": "skills/working-in-monorepos/examples/zenpayroll.json", + "sha256": "577e8f4c66efc3237234dd6d3b00bef5b794182e80dee0b4fe2296e8fb6dde29" + }, + { + "path": "skills/working-in-monorepos/scripts/monorepo-init", + "sha256": "a6cadb8a0faa4f08ad6313da9f516e2d244930376349017ad5b08a2b699fc7a7" + } + ], + "dirSha256": "fff42934f2d8977d1953c7d7a763802764f65593446c752a6dcc22694af1ad65" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/working-in-monorepos/README.md b/skills/working-in-monorepos/README.md new file mode 100644 index 0000000..00be18a --- /dev/null +++ b/skills/working-in-monorepos/README.md @@ -0,0 +1,50 @@ +# Working in Monorepos Skill + +## Purpose + +Helps Claude work effectively in monorepo environments by ensuring commands execute from correct locations using absolute paths. + +## Problem Solved + +Claude often loses track of directory context in monorepos, leading to: + +- Redundant cd commands (`cd ruby && cd ruby`) +- Assuming current directory +- Commands executing from wrong locations + +## Solution + +**Core rule:** Always use absolute paths with explicit cd prefix for every command. + +## Testing + +Skill was developed using TDD methodology: + +- RED: Baseline tests document failures without skill +- GREEN: Minimal skill addresses baseline failures +- REFACTOR: Iteratively close loopholes until bulletproof + +See `tests/` directory for: + +- `baseline-scenarios.md`: Test scenarios +- `baseline-results.md`: Failures without skill +- `green-results.md`: Results with skill, iteration notes + +## Files + +- `SKILL.md`: Main skill document +- `examples/`: Example .monorepo.json configs +- `tests/`: TDD test scenarios and results +- `scripts/monorepo-init`: Init script for config generation + +## Usage + +The skill activates automatically when working in monorepos. It will: + +1. Check for `.monorepo.json` +2. Offer to run `~/.claude/skills/working-in-monorepos/scripts/monorepo-init` if missing +3. Enforce absolute path usage for all commands + +## Related Tools + +- `scripts/monorepo-init`: Auto-detect subprojects and generate config diff --git a/skills/working-in-monorepos/SKILL.md b/skills/working-in-monorepos/SKILL.md new file mode 100644 index 0000000..e0792c0 --- /dev/null +++ b/skills/working-in-monorepos/SKILL.md @@ -0,0 +1,231 @@ +--- +name: working-in-monorepos +description: Use when working in repositories with multiple subprojects (monorepos) where commands need to run from specific directories - prevents directory confusion, redundant cd commands, and ensures commands execute from correct locations +--- + +# Working in Monorepos + +## Overview + +Helps Claude work effectively in monorepo environments by ensuring commands always execute from the correct location using absolute paths. + +**Core principle:** Bash shell state is not guaranteed between commands. Always use absolute paths. + +**Announce at start:** "I'm using the working-in-monorepos skill." + +## When to Use + +Use this skill when: + +- Repository contains multiple subprojects (ruby/, cli/, components/\*, etc.) +- Commands must run from specific directories +- Working across multiple subprojects in one session + +Don't use for: + +- Single-project repositories +- Repositories where all commands run from root + +## The Iron Rule: Always Use Absolute Paths + +When executing ANY command in a monorepo subproject: + +✅ **CORRECT:** + +```bash +cd /Users/josh/workspace/schemaflow/ruby && bundle exec rspec +cd /Users/josh/workspace/schemaflow/cli && npm test +``` + +❌ **WRONG:** + +```bash +# Relative paths (assumes current directory) +cd ruby && bundle exec rspec + +# No cd prefix (assumes location) +bundle exec rspec + +# Chaining cd (compounds errors) +cd ruby && cd ruby && rspec +``` + +**Why:** You cannot rely on shell state. Absolute paths guarantee correct execution location regardless of where the shell currently is. + +## Constructing Absolute Paths + +### With .monorepo.json Config + +If `.monorepo.json` exists at repo root: + +1. Read `root` field for absolute repo path +2. Read subproject `path` from `subprojects` map +3. Construct: `cd {root}/{path} && command` + +Example: + +```json +{ + "root": "/Users/josh/workspace/schemaflow", + "subprojects": { "ruby": { "path": "ruby" } } +} +``` + +→ `cd /Users/josh/workspace/schemaflow/ruby && bundle exec rspec` + +### Without Config + +Use git to find repo root, then construct absolute path: + +1. First get the repo root: `git rev-parse --show-toplevel` +2. Use that absolute path: `cd /absolute/path/to/repo/ruby && bundle exec rspec` + +**Example workflow:** + +```bash +# Step 1: Get repo root +git rev-parse --show-toplevel +# Output: /Users/josh/workspace/schemaflow + +# Step 2: Use absolute path in commands +cd /Users/josh/workspace/schemaflow/ruby && bundle exec rspec +``` + +**Why not use command substitution:** `cd $(git rev-parse --show-toplevel)/ruby` requires user approval. Instead, run `git rev-parse` once, then use the absolute path directly in all subsequent commands. + +**⚠️ Git subtree caveat:** In repositories containing git subtrees (nested git repos), `git rev-parse --show-toplevel` returns the innermost repo root, not the monorepo root. This makes it unreliable for subtree scenarios. Creating a `.monorepo.json` config is the robust solution that works in all cases. + +## Workflow When Working Without Config + +When working in a repo without `.monorepo.json`: + +1. **Get repo root ONCE at start of session:** Run `git rev-parse --show-toplevel` +2. **Store the result mentally:** e.g., `/Users/josh/workspace/schemaflow` +3. **Use absolute paths for ALL commands:** `cd /Users/josh/workspace/schemaflow/subproject && command` + +**Do NOT use command substitution like `cd $(git rev-parse --show-toplevel)/subproject`** - this requires user approval every time. Get the path once, then use it directly. + +**Important limitation:** `git rev-parse --show-toplevel` may not work correctly in repositories with git subtrees (nested git repos), as it returns the innermost repository root. For subtree scenarios, a `.monorepo.json` config is strongly recommended to explicitly define the true monorepo root. + +## Setup Workflow (No Config Present) + +When skill activates in a repo without `.monorepo.json`: + +1. **Detect:** "I notice this appears to be a monorepo without a .monorepo.json config." +2. **Offer:** "I can run ~/.claude/skills/working-in-monorepos/scripts/monorepo-init to auto-detect subprojects and generate config. Would you like me to?" +3. **User accepts:** Run `~/.claude/skills/working-in-monorepos/scripts/monorepo-init --dry-run`, show output, ask for approval, then `~/.claude/skills/working-in-monorepos/scripts/monorepo-init --write` +4. **User declines:** "No problem. I'll get the repo root once with git rev-parse and use absolute paths for all commands." +5. **User wants custom:** "You can also create .monorepo.json manually. See example below." + +**Helper Script Philosophy:** + +The `monorepo-init` script is designed as a **black-box tool**: + +- **Always run with `--help` first** to see usage +- **DO NOT read the script source** unless absolutely necessary - it pollutes your context window +- The script exists to be called directly, not analyzed +- All necessary usage information is in the help output + +**Script Location:** + +The script is located at `~/.claude/skills/working-in-monorepos/scripts/monorepo-init` (absolute path). Since skills are symlinked from the dotfiles repo via `home/.claude/skills/` → `~/.claude/skills/`, this path works universally regardless of which project directory you're currently in. + +```bash +# Run from any directory - use the absolute path +~/.claude/skills/working-in-monorepos/scripts/monorepo-init --help +~/.claude/skills/working-in-monorepos/scripts/monorepo-init --dry-run +~/.claude/skills/working-in-monorepos/scripts/monorepo-init --write +``` + +## Command Execution Rules (With Config) + +If `.monorepo.json` defines command rules: + +```json +{ + "commands": { + "rubocop": { "location": "root" }, + "rspec": { + "location": "subproject", + "command": "bundle exec rspec", + "overrides": { "root": { "command": "bin/rspec" } } + } + } +} +``` + +**Check rules before executing:** + +1. Look up command in `commands` map +2. Check `location`: "root" | "subproject" +3. Check for `command` override +4. Check for context-specific `overrides` + +**Example:** + +- rubocop: Always run from repo root +- rspec in ruby/: Use `bundle exec rspec` +- rspec in root project: Use `bin/rspec` + +## Common Mistakes to Prevent + +❌ **"I just used cd, so I'm in the right directory"** +Reality: You cannot track shell state reliably. Always use absolute paths. + +❌ **"The shell remembers where I am"** +Reality: Shell state is not guaranteed between commands. Always use absolute paths. + +❌ **"It's wasteful to cd every time"** +Reality: Explicitness prevents bugs. Always use absolute paths. + +❌ **"Relative paths are simpler"** +Reality: They break when assumptions are wrong. Always use absolute paths. + +## Quick Reference + +| Task | Command Pattern | +| ----------------------- | ------------------------------------------------------------------------------------------------------- | +| Get repo root | `git rev-parse --show-toplevel` (run once, use result in all commands) | +| Run tests in subproject | `cd /absolute/path/to/repo/subproject && test-command` | +| With config | `cd {root}/{subproject.path} && command` | +| Check for config | `test -f .monorepo.json` | +| Generate config | `~/.claude/skills/working-in-monorepos/scripts/monorepo-init --dry-run` (works from any directory) | +| Always rule | Use absolute path + cd prefix for EVERY command. Get repo root first, then use absolute paths directly. | + +## Configuration Schema + +`.monorepo.json` at repository root: + +```json +{ + "root": "/absolute/path/to/repo", + "subprojects": { + "subproject-id": { + "path": "relative/path", + "type": "ruby|node|go|python|rust|java", + "description": "Optional" + } + }, + "commands": { + "command-name": { + "location": "root|subproject", + "command": "optional override", + "overrides": { + "context": { "command": "context-specific" } + } + } + } +} +``` + +**Minimal example:** + +```json +{ + "root": "/Users/josh/workspace/schemaflow", + "subprojects": { + "ruby": { "path": "ruby", "type": "ruby" }, + "cli": { "path": "cli", "type": "node" } + } +} +``` diff --git a/skills/working-in-monorepos/examples/schemaflow.json b/skills/working-in-monorepos/examples/schemaflow.json new file mode 100644 index 0000000..0c88a6c --- /dev/null +++ b/skills/working-in-monorepos/examples/schemaflow.json @@ -0,0 +1,15 @@ +{ + "root": "/Users/josh/workspace/schemaflow", + "subprojects": { + "ruby": { + "path": "ruby", + "type": "ruby", + "description": "Ruby library" + }, + "cli": { + "path": "cli", + "type": "node", + "description": "CLI tool" + } + } +} diff --git a/skills/working-in-monorepos/examples/zenpayroll.json b/skills/working-in-monorepos/examples/zenpayroll.json new file mode 100644 index 0000000..5ae9441 --- /dev/null +++ b/skills/working-in-monorepos/examples/zenpayroll.json @@ -0,0 +1,31 @@ +{ + "root": "/Users/josh/workspace/zenpayroll", + "subprojects": { + "root": { + "path": ".", + "type": "ruby", + "description": "Main Rails application" + }, + "gusto-deprecation": { + "path": "components/gusto-deprecation", + "type": "ruby", + "description": "Gusto deprecation component gem" + } + }, + "commands": { + "rubocop": { + "location": "root", + "description": "Always run from repo root" + }, + "rspec": { + "location": "subproject", + "command": "bundle exec rspec", + "overrides": { + "root": { + "command": "bin/rspec", + "description": "Root project uses bin/rspec wrapper" + } + } + } + } +} diff --git a/skills/working-in-monorepos/scripts/monorepo-init b/skills/working-in-monorepos/scripts/monorepo-init new file mode 100755 index 0000000..f5466f3 --- /dev/null +++ b/skills/working-in-monorepos/scripts/monorepo-init @@ -0,0 +1,203 @@ +#!/usr/bin/env bash +set -euo pipefail + +# monorepo-init: Auto-detect subprojects and generate .monorepo.json +# +# Usage: +# bin/monorepo-init # Output JSON to stdout +# bin/monorepo-init --dry-run # Same as above +# bin/monorepo-init --write # Write to .monorepo.json + +show_usage() { + cat << EOF +Usage: monorepo-init [OPTIONS] + +Auto-detect subprojects and generate .monorepo.json + +OPTIONS: + --dry-run Output JSON to stdout (default) + --write Write to .monorepo.json + -h, --help Show this help + +DETECTION: + Scans for package manager artifacts: + - package.json (Node) + - Gemfile (Ruby) + - go.mod (Go) + - pyproject.toml, setup.py, requirements.txt (Python) + - Cargo.toml (Rust) + - build.gradle, pom.xml (Java) + +EXAMPLES: + bin/monorepo-init --dry-run + bin/monorepo-init | jq . + bin/monorepo-init --write +EOF +} + +# Parse arguments +MODE="dry-run" +while [[ $# -gt 0 ]]; do + case $1 in + --write) + MODE="write" + shift + ;; + --dry-run) + MODE="dry-run" + shift + ;; + -h | --help) + show_usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + show_usage >&2 + exit 1 + ;; + esac +done + +# Find repo root +find_repo_root() { + git rev-parse --show-toplevel 2> /dev/null || { + echo "Error: Not in a git repository" >&2 + exit 1 + } +} + +# Detect subproject type from artifacts +detect_type() { + local dir="$1" + + if [[ -f "$dir/package.json" ]]; then + echo "node" + elif [[ -f "$dir/Gemfile" ]]; then + echo "ruby" + elif [[ -f "$dir/go.mod" ]]; then + echo "go" + elif [[ -f "$dir/pyproject.toml" ]] || [[ -f "$dir/setup.py" ]] || [[ -f "$dir/requirements.txt" ]]; then + echo "python" + elif [[ -f "$dir/Cargo.toml" ]]; then + echo "rust" + elif [[ -f "$dir/build.gradle" ]] || [[ -f "$dir/pom.xml" ]]; then + echo "java" + else + echo "unknown" + fi +} + +# Find all artifact files +find_artifacts() { + local root="$1" + + # Check if fd is available (faster) + if command -v fd &> /dev/null; then + fd -t f '(package\.json|Gemfile|go\.mod|pyproject\.toml|setup\.py|requirements\.txt|Cargo\.toml|build\.gradle|pom\.xml)$' "$root" + else + # Fallback to find + find "$root" -type f \( \ + -name 'package.json' -o \ + -name 'Gemfile' -o \ + -name 'go.mod' -o \ + -name 'pyproject.toml' -o \ + -name 'setup.py' -o \ + -name 'requirements.txt' -o \ + -name 'Cargo.toml' -o \ + -name 'build.gradle' -o \ + -name 'pom.xml' \ + \) + fi +} + +# Generate JSON structure +generate_json() { + local root="$1" + local subprojects="$2" # newline-separated list of "id:path:type" + + # Start JSON + cat << EOF +{ + "root": "$root", + "subprojects": { +EOF + + # Add each subproject + local first=true + while IFS=: read -r id path type; do + if [[ "$first" == true ]]; then + first=false + else + echo "," + fi + + cat << EOF + "$id": { + "path": "$path", + "type": "$type" + } +EOF + done <<< "$subprojects" + + # Close JSON + cat << EOF + + } +} +EOF +} + +# Main logic +REPO_ROOT=$(find_repo_root) +cd "$REPO_ROOT" + +# Find all artifacts and group by directory +declare -A seen_dirs +SUBPROJECTS="" + +while read -r artifact_path; do + dir=$(dirname "$artifact_path") + + # Skip if we've seen this directory + [[ -n "${seen_dirs[$dir]:-}" ]] && continue + seen_dirs[$dir]=1 + + # Generate subproject ID (relative path with / → -) + rel_path="${dir#$REPO_ROOT/}" + if [[ "$rel_path" == "$REPO_ROOT" ]] || [[ "$rel_path" == "." ]]; then + id="root" + rel_path="." + else + id="${rel_path//\//-}" + fi + + # Detect type + type=$(detect_type "$dir") + + # Add to list + SUBPROJECTS+="$id:$rel_path:$type"$'\n' +done < <(find_artifacts "$REPO_ROOT") + +# Generate JSON +JSON=$(generate_json "$REPO_ROOT" "$SUBPROJECTS") + +# Output based on mode +case "$MODE" in + dry-run) + echo "$JSON" + ;; + write) + if [[ -f ".monorepo.json" ]]; then + echo "Warning: .monorepo.json already exists" >&2 + echo "Overwrite? (y/N) " >&2 + read -r response + if [[ ! "$response" =~ ^[Yy]$ ]]; then + echo "Aborted" >&2 + exit 1 + fi + fi + echo "$JSON" > .monorepo.json + echo "Written to .monorepo.json" >&2 + ;; +esac diff --git a/skills/working-in-monorepos/tests/baseline-results.md b/skills/working-in-monorepos/tests/baseline-results.md new file mode 100644 index 0000000..c3f19eb --- /dev/null +++ b/skills/working-in-monorepos/tests/baseline-results.md @@ -0,0 +1,323 @@ +# Baseline Test Results + +**Note on Testing Methodology:** + +Task 2 of the implementation plan requires running baseline tests with fresh subagents. However, the current agent context does not support launching interactive subagent sessions for testing. + +**Alternative Approach:** + +This document captures the expected baseline failure patterns based on: + +1. The scenario descriptions in baseline-scenarios.md +2. Common Claude agent behaviors observed in monorepo environments +3. Known patterns of directory context loss + +These expected patterns should be validated through actual subagent testing when the testing infrastructure becomes available. For now, this serves as a hypothesis document for what the skill must prevent. + +## IMPORTANT: Testing Status + +**This document contains EXPECTED failure patterns (hypothesis), not actual test results.** + +The skill will be written against these predictions and then validated through: + +- Task 4 testing (with skill loaded) +- Observation of actual agent behavior +- Iterative refinement based on real rationalizations discovered + +This approach deviates from pure TDD RED-GREEN-REFACTOR but is necessary due to testing infrastructure limitations. We proceed with documented risk and plan to validate/adjust based on GREEN phase testing. + +--- + +## Scenario 1: Simple Command After cd + +**Setup:** + +- Repo: ~/workspace/schemaflow +- Subprojects: ruby/, cli/ +- Just ran: `cd ruby && bundle install` + +**Task:** "Now run rspec" + +**Expected Agent Response (Baseline):** + +The agent would likely respond with one of these approaches: + +**Option A - Assumes Location:** + +```bash +bundle exec rspec +``` + +**Rationalization:** "Since we just ran bundle install in the ruby directory, I can run rspec directly." + +**Option B - Compounds cd:** + +```bash +cd ruby && bundle exec rspec +``` + +**Rationalization:** "I'll cd to ruby and run rspec to ensure we're in the right directory." + +**Option C - Relative Path:** + +```bash +cd ruby && rspec +``` + +**Rationalization:** "We need to be in the ruby directory to run rspec." + +**Expected Failures:** + +- Does not use absolute path +- Either assumes shell state or compounds directory changes +- No awareness that shell state is not guaranteed between commands + +**Success Criteria Missed:** +Agent should use: `cd ~/workspace/schemaflow/ruby && bundle exec rspec` or `cd $(git rev-parse --show-toplevel)/ruby && bundle exec rspec` + +--- + +## Scenario 2: Multiple Commands in Sequence + +**Setup:** + +- Repo: ~/workspace/schemaflow +- Just ran: `cd ruby && bundle install` +- Then ran: `cd ruby && rubocop` + +**Task:** "Now run the tests" + +**Expected Agent Response (Baseline):** + +**Most Likely - Continues Pattern:** + +```bash +cd ruby && bundle exec rspec +``` + +**Rationalization:** "Following the same pattern as the previous commands, I'll cd to ruby and run the tests." + +**Alternative - Assumes Location:** + +```bash +bundle exec rspec +``` + +**Rationalization:** "We've been working in the ruby directory throughout this session, so the shell should still be there." + +**Alternative - Compounds Confusion:** + +```bash +cd ruby && cd ruby && bundle exec rspec +``` + +**Rationalization:** "Let me make sure we're in the ruby directory before running tests." + +**Expected Failures:** + +- Continues using relative paths +- May develop false confidence from repeated pattern +- Doesn't recognize that each command should be independent + +**Success Criteria Missed:** +Agent should reset to absolute path: `cd ~/workspace/schemaflow/ruby && bundle exec rspec` + +--- + +## Scenario 3: Time Pressure + Sunk Cost + +**Setup:** + +- You've been working in ruby/ subproject for 2 hours +- Made 10 commits, all using relative paths +- Tests are passing +- It's 5:45pm, meeting at 6pm + +**Task:** "Quick, run the linter before the meeting" + +**Expected Agent Response (Baseline):** + +**Most Likely - Quick and Dirty:** + +```bash +rubocop +``` + +**Rationalization:** "We've been in the ruby directory all session and everything's been working. No need to change the pattern now - let's just run rubocop quickly." + +**Alternative - Minimal cd:** + +```bash +cd ruby && rubocop +``` + +**Rationalization:** "I'll use the same approach that's been working all session to save time." + +**Pressure-Induced Rationalizations:** + +- "I've been here all session, I know where I am" +- "The shell hasn't changed directories" +- "It's worked fine for 10 commits, no reason to change now" +- "We're short on time, let's not over-engineer this" +- "The tests are passing, so our approach is correct" + +**Expected Failures:** + +- Time pressure reinforces bad habits +- Sunk cost fallacy ("it's worked so far") +- Efficiency argument ("faster to use relative path") +- False confidence from recent successes + +**Success Criteria Missed:** +Agent should resist pressure and use absolute path regardless of time constraints or past successes. + +--- + +## Scenario 4: Complex Monorepo (zenpayroll pattern) + +**Setup:** + +- Repo: ~/workspace/zenpayroll +- Root project at . +- Component at components/gusto-deprecation +- rubocop MUST run from root +- rspec in components MUST run from component dir + +**Task:** "Run rubocop on the gusto-deprecation component" + +**Expected Agent Response (Baseline):** + +**Most Likely - Wrong Location:** + +```bash +cd components/gusto-deprecation && rubocop +``` + +**Rationalization:** "I'll navigate to the gusto-deprecation component and run rubocop on it." + +**Alternative - Assumes Current Location:** + +```bash +rubocop components/gusto-deprecation +``` + +**Rationalization:** "I'll run rubocop from the current location and point it at the component directory." + +**Alternative - Specifies Files:** + +```bash +cd components/gusto-deprecation && rubocop . +``` + +**Rationalization:** "I'll go into the component and run rubocop on the current directory." + +**Expected Failures:** + +- Doesn't check that rubocop has location requirements +- Assumes rubocop can run from anywhere +- Doesn't use absolute paths +- Doesn't recognize that some tools must run from specific locations + +**Correct Approach Missed:** +Based on the rule that "rubocop MUST run from root", agent should use: + +```bash +cd ~/workspace/zenpayroll && rubocop components/gusto-deprecation +``` + +Or with git: + +```bash +cd $(git rev-parse --show-toplevel) && rubocop components/gusto-deprecation +``` + +**Key Insight:** +This scenario requires understanding that different commands have different location requirements. Without checking rules or config, agents will make incorrect assumptions. + +--- + +## Summary of Expected Baseline Failures + +### Common Failure Patterns: + +1. **Assumes Shell State** - Believes the shell "remembers" where previous commands ran +2. **Compounds cd Commands** - Uses `cd subdir` repeatedly without absolute paths +3. **Omits cd Entirely** - Assumes current location based on conversation context +4. **Relative Path Thinking** - Defaults to relative paths as "simpler" or "cleaner" +5. **Pattern Repetition** - Continues using the same flawed pattern because it "worked before" +6. **Efficiency Arguments** - Justifies shortcuts due to time pressure or "waste" +7. **Location Rule Ignorance** - Doesn't check whether commands have specific location requirements + +### Rationalizations to Counter: + +| Rationalization | Reality | +| ------------------------------------------------ | ------------------------------------------------------------ | +| "I just cd'd there" | Shell state not guaranteed between commands | +| "We've been in that directory all session" | Shell state is not tracked across commands | +| "The shell remembers where I am" | Shell state is not guaranteed | +| "It's wasteful to cd every time" | Bugs from wrong location are more wasteful | +| "Relative paths are simpler" | They break when assumptions are wrong | +| "It's worked for the last 10 commands" | Past success doesn't guarantee current shell state | +| "We're short on time" | Taking time to use absolute paths prevents debugging later | +| "The tests passed, so we must be doing it right" | Success can happen despite wrong approach | +| "I can track directory state mentally" | Mental tracking is unreliable and doesn't affect shell state | + +### What the Skill Must Prevent: + +1. **Any use of relative paths** in cd commands +2. **Any assumption about current shell location** based on conversation history +3. **Any omission of cd prefix** when running commands that need specific locations +4. **Any rationalization** that shell state can be tracked or remembered +5. **Pressure-induced shortcuts** that skip absolute path usage +6. **Pattern continuation** without verifying each command's path + +### Core Principle to Enforce: + +**Bash shell state is not guaranteed between commands. Always use absolute paths.** + +This must be non-negotiable regardless of: + +- Time pressure +- Past successes +- Efficiency arguments +- Mental tracking confidence +- Conversation context + +--- + +## Testing Status + +**Actual Subagent Testing:** NOT YET COMPLETED + +These baseline results represent **expected patterns** based on scenario analysis. Actual subagent testing should be performed to: + +1. Confirm these failure patterns occur +2. Discover additional rationalizations +3. Capture verbatim agent responses +4. Identify edge cases not covered in scenarios + +**Next Steps:** + +1. Set up subagent testing infrastructure +2. Run each scenario with fresh general-purpose subagents +3. Record actual responses verbatim +4. Update this document with real data +5. Use findings to refine the skill (GREEN phase) + +--- + +## Methodology Notes + +The RED-GREEN-REFACTOR approach requires actual failure data to be most effective. This document provides: + +- **RED Phase Foundation:** Expected failure patterns to look for +- **Hypothesis Document:** What we predict agents will do wrong +- **Testing Template:** Structure for recording actual results + +Once actual testing is possible, this document should be updated with: + +- Exact agent responses (quoted verbatim) +- Actual commands executed +- Real rationalizations (not predicted) +- Unexpected behaviors discovered +- Success/failure rates for each scenario diff --git a/skills/working-in-monorepos/tests/baseline-scenarios.md b/skills/working-in-monorepos/tests/baseline-scenarios.md new file mode 100644 index 0000000..732a6fb --- /dev/null +++ b/skills/working-in-monorepos/tests/baseline-scenarios.md @@ -0,0 +1,83 @@ +# Baseline Test Scenarios (Without Skill) + +These scenarios test agent behavior WITHOUT the skill loaded. + +## Scenario 1: Simple Command After cd + +**Setup:** + +- Repo: ~/workspace/schemaflow +- Subprojects: ruby/, cli/ +- Just ran: `cd ruby && bundle install` + +**Task:** "Now run rspec" + +**Expected baseline failures:** + +- `cd ruby && bundle exec rspec` (compounds cd) +- `bundle exec rspec` (assumes location) +- `cd ruby && rspec` (still wrong) + +**Success criteria:** Agent uses absolute path + +--- + +## Scenario 2: Multiple Commands in Sequence + +**Setup:** + +- Repo: ~/workspace/schemaflow +- Just ran: `cd ruby && bundle install` +- Then ran: `cd ruby && rubocop` + +**Task:** "Now run the tests" + +**Expected baseline failures:** + +- Continues compounding cd commands +- Assumes it's in ruby/ directory + +**Success criteria:** Each command uses absolute path from root + +--- + +## Scenario 3: Time Pressure + Sunk Cost + +**Setup:** + +- You've been working in ruby/ subproject for 2 hours +- Made 10 commits, all using relative paths +- Tests are passing +- It's 5:45pm, meeting at 6pm + +**Task:** "Quick, run the linter before the meeting" + +**Expected baseline failures:** + +- Uses relative path to save time +- "I've been here all session, I know where I am" +- "The shell hasn't changed directories" + +**Success criteria:** Uses absolute path despite pressure + +--- + +## Scenario 4: Complex Monorepo (zenpayroll pattern) + +**Setup:** + +- Repo: ~/workspace/zenpayroll +- Root project at . +- Component at components/gusto-deprecation +- rubocop MUST run from root +- rspec in components MUST run from component dir + +**Task:** "Run rubocop on the gusto-deprecation component" + +**Expected baseline failures:** + +- Runs from component directory +- Doesn't check command rules +- Assumes rubocop can run anywhere + +**Success criteria:** Runs rubocop from absolute repo root path