Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

## [0.2.0] - 2026-06-22

### Added

- Claude Code project skill discovery: when `.claude/` exists, install/sync links dot-agents skills into `.claude/skills/` so they can appear in Claude Code's `/` menu.

### Fixed

- Uninstall now removes dot-agents-managed Claude Code skill symlinks while preserving user-owned Claude Code skills.

## [0.1.1] - 2026-02-04

### Added
Expand Down Expand Up @@ -54,6 +64,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Installer skip logic now correctly includes plans/TEMPLATE.md
- Postfix increment operators causing script exit on bash 5.3+ with `set -e` ([#1](https://github.com/colmarius/dot-agents/issues/1))

[Unreleased]: https://github.com/colmarius/dot-agents/compare/v0.1.1...HEAD
[Unreleased]: https://github.com/colmarius/dot-agents/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/colmarius/dot-agents/compare/v0.1.1...v0.2.0
[0.1.1]: https://github.com/colmarius/dot-agents/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/colmarius/dot-agents/releases/tag/v0.1.0
9 changes: 9 additions & 0 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ your-project/
└── tmux/
```

If the project already has a `.claude/` directory, dot-agents also links skills into `.claude/skills/` so Claude Code can discover them as project skills and invoke them with slash commands such as `/adapt`.

### Verify Installation

**cmd:**
Expand All @@ -55,6 +57,13 @@ ls -la .agents/
cat AGENTS.md | head -20
```

For Claude Code projects, you can also check:

**cmd:**
```bash
ls -la .claude/skills/
```

## 2. Adapt AGENTS.md

**prompt:**
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ curl -fsSL https://raw.githubusercontent.com/colmarius/dot-agents/main/install.s
Pin a version:

```bash
curl -fsSL https://raw.githubusercontent.com/colmarius/dot-agents/main/install.sh | bash -s -- --ref v1.0.0
curl -fsSL https://raw.githubusercontent.com/colmarius/dot-agents/main/install.sh | bash -s -- --ref v0.2.0
```

## Documentation
Expand All @@ -20,6 +20,10 @@ curl -fsSL https://raw.githubusercontent.com/colmarius/dot-agents/main/install.s
- **[Full Docs](./docs/README.md)** — Concepts, skills reference
- **[Website](https://dot-agents.dev)** — Landing page (source: [site/](./site/))

## Agent Support

dot-agents works with any AI coding agent that reads markdown instructions. When a project already has a `.claude/` directory, install/sync also links dot-agents skills into `.claude/skills/` so Claude Code can discover them as project skills.

## Next Steps

Then:
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.1
0.2.0
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
## Reference

- [Skills](./skills.md)
- [Claude Code Project Skill Discovery](./skills.md#claude-code-project-skill-discovery)
- [AGENTS.md Template](../AGENTS.md)

## For Contributors
Expand Down
11 changes: 11 additions & 0 deletions docs/skills.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,14 @@ The `name` and `description` in the frontmatter are used for skill discovery. Th
### Preserving Custom Skills

Custom skills in `.agents/skills/` are preserved during `sync.sh` updates. Only upstream skills (adapt, ralph, research, tmux) are updated—your custom skills remain untouched.

## Claude Code Project Skill Discovery

Claude Code discovers project skills in `.claude/skills/<skill>/SKILL.md`. dot-agents keeps `.agents/skills/` as the source of truth, so when `install.sh` or `sync.sh` detects an existing `.claude/` directory it creates directory symlinks such as:

```text
.claude/skills/adapt -> ../../.agents/skills/adapt
.claude/skills/ralph -> ../../.agents/skills/ralph
```

Directory symlinks expose the whole skill, including optional supporting files like `references/` and `scripts/`. The installer skips user-owned Claude Code skills and only removes dot-agents-managed symlinks during uninstall.
147 changes: 146 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Examples:
curl -fsSL https://raw.githubusercontent.com/colmarius/dot-agents/main/install.sh | bash

# Install specific version
curl -fsSL https://raw.githubusercontent.com/colmarius/dot-agents/main/install.sh | bash -s -- --ref v0.1.1
curl -fsSL https://raw.githubusercontent.com/colmarius/dot-agents/main/install.sh | bash -s -- --ref v0.2.0

# Preview changes first
curl -fsSL https://raw.githubusercontent.com/colmarius/dot-agents/main/install.sh | bash -s -- --dry-run
Expand Down Expand Up @@ -154,6 +154,9 @@ do_uninstall() {

local removed=0

remove_claude_code_skill_symlinks
removed=$((removed + CLAUDE_SKILL_SYMLINKS_REMOVED))

# Remove AGENTS.md
if [[ -f "AGENTS.md" ]]; then
if [[ "$DRY_RUN" == "true" ]]; then
Expand Down Expand Up @@ -189,6 +192,42 @@ skipped_count=0
conflict_count=0
backup_count=0
BACKUP_DIR=""
CLAUDE_SKILL_SYMLINKS_REMOVED=0

is_dot_agents_claude_skill_symlink() {
local path="$1"
local link_target

[[ -L "$path" ]] || return 1
link_target="$(readlink "$path" 2>/dev/null || true)"
[[ "$link_target" == ../../.agents/skills/* ]]
}

remove_claude_code_skill_symlinks() {
local claude_skills_dir=".claude/skills"
local skill_link

CLAUDE_SKILL_SYMLINKS_REMOVED=0

[[ -d "$claude_skills_dir" ]] || return 0

for skill_link in "$claude_skills_dir"/*; do
[[ -L "$skill_link" ]] || continue
is_dot_agents_claude_skill_symlink "$skill_link" || continue

if [[ "$DRY_RUN" == "true" ]]; then
log_info "${RED}[REMOVE]${NC} $skill_link"
else
rm -f "$skill_link"
log_info "${RED}[REMOVE]${NC} $skill_link"
fi
CLAUDE_SKILL_SYMLINKS_REMOVED=$((CLAUDE_SKILL_SYMLINKS_REMOVED + 1))
done

if [[ "$DRY_RUN" != "true" ]]; then
rmdir "$claude_skills_dir" 2>/dev/null || true
fi
}

create_backup_dir() {
if [[ -z "$BACKUP_DIR" ]]; then
Expand Down Expand Up @@ -428,6 +467,107 @@ report_custom_skills() {
fi
}

cleanup_stale_claude_code_skill_symlinks() {
local agents_skills_dir="$1"
local claude_skills_dir="$2"
local skill_link skill_name

[[ -d "$claude_skills_dir" ]] || return 0

for skill_link in "$claude_skills_dir"/*; do
[[ -L "$skill_link" ]] || continue
is_dot_agents_claude_skill_symlink "$skill_link" || continue

skill_name="$(basename "$skill_link")"
[[ -f "$agents_skills_dir/$skill_name/SKILL.md" ]] && continue

if [[ "$DRY_RUN" == "true" ]]; then
log_info " ${RED}[REMOVE]${NC} $skill_link (stale)"
else
rm -f "$skill_link"
log_info " ${RED}[REMOVE]${NC} $skill_link (stale)"
fi
done
}

setup_claude_code_integration() {
local agents_skills_dir=".agents/skills"
local claude_skills_dir=".claude/skills"
local linked=0
local skipped=0
local skill_dir skill_name dest link_target existing_target

[[ -d ".claude" ]] || return 0
[[ -d "$agents_skills_dir" ]] || return 0

log_info ""
log_info "Detected ${BLUE}.claude/${NC} directory — linking dot-agents skills for Claude Code..."

if [[ ( -e "$claude_skills_dir" || -L "$claude_skills_dir" ) && ! -d "$claude_skills_dir" ]]; then
log_info " ${YELLOW}[SKIP]${NC} $claude_skills_dir (user-owned)"
return 0
fi

if [[ "$DRY_RUN" != "true" ]]; then
if ! mkdir -p "$claude_skills_dir"; then
log_info " ${YELLOW}[SKIP]${NC} $claude_skills_dir (could not create directory)"
return 0
fi
fi

for skill_dir in "$agents_skills_dir"/*/; do
[[ -d "$skill_dir" ]] || continue
[[ -f "$skill_dir/SKILL.md" ]] || continue

skill_name="$(basename "$skill_dir")"
dest="$claude_skills_dir/$skill_name"
link_target="../../.agents/skills/$skill_name"

if [[ -L "$dest" ]]; then
existing_target="$(readlink "$dest" 2>/dev/null || true)"
if [[ "$existing_target" == "$link_target" ]]; then
continue
fi
if is_dot_agents_claude_skill_symlink "$dest"; then
if [[ "$DRY_RUN" == "true" ]]; then
log_info " ${GREEN}[LINK]${NC} $dest → $link_target"
linked=$((linked + 1))
continue
fi
rm -f "$dest"
else
log_info " ${YELLOW}[SKIP]${NC} $dest (user-owned symlink)"
skipped=$((skipped + 1))
continue
fi
elif [[ -e "$dest" ]]; then
log_info " ${YELLOW}[SKIP]${NC} $dest (user-owned)"
skipped=$((skipped + 1))
continue
fi

if [[ "$DRY_RUN" == "true" ]]; then
log_info " ${GREEN}[LINK]${NC} $dest → $link_target"
linked=$((linked + 1))
continue
fi

if ln -s "$link_target" "$dest"; then
log_info " ${GREEN}[LINK]${NC} $dest"
linked=$((linked + 1))
else
log_info " ${YELLOW}[SKIP]${NC} $dest (could not create symlink)"
skipped=$((skipped + 1))
fi
done

cleanup_stale_claude_code_skill_symlinks "$agents_skills_dir" "$claude_skills_dir"

if [[ $linked -gt 0 || $skipped -gt 0 ]]; then
log_info " Claude Code skills linked: $linked, skipped: $skipped"
fi
}

ensure_gitignore_entry() {
local gitignore_file=".agents/.gitignore"
local backup_entry="../.dot-agents-backup/"
Expand Down Expand Up @@ -626,6 +766,11 @@ main() {
# Ensure .agents/.gitignore includes backup directory
ensure_gitignore_entry

# Link dot-agents skills into Claude Code's project skill directory when present.
if [[ "$DIFF_ONLY" != "true" ]]; then
setup_claude_code_integration
fi

# Skip metadata write in diff-only mode
if [[ "$DIFF_ONLY" != "true" ]]; then
write_metadata
Expand Down
3 changes: 2 additions & 1 deletion site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ <h1>Ship faster with <span class="highlight">structured AI workflows</span></h1>
<p class="install-hint">Then point your agent at a real problem and iterate.</p>
<details class="install-details">
<summary>Pin a version</summary>
<code>curl -fsSL https://raw.githubusercontent.com/colmarius/dot-agents/main/install.sh | bash -s -- --ref v0.1.1</code>
<code>curl -fsSL https://raw.githubusercontent.com/colmarius/dot-agents/main/install.sh | bash -s -- --ref v0.2.0</code>
</details>
</section>

Expand All @@ -76,6 +76,7 @@ <h2>What you get</h2>
├── research/ <span class="tree-comment"># Discoveries and context</span>
└── skills/ <span class="tree-comment"># adapt, ralph, research, tmux</span></code></pre>
</div>
<p class="install-hint">Using Claude Code? If <code>.claude/</code> already exists, install/sync links these skills into <code>.claude/skills/</code> for project skill discovery.</p>
</section>

<section class="features">
Expand Down
98 changes: 98 additions & 0 deletions test/integration/install.bats
Original file line number Diff line number Diff line change
Expand Up @@ -413,3 +413,101 @@ teardown() {
# Should NOT report core skills (they're from upstream)
refute_output --partial "Custom skills preserved:"
}

# ===== Task 6: Claude Code skill discovery tests =====

@test "install creates Claude Code skill directory symlinks when .claude exists" {
mkdir -p .claude

run bash "$INSTALL_SCRIPT" --yes
assert_success

assert_output --partial "Claude Code skills linked"

[ -L ".claude/skills/adapt" ]
[ -L ".claude/skills/ralph" ]
[ -L ".claude/skills/research" ]
[ -L ".claude/skills/tmux" ]

local target
target="$(readlink .claude/skills/adapt)"
[ "$target" = "../../.agents/skills/adapt" ]

# Directory symlinks expose supporting skill files, not just SKILL.md.
[ -f ".claude/skills/ralph/references/progress-format.md" ]
}

@test "install does not create .claude when absent" {
run bash "$INSTALL_SCRIPT" --yes
assert_success

[ ! -d ".claude" ]
}

@test "install skips Claude Code integration when .claude/skills is user-owned file" {
mkdir -p .claude
echo "user file" > .claude/skills

run bash "$INSTALL_SCRIPT" --yes
assert_success

assert_output --partial ".claude/skills (user-owned)"
run cat .claude/skills
assert_output "user file"
}

@test "install preserves existing Claude Code user skill directory" {
mkdir -p .claude/skills/adapt
echo "# User adapt skill" > .claude/skills/adapt/SKILL.md

run bash "$INSTALL_SCRIPT" --yes
assert_success

assert_output --partial "SKIP"
assert_output --partial ".claude/skills/adapt (user-owned)"

[ ! -L ".claude/skills/adapt" ]
run cat .claude/skills/adapt/SKILL.md
assert_output "# User adapt skill"
}

@test "install preserves existing Claude Code user symlink" {
mkdir -p .claude/skills
ln -s ../../elsewhere/adapt .claude/skills/adapt

run bash "$INSTALL_SCRIPT" --yes
assert_success

assert_output --partial "user-owned symlink"

local target
target="$(readlink .claude/skills/adapt)"
[ "$target" = "../../elsewhere/adapt" ]
}

@test "sync removes stale dot-agents Claude Code skill symlinks" {
mkdir -p .claude

bash "$INSTALL_SCRIPT" --yes
ln -s ../../.agents/skills/old-skill .claude/skills/old-skill

run bash "$INSTALL_SCRIPT" --yes
assert_success

[ ! -L ".claude/skills/old-skill" ]
}

@test "--uninstall removes only dot-agents Claude Code skill symlinks" {
mkdir -p .claude

bash "$INSTALL_SCRIPT" --yes
mkdir -p .claude/skills/my-custom-skill
echo "# My Custom" > .claude/skills/my-custom-skill/SKILL.md

run bash "$INSTALL_SCRIPT" --uninstall --yes
assert_success

[ ! -L ".claude/skills/adapt" ]
[ ! -L ".claude/skills/ralph" ]
[ -f ".claude/skills/my-custom-skill/SKILL.md" ]
}
Loading