Skip to content
Closed
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
70 changes: 70 additions & 0 deletions packages/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!-- PRPM_MANIFEST_START -->

<skills_system priority="1">
<usage>
When users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.

How to use skills (loaded into main context):
- Use the <path> from the skill entry below
- Invoke: Bash("cat <path>")
- The skill content will load into your current context
- Example: Bash("cat .openskills/backend-architect/SKILL.md")

Usage notes:
- Skills share your context window
- Do not invoke a skill that is already loaded in your context
</usage>

<available_skills>

<skill activation="lazy">
<name>claude-skill</name>
<description>test-kiro skill</description>
<path>.openskills/claude-skill/SKILL.md</path>
</skill>

<skill activation="lazy">
<name>complex-skill</name>
<description>complex-skill skill</description>
<path>.openskills/complex-skill/SKILL.md</path>
</skill>

</available_skills>
</skills_system>

<agents_system priority="1">
<usage>
Agents are specialized AI assistants that run in independent contexts for complex multi-step tasks.

How to use agents (spawned with independent context):
- The <path> from the agent entry contains the agent definition file
- The agent definition will be automatically loaded into the subagent's context
- Invoke: Task(subagent_type="<agent-name>", prompt="task description")
- The agent runs in a separate context and returns results
- Example: Task(subagent_type="code-reviewer", prompt="Review the authentication code in auth.ts")

Usage notes:
- Agents have independent context windows
- Each agent invocation is stateless
- Agents are spawned as subprocesses via the Task tool
- The agent's AGENT.md file is loaded into the subagent's context automatically
</usage>

<available_agents>

<agent activation="lazy">
<name>windsurf-agent</name>
<description>windsurf-agent agent</description>
<path>.openagents/windsurf-agent/AGENT.md</path>
</agent>

<agent activation="lazy">
<name>claude-agent</name>
<description>claude-agent agent</description>
<path>.openagents/claude-agent/AGENT.md</path>
</agent>

</available_agents>
</agents_system>

<!-- PRPM_MANIFEST_END -->
69 changes: 63 additions & 6 deletions packages/cli/src/__tests__/install-file-locations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { vi, describe, it, expect, beforeEach, afterEach, beforeAll, afterAll, t
import { handleInstall, createInstallCommand } from '../commands/install';
import { getRegistryClient } from '@pr-pm/registry-client';
import { getConfig } from '../core/user-config';
import { saveFile, getDestinationDir } from '../core/filesystem';
import { saveFile, getDestinationDir, createSymlink } from '../core/filesystem';
import { readLockfile, writeLockfile, addToLockfile, createLockfile, setPackageIntegrity, parseLockfileKey } from '../core/lockfile';
import { gzipSync } from 'zlib';
import * as fs from 'fs/promises';
Expand Down Expand Up @@ -41,6 +41,7 @@ vi.mock('../core/filesystem', async () => {
...actual,
saveFile: vi.fn(actual.saveFile),
ensureDirectoryExists: vi.fn(actual.ensureDirectoryExists),
createSymlink: vi.fn().mockResolvedValue({ symlinked: true, path: '' }),
};
});

Expand Down Expand Up @@ -394,23 +395,79 @@ description: Run headless orchestration
{ from: 'user' },
);

// First format (claude) uses saveFile
expect(saveFile).toHaveBeenCalledWith(
path.join(testDir, '.claude', 'skills', 'running-headless-orchestrator', 'SKILL.md'),
expect.any(String),
);
expect(saveFile).toHaveBeenCalledWith(
path.join(testDir, '.agents', 'skills', 'running-headless-orchestrator', 'SKILL.md'),
expect.any(String),
// Second format (codex) uses symlink (claude and codex are symlink-compatible for skills)
expect(createSymlink).toHaveBeenCalledWith(
expect.stringContaining('.claude/skills/running-headless-orchestrator'),
expect.stringContaining('.agents/skills/running-headless-orchestrator'),
true,
);
expect(addToLockfile).toHaveBeenCalledWith(
expect.any(Object),
'@agent-relay/running-headless-orchestrator',
expect.objectContaining({ format: 'claude', global: true }),
);
// Note: addToLockfile is NOT called for symlinked format (codex) since it reuses claude's installation
});

it('creates symlinks for claude,codex skill installs (symlink-compatible formats)', async () => {
// Mock homedir for global install and reset symlink mock
vi.spyOn(os, 'homedir').mockReturnValue(testDir);
vi.mocked(createSymlink).mockResolvedValue({ symlinked: true, path: '' });

const mockPackage = {
id: '@test/multi-format-skill',
name: '@test/multi-format-skill',
format: 'claude',
subtype: 'skill',
tags: [],
total_downloads: 100,
verified: true,
latest_version: {
version: '1.0.0',
tarball_url: 'https://example.com/package.tar.gz',
},
};

mockClient.getPackage.mockResolvedValue(mockPackage);
mockClient.downloadPackage.mockResolvedValue(gzipSync(`---
name: multi-format-skill
description: Test multi-format skill
---

# Multi-Format Skill Test
`));

const cmd = createInstallCommand();
cmd.exitOverride();

await cmd.parseAsync(
['@test/multi-format-skill', '--as', 'claude,codex', '--global'],
{ from: 'user' },
);

// First format (claude) saves files normally
expect(saveFile).toHaveBeenCalledWith(
path.join(testDir, '.claude', 'skills', 'multi-format-skill', 'SKILL.md'),
expect.any(String),
);

// Codex uses symlinks to claude installation (claude and codex are symlink-compatible for skills)
expect(createSymlink).toHaveBeenCalledWith(
expect.stringContaining('.claude/skills/multi-format-skill'),
expect.stringContaining('.agents/skills/multi-format-skill'),
true,
);

// Only claude installation adds to lockfile
expect(addToLockfile).toHaveBeenCalledWith(
expect.any(Object),
'@agent-relay/running-headless-orchestrator',
expect.objectContaining({ format: 'codex', global: true }),
'@test/multi-format-skill',
expect.objectContaining({ format: 'claude', global: true }),
);
});
});
Expand Down
Loading
Loading