Skip to content
Open
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
59 changes: 57 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,62 @@ Global home affects all projects. Project folder only affects the current direct

`.agents/skills` → `~/.gemini/skills`

Project scope links only commands/hooks/skills into the project’s client folders (no AGENTS/CLAUDE/GEMINI rules).
Project scope links commands/hooks/skills and `rules/` into the project’s client folders. It does not link instruction files (`AGENTS.md`/`CLAUDE.md`/`GEMINI.md`).

## Where it links (project scope)

Commands:

`.agents/commands` → `.claude/commands`

`.agents/commands` → `.factory/commands`

`.agents/commands` → `.codex/prompts`

`.agents/commands` → `.cursor/commands`

`.agents/commands` → `.opencode/commands`

`.agents/commands` → `.gemini/commands`

Hooks:

`.agents/hooks` → `.claude/hooks`

`.agents/hooks` → `.factory/hooks`

Skills:

`.agents/skills` → `.claude/skills`

`.agents/skills` → `.factory/skills`

`.agents/skills` → `.codex/skills`

`.agents/skills` → `.cursor/skills`

`.agents/skills` → `.opencode/skills`

`.agents/skills` → `.gemini/skills`

`.agents/skills` → `.github/skills` (GitHub Copilot)

Rules:

`.agents/rules` → `.claude/rules` (Claude)

`.agents/rules` → `.cursor/rules` (Cursor)

`.agents/rules` → `.github/instructions` (GitHub Copilot)

Notes / limitations:

- This is project-scope only (it never touches `~/.claude`, `~/.cursor`, or other global rule locations).
- Tools use different rule formats. Keeping a shared `.agents/rules` folder is convenient, but you may need tool-specific files for path targeting:
- Claude rules are typically Markdown with YAML frontmatter like `paths: src/api/**/*.ts` (you can verify what’s active via `/memory` in Claude Code).
- Cursor rules are typically `.mdc` files with fields like `globs`.
- GitHub Copilot’s `.github/instructions` expects `*.instructions.md` files with frontmatter like `applyTo`.
- A practical approach is to colocate multiple formats side-by-side (e.g. `backend.md`, `backend.mdc`, `backend.instructions.md`) and let each tool ignore what it doesn’t understand.

## Development

Expand Down Expand Up @@ -109,7 +164,7 @@ bun run build
- Skills require a valid `SKILL.md` with `name` + `description` frontmatter.
- Claude prompt precedence: if `.agents/CLAUDE.md` exists, it links to `.claude/CLAUDE.md`. Otherwise `.agents/AGENTS.md` is used. After adding or removing `.agents/CLAUDE.md`, re-run dotagents and apply/repair links to update the symlink. Factory/Codex always link to `.agents/AGENTS.md`.
- Gemini context file precedence: if `.agents/GEMINI.md` exists, it links to `.gemini/GEMINI.md`. Otherwise `.agents/AGENTS.md` is used. After adding or removing `.agents/GEMINI.md`, re-run dotagents and apply/repair links to update the symlink.
- Project scope creates `.agents` plus client folders for commands/hooks/skills only. Rule files (`AGENTS.md`/`CLAUDE.md`/`GEMINI.md`) are left to the repo root so you can manage them explicitly.
- Project scope creates `.agents` plus client folders for commands/hooks/skills and `rules/`. Instruction files (`AGENTS.md`/`CLAUDE.md`/`GEMINI.md`) are left to the repo root so you can manage them explicitly.
- Backups are stored under `.agents/backup/<timestamp>` and can be restored via “Undo last change.”

## License
Expand Down
17 changes: 17 additions & 0 deletions src/core/mappings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,23 @@ export async function getMappings(opts: MappingOptions): Promise<Mapping[]> {
}
}

if (opts.scope === 'project') {
const ruleTargets = [
clients.has('claude') ? path.join(roots.claudeRoot, 'rules') : null,
clients.has('cursor') ? path.join(roots.cursorRoot, 'rules') : null,
clients.has('github') ? path.join(roots.githubRoot, 'instructions') : null,
].filter(Boolean) as string[];

if (ruleTargets.length > 0) {
mappings.push({
name: 'rules',
source: path.join(canonical, 'rules'),
targets: ruleTargets,
kind: 'dir',
});
}
}

mappings.push(
{
name: 'commands',
Expand Down
20 changes: 20 additions & 0 deletions tests/linking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,26 @@ test('project scope does not link AGENTS/CLAUDE files', async () => {
}
});

test('project scope links .agents/rules to supported tool rule folders', async () => {
const home = await makeTempDir('dotagents-home-');
const project = await makeTempDir('dotagents-project-');

const plan = await buildLinkPlan({ scope: 'project', homeDir: home, projectRoot: project });
const backup = await createBackupSession({ canonicalRoot: path.join(project, '.agents'), scope: 'project', operation: 'test' });
const result = await applyLinkPlan(plan, { backup });
await finalizeBackup(backup);
expect(result.applied).toBeGreaterThan(0);

const rules = path.join(project, '.agents', 'rules');
const cursorRules = path.join(project, '.cursor', 'rules');
const claudeRules = path.join(project, '.claude', 'rules');
const githubInstructions = path.join(project, '.github', 'instructions');

expect(await readLinkTarget(cursorRules)).toBe(rules);
expect(await readLinkTarget(claudeRules)).toBe(rules);
expect(await readLinkTarget(githubInstructions)).toBe(rules);
});

test('github skills link to .github/skills in project scope', async () => {
const home = await makeTempDir('dotagents-home-');
const project = await makeTempDir('dotagents-project-');
Expand Down