Initial commit
This commit is contained in:
18
.claude-plugin/plugin.json
Normal file
18
.claude-plugin/plugin.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# working-in-monorepos
|
||||
|
||||
Navigate and execute commands in monorepo subprojects
|
||||
18
commands/monorepo-init.md
Normal file
18
commands/monorepo-init.md
Normal file
@@ -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.
|
||||
13
hooks/hooks.json
Normal file
13
hooks/hooks.json
Normal file
@@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
34
hooks/scripts/detect-monorepo.sh
Executable file
34
hooks/scripts/detect-monorepo.sh
Executable file
@@ -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
|
||||
81
plugin.lock.json
Normal file
81
plugin.lock.json
Normal file
@@ -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": []
|
||||
}
|
||||
}
|
||||
50
skills/working-in-monorepos/README.md
Normal file
50
skills/working-in-monorepos/README.md
Normal file
@@ -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
|
||||
231
skills/working-in-monorepos/SKILL.md
Normal file
231
skills/working-in-monorepos/SKILL.md
Normal file
@@ -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" }
|
||||
}
|
||||
}
|
||||
```
|
||||
15
skills/working-in-monorepos/examples/schemaflow.json
Normal file
15
skills/working-in-monorepos/examples/schemaflow.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
31
skills/working-in-monorepos/examples/zenpayroll.json
Normal file
31
skills/working-in-monorepos/examples/zenpayroll.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
203
skills/working-in-monorepos/scripts/monorepo-init
Executable file
203
skills/working-in-monorepos/scripts/monorepo-init
Executable file
@@ -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
|
||||
323
skills/working-in-monorepos/tests/baseline-results.md
Normal file
323
skills/working-in-monorepos/tests/baseline-results.md
Normal file
@@ -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
|
||||
83
skills/working-in-monorepos/tests/baseline-scenarios.md
Normal file
83
skills/working-in-monorepos/tests/baseline-scenarios.md
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user