Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 4 additions & 1 deletion .ruler/mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
},
"GitHub": {
"command": "sh",
"args": ["-c", "exec mcp-remote 'https://api.githubcopilot.com/mcp/' --header \"Authorization:Bearer $(gh auth token)\""]
"args": [
"-c",
"exec mcp-remote 'https://api.githubcopilot.com/mcp/' --header \"Authorization:Bearer $(gh auth token)\""
]
},
"MemPalace": {
"command": "python3-mempalace",
Expand Down
212 changes: 3 additions & 209 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ RULES_TARGET_DIR := $(dir $(lastword $(MAKEFILE_LIST))).ruler
SKILLS_SRC_DIR := $(dir $(lastword $(MAKEFILE_LIST)))skills
SKILLS_RULER_DIR := $(dir $(lastword $(MAKEFILE_LIST))).ruler/skills
SKILLS_TARGET_DIRS := $(HOME)/.claude/skills $(HOME)/.cursor/skills $(HOME)/.codex/skills $(HOME)/.roo/skills $(HOME)/.gemini/skills $(HOME)/.agents/skills $(HOME)/.vibe/skills $(HOME)/.config/opencode/skills
SKILLS_STATE_DIR := $(HOME)/.cache/dotagents/skills
SKILLS_MANIFEST_DIR := $(SKILLS_STATE_DIR)/manifests
SKILLS_SPEC_STATE_FILE := $(SKILLS_STATE_DIR)/skills.txt.normalized
SKILLS_EXTERNAL_SOURCE_DIR := $(HOME)/.agents/skills

MCP_SRC := $(dir $(lastword $(MAKEFILE_LIST))).ruler/mcp.json
MCP_TARGET_DIRS := $(HOME)/.cursor $(HOME)/.claude $(HOME)/.codex
Expand All @@ -27,8 +23,6 @@ MCP_SETTINGS_TARGETS := $(addsuffix /settings.local.json,$(MCP_TARGET_DIRS)) $(d
DOTDIRS := .agent .agents .amazonq .augment .claude .cursor .gemini .idx .junie .kilocode .kiro .opencode .openhands .pi .qwen .roo .skillz .trae .vibe .vscode .windsurf .zed
DOTDIRS_SRC_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))

SKILLS_FILE := $(dir $(lastword $(MAKEFILE_LIST)))SKILLS.txt

# ====================================================================================
# ROOT TARGETS
# ====================================================================================
Expand All @@ -38,7 +32,7 @@ ifeq ($(DOTAGENTS_SKIP_SYNC),)
sync: ruler-prepare ## Sync project commands, skills, and MCP configuration to assistant-specific directories.
@$(MAKE) ruler-apply-global
@$(MAKE) commands-sync
@$(MAKE) skills-install
@bun run skills:install
@$(MAKE) skills-sync
@$(MAKE) mcp-sync
@$(MAKE) ruler-dotdirs-sync
Expand Down Expand Up @@ -82,208 +76,8 @@ ruler-rules-copy: ## Copy rules to .ruler directory.
# ====================================================================================
# SKILLS
# ====================================================================================

.PHONY: skills-clean
skills-clean: ## Remove all globally installed skills and cached install state.
@for target in $(SKILLS_TARGET_DIRS); do \
if [ -d "$$target" ]; then \
rm -rf "$$target"/*; \
echo "Cleaned $$target"; \
fi; \
done
@if [ -d "$(SKILLS_STATE_DIR)" ]; then \
rm -rf "$(SKILLS_STATE_DIR)"; \
echo "Cleared $(SKILLS_STATE_DIR)"; \
fi

.PHONY: skills-managed-clean
skills-managed-clean: ## Remove managed external skills recorded from SKILLS.txt.
@manifest_dir="$(SKILLS_MANIFEST_DIR)"; \
if [ -d "$$manifest_dir" ]; then \
for manifest in "$$manifest_dir"/*.skills; do \
if [ ! -f "$$manifest" ]; then \
continue; \
fi; \
while IFS= read -r skill || [ -n "$$skill" ]; do \
if [ -z "$$skill" ]; then \
continue; \
fi; \
for target in $(SKILLS_TARGET_DIRS); do \
if [ -e "$$target/$$skill" ] || [ -L "$$target/$$skill" ]; then \
rm -rf "$$target/$$skill"; \
echo "Removed $$target/$$skill"; \
fi; \
done; \
done < "$$manifest"; \
done; \
fi
@if [ -d "$(SKILLS_STATE_DIR)" ]; then \
rm -rf "$(SKILLS_STATE_DIR)"; \
echo "Cleared $(SKILLS_STATE_DIR)"; \
fi

.PHONY: skills-install
skills-install: ## Ensure skills from SKILLS.txt are installed and reconcile managed removals.
@state_dir="$(SKILLS_STATE_DIR)"; \
manifest_dir="$(SKILLS_MANIFEST_DIR)"; \
spec_state="$(SKILLS_SPEC_STATE_FILE)"; \
external_source="$(SKILLS_EXTERNAL_SOURCE_DIR)"; \
tmp_spec=$$(mktemp); \
spec_changed=0; \
failed=0; \
mkdir -p "$$state_dir" "$$manifest_dir" "$$external_source"; \
list_external_skills() { \
if [ -d "$$external_source" ]; then \
find "$$external_source" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) -exec basename {} \; | LC_ALL=C sort -u; \
fi; \
}; \
remove_repo_skills() { \
manifest_file="$$1"; \
if [ ! -f "$$manifest_file" ]; then \
return 0; \
fi; \
while IFS= read -r skill || [ -n "$$skill" ]; do \
if [ -z "$$skill" ]; then \
continue; \
fi; \
for target in $(SKILLS_TARGET_DIRS); do \
if [ -e "$$target/$$skill" ] || [ -L "$$target/$$skill" ]; then \
rm -rf "$$target/$$skill"; \
fi; \
done; \
done < "$$manifest_file"; \
}; \
list_repo_skills() { \
repo="$$1"; \
bun x skills add $$repo --global --yes --list </dev/null 2>&1 \
| sed 's/\x1b\[[0-9;]*m//g' \
| sed 's/\x1b\[?25[hl]//g' \
| sed 's/\x1b\[999D\x1b\[J//g' \
| grep -E '^│[[:space:]]{4}[a-z]' \
| sed 's/^│[[:space:]]*//'; \
}; \
install_repo() { \
repo="$$1"; \
normalized_skills="$$2"; \
manifest_file="$$3"; \
cleanup_old="$$4"; \
if [ "$$cleanup_old" = "1" ]; then \
remove_repo_skills "$$manifest_file"; \
fi; \
if [ -n "$$normalized_skills" ]; then \
skill_args=$$(printf '%s\n' "$$normalized_skills" | tr ',' '\n' | sed '/^$$/d' | while IFS= read -r s; do printf " --skill %s" "$$s"; done); \
echo "Installing selected skills from $$repo..."; \
if bun x skills add $$repo --global --yes $$skill_args </dev/null; then \
printf '%s\n' "$$normalized_skills" | tr ',' '\n' | sed '/^$$/d' | LC_ALL=C sort > "$$manifest_file"; \
echo "✓ Installed $$repo (selective)"; \
else \
echo "✗ Failed to install $$repo (continuing...)"; \
failed=1; \
fi; \
else \
echo "Installing all skills from $$repo..."; \
if bun x skills add $$repo --global --yes </dev/null; then \
list_repo_skills "$$repo" | LC_ALL=C sort > "$$manifest_file"; \
echo "✓ Installed $$repo (all)"; \
else \
echo "✗ Failed to install $$repo (continuing...)"; \
failed=1; \
fi; \
fi; \
}; \
while IFS= read -r raw_line || [ -n "$$raw_line" ]; do \
line=$$(printf '%s' "$$raw_line" | sed 's/^[[:space:]]*//; s/[[:space:]]*$$//'); \
case "$$line" in \
''|\#*) continue ;; \
esac; \
repo=$$(printf '%s\n' "$$line" | awk '{print $$1}'); \
skills_csv=$$(printf '%s\n' "$$line" | awk '{print $$2}'); \
normalized_skills=$$(printf '%s\n' "$$skills_csv" | tr ',' '\n' | sed '/^$$/d' | LC_ALL=C sort | paste -sd, -); \
printf '%s|%s\n' "$$repo" "$$normalized_skills" >> "$$tmp_spec"; \
done < "$(SKILLS_FILE)"; \
if [ "$${DOTAGENTS_FORCE_SKILLS_INSTALL:-0}" = "1" ]; then \
echo "Forcing managed external skill reinstall..."; \
$(MAKE) skills-managed-clean; \
mkdir -p "$$state_dir" "$$manifest_dir" "$$external_source"; \
while IFS='|' read -r repo normalized_skills || [ -n "$$repo$$normalized_skills" ]; do \
manifest_file="$$manifest_dir/$$(printf '%s' "$$repo" | sed 's#[^A-Za-z0-9_.-]#_#g').skills"; \
install_repo "$$repo" "$$normalized_skills" "$$manifest_file" 0; \
done < "$$tmp_spec"; \
else \
if [ -f "$$spec_state" ] && ! cmp -s "$$tmp_spec" "$$spec_state"; then \
echo "Detected SKILLS.txt changes; applying incremental update..."; \
while IFS='|' read -r old_repo old_skills || [ -n "$$old_repo$$old_skills" ]; do \
if ! grep -qF "$$old_repo|" "$$tmp_spec"; then \
echo "Removing $$old_repo (deleted from SKILLS.txt)..."; \
manifest_file="$$manifest_dir/$$(printf '%s' "$$old_repo" | sed 's#[^A-Za-z0-9_.-]#_#g').skills"; \
remove_repo_skills "$$manifest_file"; \
rm -f "$$manifest_file"; \
fi; \
done < "$$spec_state"; \
fi; \
while IFS='|' read -r repo normalized_skills || [ -n "$$repo$$normalized_skills" ]; do \
manifest_file="$$manifest_dir/$$(printf '%s' "$$repo" | sed 's#[^A-Za-z0-9_.-]#_#g').skills"; \
reinstall_repo=0; \
if [ -f "$$spec_state" ]; then \
old_line=$$(grep "^$$repo|" "$$spec_state" 2>/dev/null || true); \
new_line="$$repo|$$normalized_skills"; \
if [ -n "$$old_line" ] && [ "$$old_line" != "$$new_line" ]; then \
reinstall_repo=1; \
echo "Reinstalling $$repo (skills selection changed)"; \
fi; \
fi; \
if [ "$$reinstall_repo" = "0" ]; then \
if [ ! -f "$$manifest_file" ] || [ ! -s "$$manifest_file" ]; then \
reinstall_repo=1; \
echo "Reinstalling $$repo (missing or empty manifest)"; \
else \
while IFS= read -r skill || [ -n "$$skill" ]; do \
if [ -z "$$skill" ]; then \
continue; \
fi; \
if [ ! -e "$$external_source/$$skill" ] && [ ! -L "$$external_source/$$skill" ]; then \
reinstall_repo=1; \
echo "Reinstalling $$repo (missing $$external_source/$$skill)"; \
break; \
fi; \
done < "$$manifest_file"; \
fi; \
fi; \
if [ "$$reinstall_repo" = "1" ]; then \
install_repo "$$repo" "$$normalized_skills" "$$manifest_file" 1; \
else \
echo "Skipping $$repo (installed state matches SKILLS.txt)"; \
fi; \
done < "$$tmp_spec"; \
fi; \
cp "$$tmp_spec" "$$spec_state"; \
rm -f "$$tmp_spec"; \
if [ "$$failed" = "1" ]; then \
echo "Some skills failed to install (see above)."; \
else \
echo "Managed external skills are in sync."; \
fi

.PHONY: skills-refresh
skills-refresh: ## Force a clean reinstall of external skills and re-sync local repo skills.
@$(MAKE) skills-managed-clean
@DOTAGENTS_FORCE_SKILLS_INSTALL=1 $(MAKE) skills-install
@$(MAKE) skills-sync

.PHONY: skills-install-repo
skills-install-repo: ## Install a single skill repo. Usage: make skills-install-repo REPO=owner/repo [SKILLS=a,b,c]
@if [ -z "$(REPO)" ]; then \
echo "Error: REPO is required. Usage: make skills-install-repo REPO=owner/repo"; \
exit 1; \
fi
@if [ -n "$(SKILLS)" ]; then \
echo "Installing selected skills from $(REPO) ($(SKILLS))..."; \
bun x skills add $(REPO) --global --yes $(shell echo "$(SKILLS)" | tr ',' '\n' | sed '/^$$/d' | while read -r s; do printf " --skill $$s"; done); \
else \
echo "Installing all skills from $(REPO)..."; \
bun x skills add $(REPO) --global --yes; \
fi
@echo "✓ Installed $(REPO)"
# External skills are declared in SKILLS.txt and locked in skills-lock.json.
# Regenerate lock: `bun run skills:lock` / install: `bun run skills:install`.

.PHONY: ruler-skills-copy
ruler-skills-copy: ## Copy skills from root to .ruler/skills directory (overwrites, preserves other files).
Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ Rules for Agents
make sync
```

This runs the full pipeline: prepares `.ruler/`, generates agent instruction files (`~/.claude/CLAUDE.md`, etc.) via Ruler, reconciles managed external skills from `SKILLS.txt` against the actual installed skill directories, then syncs commands, local repo skills, MCP config, and dot directories to `$HOME`.
This runs the full pipeline: prepares `.ruler/`, generates agent instruction files (`~/.claude/CLAUDE.md`, etc.) via Ruler, installs external skills from `skills-lock.json`, then syncs commands, local repo skills, MCP config, and dot directories to `$HOME`.

To force a clean reinstall of external skills, run:
## External skills

External skills are declared in `SKILLS.txt` (canonical: repo plus comma-separated selection, no selection installs all) and locked in `skills-lock.json`, both committed. Installs go through the [skills CLI](https://github.com/vercel-labs/skills).

```bash
make skills-refresh
# Regenerate skills-lock.json from SKILLS.txt
bun run skills:lock

# Install everything in the lock that is missing (idempotent; --force reinstalls)
bun run skills:install
```

To add skills: edit `SKILLS.txt`, run `bun run skills:lock && bun run skills:install && bun run skills:lock` (the second lock captures resolved metadata), and commit both files. To remove: delete from `SKILLS.txt`, run `bunx skills remove <name> --global --yes`, then `bun run skills:lock`.
5 changes: 5 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
"lefthook:install": "lefthook install",
"lefthook:uninstall": "lefthook uninstall",
"lefthook:run": "lefthook run",
"skills:install": "bun run scripts/skills-install.ts",
"skills:lock": "bun run scripts/skills-lock.ts",
"prepare": "make ruler-prepare"
},
"devDependencies": {
"@biomejs/biome": "2.3.11",
"@intellectronica/ruler": "^0.3.23",
"lefthook": "^2.0.15"
"lefthook": "^2.0.15",
"skills": "^1.5.20"
},
"packageManager": "bun@1.2.22"
}
46 changes: 46 additions & 0 deletions rules/skills-management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Skills Management

External skills are declared in `dotagents/SKILLS.txt` (canonical) and locked
in `dotagents/skills-lock.json` (generated, committed). Skills install globally
via the skills CLI (https://github.com/vercel-labs/skills/issues/549).

## SKILLS.txt Format

```
repo <- install all skills
repo skill1,skill2,skill3 <- install selected (comma-separated)
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

## Adding Skills

```bash
# 1. List available skills in a repo
bunx skills add owner/repo --global --list

# 2. Add the repo (and selection) to dotagents/SKILLS.txt
# Repos with <10 skills: install all (no selection)
# Repos with 10+ skills: always specify selections

# 3. Regenerate the lock, install, then re-lock to capture resolved metadata
cd dotagents && bun run skills:lock && bun run skills:install && bun run skills:lock

# 4. Commit SKILLS.txt and skills-lock.json together
```

## Removing Skills

```bash
# 1. Remove from SKILLS.txt, then:
bunx skills remove <name> --global --yes
cd dotagents && bun run skills:lock
```

## Restoring (fresh machine or CI)

```bash
cd dotagents && bun run skills:install
```

- Idempotent: skips skills already present in `~/.agents/skills`
- `--force` reinstalls everything
- `make sync` runs it automatically
Loading